Linux常见问题-获取Vsync信号

1 Linux 获取VSYNC核心原理解读

android系统上 获取VSYNC有现成的方法,但是Linux上想获取Vsync还是不那么直接的,这里主要是介绍使用libdrm库获取vsync信号的方法(该方法适用于ubuntu和debian系统)

我们首先要理解几个基础概念,行同步/场同步/行消隐/场消隐,如下所示:

  • 行同步HSYNC:电视信号发送端为了使接收端的行扫描与场扫描规律与其同步,在行扫描正常结束后,向接收机发出一个脉冲信号,表示这一行已经结束,这个脉冲信号就是行同步信号。
  • 场同步VSYNC:电视信号发送端为了使接收端的行扫描与场扫描规律与其同步,在场扫描正常结束后,向接收机发出一个脉冲信号,表示这一场已经结束,这个脉冲信号就是场同步信号。
  • 行消隐HBLANK:指行与行之间的返回过程。而扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做行消隐。
  • 场消隐VBLANK:扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐。

使用Linux系统 libdrm库 获取Vsync的核心原理:使用drmWaitVBlank()可以获得场消隐VBlank,可以简单理解为要想获得2个Vsync之间的时间间隔,可以通过获得 场消隐VBlank之间的时间间隔,因为每一次场消隐伴随着场同步信号VSYNC的触发。

2 Linux获取vsync demo实现

有了这个理解,使用drmWaitVBlank方法就可以使用VSYNC信号了,demo源码如下所示:

#include 
#include 
#include 
#include 
#include 

#define DRM_VBLANK_HIGH_CRTC_SHIFT 1

struct timeval tp1, tp2;

void onVsync(){
    long usec1 = 0;
    gettimeofday(&tp2, NULL);
    usec1 = 1000000 * (tp2.tv_sec - tp1.tv_sec) + (tp2.tv_usec - tp1.tv_usec);
    printf("onVsync= %f ms \n",usec1/1000.0f);
    gettimeofday(&tp1, NULL);
}

int main(int argc, char** argv) 
{
    int ret = 0;
    drmVBlank vbl;
    
    memset(&vbl, 0, sizeof(vbl));
    vbl.request.type = (drmVBlankSeqType)(DRM_VBLANK_RELATIVE);;
    vbl.request.sequence = 0;
    
    //open card0 device point
    int fd = open("/dev/dri/card0",O_RDWR);
    if ( fd<0 ) {
        printf("failed to open /dev/dri/card0");
        return -1;
    }

    gettimeofday(&tp1, NULL);
    while (1) {
        uint32_t high_crtc = (0 << DRM_VBLANK_HIGH_CRTC_SHIFT);
    	vbl.request.type = (drmVBlankSeqType)(DRM_VBLANK_RELATIVE | (high_crtc & DRM_VBLANK_HIGH_CRTC_MASK) );
    	vbl.request.sequence = 1;
    	//wait next vsync
    	ret = drmWaitVBlank(fd, &vbl);
    	if (ret != 0) {
    	    printf("drmWaitVBlank failed ret=%d\n", ret);
    	    return -1;
    	}
        //vsync callback
        onVsync();
    }
    return 0;
}

接下来 编译一下代码,编译 & 交叉编译 如下:

#Linux PC端编译
$g++ vsynctest.cpp -I/usr/include/libdrm -ldrm

#嵌入式Linux交叉编译,注意:aarch64交叉编译要指定sysroot路径,否则找不到drm库
$aarch64-linux-gnu-g++ vsynctest.cpp -I/usr/include/drm -ldrm --sysroot=[sysroot路径]

生成./a.out,分别在Linux和嵌入式Linux上执行,效果基本一致,如下所示:

onVsync= 11.444000 ms 
onVsync= 16.332001 ms 
onVsync= 16.792999 ms 
onVsync= 16.490999 ms 
onVsync= 16.605000 ms 
onVsync= 16.643999 ms 
onVsync= 16.688999 ms 
onVsync= 16.639999 ms 
onVsync= 16.576000 ms 
onVsync= 16.611000 ms 
onVsync= 16.663000 ms 

看效果还是可以用的。但是注意了,这样做有一个问题,单个屏幕HDMI这种是没问题的,毕竟只有一个屏幕,但如果有多个屏幕,比如嵌入式Linux上某些特殊设备有扩展屏之类的,那这样做是无法区分多个屏幕的,那怎么办呢?接下来我们就看如何获取不同屏幕的vsync。

3 Linux 获取特定设备Vsync

这时候我们就需要通过配置信息来区分屏幕了,配置信息主要是crtc 和connector(drm相关知识)以及crtc对应的framebuffer id,这里给出另一个demo。如下所示:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define TARGET_CRTC_ID 102
#define TARGET_CONN_ID 208

static int count = 0; 
struct timeval tp1, tp2;
int fb_id;

void onVsync(){
    long usec1 = 0;
    gettimeofday(&tp2, NULL);
    usec1 = 1000000 * (tp2.tv_sec - tp1.tv_sec) + (tp2.tv_usec - tp1.tv_usec);
    printf("onVsync= %f ms \n",usec1/1000.0f);
    gettimeofday(&tp1, NULL);
	count++;
	if(count>1000){
		exit(0);
	}
}

static void modeset_page_flip_handler(int fd, uint32_t frame,
				    uint32_t sec, uint32_t usec,
				    void *data)
{
	uint32_t crtc_id = *(uint32_t *)data;
	drmModePageFlip(fd, crtc_id, fb_id,DRM_MODE_PAGE_FLIP_EVENT, data);
	onVsync();
}

int main(int argc, char **argv)
{
	int fd;
	drmEventContext ev = {};
	drmModeConnectorPtr conn = NULL;
	drmModeResPtr res = NULL;
	uint32_t conn_id = TARGET_CONN_ID;
	uint32_t crtc_id = 0;
	drmModeCrtcPtr crtc = NULL;
	int i=0;

	ev.version = DRM_EVENT_CONTEXT_VERSION;
	ev.page_flip_handler = modeset_page_flip_handler;

	//open gpu
	fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	if(fd<0){
		printf("open dev error\n");
		return -1;
	}

    // permission authentication
    drm_magic_t magic;
	int ret = -1;
	ret = drmGetMagic(mPoll_fd.fd, &magic);
	if(ret<0){
		ILOGE("drmGetMagic ret err code =%d\n",ret);
        return -1;
	}
	ret = drmAuthMagic(mPoll_fd.fd, magic);
	if(ret<0){
		ILOGE("drmAuthMagic ret err code =%d\n",ret);
        return -1;
	}
	
	//get resource
	res = drmModeGetResources(fd);
	if ((!res)||(!res->count_crtcs)){
		return -1;
	}

	//get target crtc && crtc framebuffer id through target crtc id
	for(i=0;icount_crtcs;i++){
		crtc_id = res->crtcs[i];
		if(crtc_id == TARGET_CRTC_ID){
			crtc = drmModeGetCrtc(fd, crtc_id);
			if (!crtc) {
                continue;
            }
            if (crtc->buffer_id == 0) {
                drmModeFreeCrtc(crtc);
            }else{
				fb_id = crtc->buffer_id;
			}
			break;
		}
	}
	printf("crtc id = %d,framebuffer id = %d\n",crtc_id,fb_id);

	//get target connector through connector id  crtc_id
	conn = drmModeGetConnector(fd, conn_id);	

	drmModeSetCrtc(fd, crtc_id, fb_id,0, 0, &conn_id, 1, &conn->modes[0]);
	drmModePageFlip(fd, crtc_id, fb_id,DRM_MODE_PAGE_FLIP_EVENT, &crtc_id);

	while (true) {
		drmHandleEvent(fd, &ev);
	}

	drmModeFreeConnector(conn);
	drmModeFreeResources(res);
	close(fd);
	return 0;
}

这里首先 我们要根据自己的需求 确定出来我们需要的是哪块屏幕,这里一般使用xrandr命令去看,这时候主要是确定对应的接口类型是 HDMI、DP还是DSI接口。同时也确定对应的数字,比如,执行xrandr后显示如下:

Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
HDMI-1 disconnected (normal left inverted right x axis y axis)
DP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 527mm x 296mm
   1920x1080     60.00 +  60.00*   50.00    59.94    24.00    23.98  
   1920x1080i    60.00    50.00    59.94  
   1600x1200     60.00  
   1280x1024     75.02    60.02  
   1152x864      75.00  
   1280x720      60.00    50.00    59.94  
   1024x768      75.03    60.00  
   800x600       75.00    60.32  
   720x576       50.00  
   720x480       60.00    59.94  
   640x480       75.00    60.00    59.94  
   720x400       70.08  
HDMI-2 disconnected (normal left inverted right x axis y axis)
DP-2 disconnected (normal left inverted right x axis y axis)
HDMI-3 disconnected (normal left inverted right x axis y axis)
DP-3 disconnected (normal left inverted right x axis y axis)

那我们就知道 DP-1是我们目前连接的屏幕。

其次就是要拿crtc id 和connect id,这时需要通过一个modetest的程序(drm的测试程序)来获取,主要是通过modetest命令 结合 xrandr拿到 比如DP-1的名字 拿到对应的crtc id 和 connector id,之后将其定义为我们的目标id,即:TARGET_CRTC_ID 和 TARGET_CONN_ID。同时我们要通过TARGET_CRTC_ID拿到对应的framebuffer id。

最后就可以根据以上的这些信息 来确定拿到哪个具体设备的vsync了。

你可能感兴趣的:(Linux,系统,linux,drm,显示)