root@debian11:/home/rpdzkj/pcie_bt1120/build# sudo ./app --card=/dev/dri/card0 --w=1920 --h=1080 --fps=50 --addr=0xB1000000 --src-fmt=RGB888 --src-stride=5760
找不到可用的 connector/CRTC(请确认显示子系统/VOP2 已上线)
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <csignal>
#include <string>
#include <vector>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <cmath> // std::abs
#include <climits> // INT_MAX
// libdrm
#include <drm.h>
#include <drm_mode.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
// =========== 选择 VOP 帧缓冲格式 ===========
// 大多数 Rockchip VOP2 支持 UYVY;如遇到 plane 不支持,可以切到 XRGB8888
#ifndef FB_USE_UYVY
#define FB_USE_UYVY 1
#endif
#if FB_USE_UYVY
#define FB_FMT DRM_FORMAT_UYVY
#define FB_BPP 16
#define FB_BYTES_PER_PIXEL 2
#else
#define FB_FMT DRM_FORMAT_XRGB8888
#define FB_BPP 32
#define FB_BYTES_PER_PIXEL 4
#endif
// XDMA C 接口
extern "C" {
int pcie_init(void);
void pcie_deinit(void);
void c2h_transfer(unsigned int addr, unsigned int len, unsigned char *dst);
}
static volatile int running = 1;
static void on_sig(int){ running = 0; }
static bool sw(const char* s,const char* p){ return std::strncmp(s,p,std::strlen(p))==0; }
struct Args {
std::string card = "/dev/dri/card0";
int w=1920, h=1080, fps=50;
uint64_t xdma_addr = 0xB1000000ULL;
std::string src_fmt = "UYVY"; // UYVY | RGB888
int src_stride = 0; // 0 => 自动 w*Bpp
int conn_id = 0; // 手动指定连接器(可选)
bool list=false;
};
static Args parse(int ac,char**av){
Args a; for(int i=1;i<ac;i++){
if(!std::strcmp(av[i],"--list")) a.list=true;
else if(sw(av[i],"--card=")) a.card=av[i]+7;
else if(sw(av[i],"--w=")) a.w=std::atoi(av[i]+4);
else if(sw(av[i],"--h=")) a.h=std::atoi(av[i]+4);
else if(sw(av[i],"--fps=")) a.fps=std::atoi(av[i]+6);
else if(sw(av[i],"--addr=")) a.xdma_addr=strtoull(av[i]+7,nullptr,0);
else if(sw(av[i],"--src-fmt=")) a.src_fmt=av[i]+10;
else if(sw(av[i],"--src-stride=")) a.src_stride=std::atoi(av[i]+13);
else if(sw(av[i],"--conn-id=")) a.conn_id=std::atoi(av[i]+10);
}
if(a.fps<=0) a.fps=50;
return a;
}
// clamp
static inline uint8_t clamp_u8(int v){ if(v<0) return 0; if(v>255) return 255; return (uint8_t)v; }
// 高效 RGB888 -> UYVY(2 像素一组,平均 U/V)
static void rgb888_to_uyvy(const uint8_t* src, uint8_t* dst, int w, int h, int src_stride_bytes, int dst_pitch){
const int bpp = 3;
for(int y=0;y<h;y++){
const uint8_t* sline = src + y*src_stride_bytes;
uint8_t* dline = dst + y*dst_pitch;
for(int x=0;x<w; x+=2){
const uint8_t *p0 = sline + x*bpp;
const uint8_t *p1 = p0 + bpp;
int r0=p0[0], g0=p0[1], b0=p0[2];
int r1=p1[0], g1=p1[1], b1=p1[2];
int Y0 = ( 77*r0 + 150*g0 + 29*b0 + 128) >> 8; // 0.299 0.587 0.114
int Y1 = ( 77*r1 + 150*g1 + 29*b1 + 128) >> 8;
// BT.601 近似
int U0 = (-43*r0 - 85*g0 + 128*b0 + 32768) >> 8;
int V0 = (128*r0 -107*g0 - 21*b0 + 32768) >> 8;
int U1 = (-43*r1 - 85*g1 + 128*b1 + 32768) >> 8;
int V1 = (128*r1 -107*g1 - 21*b1 + 32768) >> 8;
int U = (U0 + U1) >> 1;
int V = (V0 + V1) >> 1;
dline[0] = clamp_u8(U);
dline[1] = clamp_u8(Y0);
dline[2] = clamp_u8(V);
dline[3] = clamp_u8(Y1);
dline += 4;
}
}
}
#if !FB_USE_UYVY
// UYVY -> XRGB8888(如改用 XRGB8888)
static void uyvy_to_xrgb8888(const uint8_t* src, uint8_t* dst, int w, int h, int src_stride, int dst_pitch){
for(int y=0;y<h;y++){
const uint8_t* s = src + y*src_stride;
uint32_t* d = (uint32_t*)(dst + y*dst_pitch);
for(int x=0;x<w; x+=2){
uint8_t U = s[0], Y0 = s[1], V = s[2], Y1 = s[3];
s += 4;
auto YUV2RGB = [](int Y,int U,int V){
int C = Y - 16, D = U - 128, E = V - 128;
int R = (298*C + 409*E + 128) >> 8;
int G = (298*C - 100*D - 208*E + 128) >> 8;
int B = (298*C + 516*D + 128) >> 8;
return (0xFFu<<24) | ((uint8_t)clamp_u8(R)<<16) | ((uint8_t)clamp_u8(G)<<8) | ((uint8_t)clamp_u8(B)<<0);
};
d[0] = YUV2RGB(Y0,U,V);
d[1] = YUV2RGB(Y1,U,V);
d += 2;
}
}
}
#endif
struct DumbFB {
uint32_t fb_id=0, handle=0, pitch=0;
uint64_t size=0;
uint8_t* map=nullptr;
};
static int create_fb(int fd, int w, int h, DumbFB& out){
drm_mode_create_dumb creq{}; creq.width=w; creq.height=h; creq.bpp=FB_BPP;
if(ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq)<0){ perror("CREATE_DUMB"); return -1; }
uint32_t fb_id=0;
uint32_t handles[4]={creq.handle,0,0,0};
uint32_t pitches[4]={creq.pitch,0,0,0}; // ★ 使用内核返回 pitch
uint32_t offsets[4]={0,0,0,0};
if(drmModeAddFB2(fd, w, h, FB_FMT, handles, pitches, offsets, &fb_id, 0)!=0){
perror("drmModeAddFB2");
drm_mode_destroy_dumb d{}; d.handle=creq.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d);
return -2;
}
drm_mode_map_dumb mreq{}; mreq.handle=creq.handle;
if(ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)<0){
perror("MAP_DUMB");
drmModeRmFB(fd, fb_id);
drm_mode_destroy_dumb d{}; d.handle=creq.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d);
return -3;
}
void* map = mmap(nullptr, creq.size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mreq.offset);
if(map==MAP_FAILED){
perror("mmap");
drmModeRmFB(fd, fb_id);
drm_mode_destroy_dumb d{}; d.handle=creq.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d);
return -4;
}
out.fb_id=fb_id; out.handle=creq.handle; out.pitch=creq.pitch; out.size=creq.size; out.map=(uint8_t*)map;
return 0;
}
static void destroy_fb(int fd, DumbFB& fb){
if(fb.map && fb.size) munmap(fb.map, fb.size);
if(fb.fb_id) drmModeRmFB(fd, fb.fb_id);
if(fb.handle){ drm_mode_destroy_dumb d{}; d.handle=fb.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d); }
fb = DumbFB{};
}
static void list_res(int fd){
drmModeRes* r = drmModeGetResources(fd);
if(!r){ printf("no resources\n"); return; }
printf("CRTCs:"); for(int i=0;i<r->count_crtcs;i++) printf(" %d", r->crtcs[i]); printf("\n");
for(int i=0;i<r->count_connectors;i++){
auto*c = drmModeGetConnector(fd, r->connectors[i]); if(!c) continue;
const char* st = (c->connection==DRM_MODE_CONNECTED)?"connected":(c->connection==DRM_MODE_DISCONNECTED)?"disconnected":"unknown";
printf("conn-id=%d type=%d status=%s modes=%d\n", c->connector_id, c->connector_type, st, c->count_modes);
for(int m=0;m<c->count_modes;m++){ auto& mm=c->modes[m]; printf(" %s %dx%d@%d\n", mm.name, mm.hdisplay, mm.vdisplay, mm.vrefresh); }
drmModeFreeConnector(c);
}
drmModeFreeResources(r);
}
// 选择连接器 & 匹配 CRTC(根据 encoder->possible_crtcs)
static bool pick_conn_enc_crtc(int fd, int prefer_conn_id, uint32_t& conn_id, uint32_t& enc_id, uint32_t& crtc_id){
drmModeRes* res = drmModeGetResources(fd);
if(!res) return false;
// 选 connector:优先用户指定,其次已连接,再否则随便一个
drmModeConnector* chosen_conn = nullptr;
for(int i=0;i<res->count_connectors;i++){
drmModeConnector* c = drmModeGetConnector(fd, res->connectors[i]); if(!c) continue;
if(prefer_conn_id>0 && (int)c->connector_id==prefer_conn_id){ chosen_conn=c; break; }
if(!chosen_conn || c->connection==DRM_MODE_CONNECTED) { if(chosen_conn) drmModeFreeConnector(chosen_conn); chosen_conn=c; if(c->connection==DRM_MODE_CONNECTED) break; }
else drmModeFreeConnector(c);
}
if(!chosen_conn){ drmModeFreeResources(res); return false; }
// 选 encoder
drmModeEncoder* chosen_enc = nullptr;
if(chosen_conn->encoder_id){
chosen_enc = drmModeGetEncoder(fd, chosen_conn->encoder_id);
} else if (chosen_conn->count_encoders>0){
chosen_enc = drmModeGetEncoder(fd, chosen_conn->encoders[0]);
}
if(!chosen_enc){
drmModeFreeConnector(chosen_conn); drmModeFreeResources(res); return false;
}
// 选 crtc:从 possible_crtcs bitmask 中挑第一个
int crtc_index = -1;
for(int i=0;i<res->count_crtcs;i++){
if(chosen_enc->possible_crtcs & (1<<i)){ crtc_index = i; break; }
}
if(crtc_index<0){ // 兜底:用第一个
crtc_index = 0;
}
conn_id = chosen_conn->connector_id;
enc_id = chosen_enc->encoder_id;
crtc_id = res->crtcs[crtc_index];
drmModeFreeEncoder(chosen_enc);
drmModeFreeConnector(chosen_conn);
drmModeFreeResources(res);
return conn_id && crtc_id;
}
// 在连接器 modes 里找一个最合适的(优先首选,其次匹配分辨率,最后第一个)
static bool choose_mode(int fd, uint32_t conn_id, int want_w, int want_h, int want_hz, drmModeModeInfo& out){
drmModeConnector* c = drmModeGetConnector(fd, conn_id);
if(!c) return false;
if(c->count_modes<=0){ drmModeFreeConnector(c); return false; }
// 1) Preferred
for(int i=0;i<c->count_modes;i++){
auto&m = c->modes[i];
if(m.type & DRM_MODE_TYPE_PREFERRED){ out = m; drmModeFreeConnector(c); return true; }
}
// 2) 分辨率匹配 && 刷新率最接近
int best = -1, best_delta = INT_MAX;
for(int i=0;i<c->count_modes;i++){
auto&m = c->modes[i];
if(m.hdisplay==want_w && m.vdisplay==want_h){
int d = std::abs((int)m.vrefresh - (int)want_hz); // 显式转 int,避免重载歧义
if(d < best_delta){ best = i; best_delta = d; }
}
}
if(best>=0){ out = c->modes[best]; drmModeFreeConnector(c); return true; }
// 3) 退而求其次:第一个可用
out = c->modes[0];
drmModeFreeConnector(c);
return true;
}
int main(int ac, char** av){
signal(SIGINT,on_sig); signal(SIGTERM,on_sig);
Args args = parse(ac,av);
int fd = open(args.card.c_str(), O_RDWR | O_CLOEXEC);
if(fd<0){ perror("open card"); return 1; }
if(args.list){ list_res(fd); close(fd); return 0; }
uint32_t conn_id=0, enc_id=0, crtc_id=0;
if(!pick_conn_enc_crtc(fd, args.conn_id, conn_id, enc_id, crtc_id)){
fprintf(stderr,"找不到可用的 connector/CRTC(请确认显示子系统/VOP2 已上线)\n");
close(fd); return 1;
}
drmModeModeInfo mode{};
if(!choose_mode(fd, conn_id, args.w, args.h, args.fps, mode)){
fprintf(stderr,"未获取到可用显示模式\n");
close(fd); return 1;
}
fprintf(stderr, "Use mode: %s %dx%d@%d\n", mode.name, mode.hdisplay, mode.vdisplay, mode.vrefresh);
// 创建双缓冲 FB
DumbFB fb[2];
if(create_fb(fd, mode.hdisplay, mode.vdisplay, fb[0]) || create_fb(fd, mode.hdisplay, mode.vdisplay, fb[1])){
fprintf(stderr,"create dumb fb failed\n");
destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 1;
}
// 绑定首帧到 CRTC(legacy 模式设置)
if(drmModeSetCrtc(fd, crtc_id, fb[0].fb_id, 0,0, &conn_id, 1, &mode)!=0){
perror("drmModeSetCrtc");
destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 1;
}
// XDMA init
if(pcie_init()<0){
fprintf(stderr,"pcie_init failed\n");
destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 1;
}
const bool src_is_rgb = (args.src_fmt=="RGB888");
const int src_bpp = src_is_rgb? 3 : 2;
const int src_stride = (args.src_stride>0)? args.src_stride : (args.w*src_bpp);
const size_t pull_bytes = (size_t)src_stride * args.h;
std::vector<uint8_t> srcbuf(pull_bytes);
const int interval_us = 1000000 / (args.fps?args.fps:50);
// page-flip 事件
drmEventContext ev{}; ev.version = DRM_EVENT_CONTEXT_VERSION;
auto handler = [](int, unsigned, unsigned, unsigned, void* u){ *(int*)u = 1; };
ev.page_flip_handler = handler;
int front = 0;
fprintf(stderr, "Start: %dx%d %s -> FB fmt=0x%08x fps=%d addr=0x%llx stride=%d\n",
args.w, args.h, src_is_rgb?"RGB888":"UYVY", FB_FMT, args.fps,
(unsigned long long)args.xdma_addr, src_stride);
while(running){
// 从 FPGA 拉一帧
c2h_transfer((unsigned)args.xdma_addr, (unsigned)pull_bytes, srcbuf.data());
// 写到后备 FB
uint8_t* dst = fb[front^1].map;
#if FB_USE_UYVY
if(src_is_rgb){
rgb888_to_uyvy(srcbuf.data(), dst, args.w, args.h, src_stride, fb[front^1].pitch);
}else{
// UYVY -> UYVY:逐行拷贝,照顾 pitch 差异
int copy_bytes_per_line = args.w * 2;
for(int y=0;y<args.h;y++){
std::memcpy(dst + y*fb[front^1].pitch, srcbuf.data()+ y*src_stride, copy_bytes_per_line);
}
}
#else
if(src_is_rgb){
// RGB888 -> XRGB8888(简单逐像素填充;更快可用 RGA/NEON)
for(int y=0;y<args.h;y++){
const uint8_t* s = srcbuf.data() + y*src_stride;
uint32_t* d = (uint32_t*)(dst + y*fb[front^1].pitch);
for(int x=0;x<args.w;x++){
uint8_t r=s[0], g=s[1], b=s[2]; s+=3;
d[x] = 0xFF000000u | (r<<16) | (g<<8) | (b<<0);
}
}
}else{
// UYVY -> XRGB8888
uyvy_to_xrgb8888(srcbuf.data(), dst, args.w, args.h, src_stride, fb[front^1].pitch);
}
#endif
// 提交翻转
int done=0;
if(drmModePageFlip(fd, crtc_id, fb[front^1].fb_id, DRM_MODE_PAGE_FLIP_EVENT, &done)!=0){
// 某些驱动可能第一次不支持 flip,退化为直接 setCrtc
perror("drmModePageFlip");
drmModeSetCrtc(fd, crtc_id, fb[front^1].fb_id, 0,0, &conn_id, 1, &mode);
usleep(interval_us);
front ^= 1;
}else{
struct pollfd pfd{ .fd=fd, .events=POLLIN, .revents=0 };
int r = poll(&pfd, 1, interval_us/1000);
if(r>0 && (pfd.revents & POLLIN)) drmHandleEvent(fd, &ev);
front ^= 1;
}
}
pcie_deinit();
destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]);
close(fd);
return 0;
}
最新发布