DRM遇到的实际问题及领悟(1)

一、问题背景

在学习研究libdrm和DRM的时候,为了了解和研究KWin中Compositor::composite核心中的图像帧绘制机制,在最终调用到的DrmFramebuffer::createFramebuffer函数中加入了以下代码:

std::shared_ptr<DrmFramebuffer> DrmFramebuffer::createFramebuffer1(const std::shared_ptr<DrmGpuBuffer> &buffer, const std::shared_ptr<DbmBuffer> &gbmbuffer)
{
    const auto size = buffer->size();
    const auto handles = buffer->handles();
    const auto strides = buffer->strides();
    const auto offsets = buffer->offsets();
    struct gbm_bo *bobo = gbmbuffer->bo(); //phph1

    uint32_t framebufferId = 0;
    int ret;
    if (buffer->gpu()->addFB2ModifiersSupported() && buffer->modifier() != DRM_FORMAT_MOD_INVALID) {
        //qCCritical(KWIN_DRM, "daozhelile vvvv111...\n\n"); //phph
        uint64_t modifier[4];
        for (uint32_t i = 0; i < 4; i++) {
            modifier[i] = i < buffer->planeCount() ? buffer->modifier() : 0;
        }
        //phph add begin
#if 1
        static bool one_time = false;
        if (one_time == false)
        {
            one_time = true;

            //uint8_t *framebuffer_data = mmap(NULL, framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, framebuffer->handle);
            uint32_t framebuffer_size = size.width() * size.height();
            uint8_t *framebuffer_data = (uint8_t *)mmap(NULL, framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, buffer->gpu()->fd(), handles.data());
            //uint8_t *framebuffer_data = (uint8_t *)mmap(NULL, framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, buffer->gpu()->fd(), gbm_bo_get_handle(gbmbuffer.bo()).u32);

            //for (uint32_t i = 0; i < framebuffer_size; i++)
                //framebuffer_data[i] = ~framebuffer_data[i];
            
            FILE *output_file = fopen("/home/penghao/framebuffer.bin", "wb");
            if ((void *)framebuffer_data == MAP_FAILED)
            {
                qCCritical(KWIN_DRM, "daozhelilephph Unable to map framebuffer.\n\n");
            }
            fwrite(framebuffer_data, sizeof(uint8_t), framebuffer_size, output_file);
            fclose(output_file);

            munmap(framebuffer_data, framebuffer_size);
        }
#endif
     	//phph add end

        ret = drmModeAddFB2WithModifiers(buffer->gpu()->fd(), size.width(), size.height(), buffer->format(), handles.data(), strides.data(), offsets.data(), modifier, &framebufferId, DRM_MODE_FB_MODIFIERS);
    } else {
        //qCCritical(KWIN_DRM, "meidaozheli vvvv222...\n\n"); //phph
        ret = drmModeAddFB2(buffer->gpu()->fd(), size.width(), size.height(), buffer->format(), handles.data(), strides.data(), offsets.data(), &framebufferId, 0);
        if (ret == EOPNOTSUPP && handles.size() == 1) {
            //qCCritical(KWIN_DRM, "meidaozheli vvvv333...\n\n"); //phph
            ret = drmModeAddFB(buffer->gpu()->fd(), size.width(), size.height(), 24, 32, strides[0], handles[0], &framebufferId);
        }
    }
    if (ret == 0) {
        return std::make_shared<DrmFramebuffer>(buffer, framebufferId);
    } else {
        return nullptr;
    }
}
}

所加入代码段的意义是将电脑屏幕实时的一帧图像截取出来(当然只截取一次)。

但是实际在运行的时候,发现并为实现所预想的功能,跟进之后发现是mmap这一步出现了错误。

二、初始定位

为了简化问题,在网上查找一些功能相近的例程,脱离开KWin,只调用DRM接口函数进行截屏功能的实现,这样比较好调试和定位。还真让笔者给找到了。

1. 源码准备

以下代码来自在linux平台下,通过drm框架获取一帧帧缓冲数据,并存储到文件。给出示例代码

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#define FRAMEBUFFER_FILE "/dev/dri/card0"
#define FRAMEBUFFER_WIDTH 1920
#define FRAMEBUFFER_HEIGHT 1080
int main(void) {
    int framebuffer_fd = open(FRAMEBUFFER_FILE, O_RDWR);
    if (framebuffer_fd < 0) {
        fprintf(stderr, "Unable to open framebuffer\n");
        return EXIT_FAILURE;
    }
    drmModeRes *resources = drmModeGetResources(framebuffer_fd);
    drmModeConnector *connector = NULL;
    drmModeEncoder *encoder = NULL;
    for (int i = 0; i < resources->count_connectors; ++i) {
        connector = drmModeGetConnector(framebuffer_fd, resources->connectors[i]);
        if (connector == NULL || connector->connection != DRM_MODE_CONNECTED) {
            continue;
        }
        for (int j = 0; j < resources->count_encoders; ++j) {
            encoder = drmModeGetEncoder(framebuffer_fd, resources->encoders[j]);
            if (encoder == NULL || encoder->encoder_id != connector->encoder_id) {
                continue;
            }
            break;
        }
        break;
    }
    drmModeCrtc *crtc = drmModeGetCrtc(framebuffer_fd, encoder->crtc_id);
    uint32_t framebuffer_id = crtc->buffer_id;
    drmModeFB *framebuffer = drmModeGetFB(framebuffer_fd, framebuffer_id);
    uint32_t framebuffer_size = framebuffer->pitch * framebuffer->height;
    uint8_t *framebuffer_data = mmap(NULL, framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, framebuffer->handle);
    if ((void *)framebuffer_data == MAP_FAILED) {
        fprintf(stderr, "Unable to map framebuffer\n");
        return EXIT_FAILURE;
    }
    FILE *output_file = fopen("framebuffer.bin", "wb");
    if (output_file == NULL) {
        fprintf(stderr, "Unable to open output file\n");
        return EXIT_FAILURE;
    }
    fwrite(framebuffer_data, sizeof(uint8_t), framebuffer_size, output_file);
    fclose(output_file);
    munmap(framebuffer_data, framebuffer_size);
    drmModeFreeFB(framebuffer);
    drmModeFreeCrtc(crtc);
    drmModeFreeEncoder(encoder);
    drmModeFreeConnector(connector);
    drmModeFreeResources(resources);
    close(framebuffer_fd);
    return EXIT_SUCCESS;
}

该示例代码实现的功能是:使用DRM框架获取当前显示器的帧缓冲数据,并将其存储到名为”framebuffer.bin”的二进制文件中。

笔者稍作调整,修正了格式并且添加了更多打印信息,修改后的代码如下:

/*
 * 在linux平台下,通过drm框架获取一帧帧缓冲数据,并存储到文件
 *
*/

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include <xf86drm.h>
#include <xf86drmMode.h>


#define FRAMEBUFFER_FILE "/dev/dri/card0"
#define FRAMEBUFFER_WIDTH 1920
#define FRAMEBUFFER_HEIGHT 1080


int main(int argc, char **argv)
{
    int framebuffer_fd;
	drmModeConnector *connector = NULL;
	drmModeRes *resources = NULL;
    drmModeEncoder *encoder = NULL;
    drmModeCrtc *crtc = NULL;
	uint32_t conn_id;
	uint32_t crtc_id;

    framebuffer_fd = open(FRAMEBUFFER_FILE, O_RDWR);
    if (framebuffer_fd < 0)
    {
        fprintf(stderr, "Unable to open framebuffer!\n");
        return EXIT_FAILURE;
    }

    resources = drmModeGetResources(framebuffer_fd); //获取drmModeRes资源,包含fb、crtc、encoder、connector等
    for (int i = 0; i < resources->count_connectors; ++i)
    {
        connector = drmModeGetConnector(framebuffer_fd, resources->connectors[i]); //根据connector_id获取connector资源
        if (connector == NULL || connector->connection != DRM_MODE_CONNECTED)
            continue;
        for (int j = 0; j < resources->count_encoders; ++j)
        {
            encoder = drmModeGetEncoder(framebuffer_fd, resources->encoders[j]);
            if (encoder == NULL || encoder->encoder_id != connector->encoder_id)
                continue;
            break;
        }
        break;
    }

    crtc = drmModeGetCrtc(framebuffer_fd, encoder->crtc_id);
    uint32_t framebuffer_id = crtc->buffer_id;

    //drmModeFB *framebuffer = drmModeGetFB(framebuffer_fd, framebuffer_id);
    drmModeFBPtr framebuffer = drmModeGetFB(framebuffer_fd, framebuffer_id);
    //phph add begin
    if (framebuffer == NULL)
    {
        printf("daozhelilema?\n");
    }
    //phph add end
    uint32_t framebuffer_size = framebuffer->pitch * framebuffer->height;
    //uint32_t framebuffer_size = connector->modes[0].hdisplay * connector->modes[0].vdisplay;
    printf("connector->modes[0].hdisplay: %d, connector->modes[0].vdisplay: %d\n", connector->modes[0].hdisplay, connector->modes[0].vdisplay); //phph add
    printf("framebuffer->pitch: %d, framebuffer->height: %d\n", framebuffer->pitch, framebuffer->height); //phphadd

    uint8_t *framebuffer_data = mmap(NULL, framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, framebuffer->handle);
    if ((void *)framebuffer_data == MAP_FAILED)
    {
        fprintf(stderr, "Unable to map framebuffer!\n");
        fprintf(stderr, "Reason is: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    FILE *output_file = fopen("framebuffer.bin", "wb");
    if (output_file == NULL)
    {
        fprintf(stderr, "Unable to open output file\n");
        return EXIT_FAILURE;
    }
    fwrite(framebuffer_data, sizeof(uint8_t), framebuffer_size, output_file);
    fclose(output_file);

    munmap(framebuffer_data, framebuffer_size);
    drmModeFreeFB(framebuffer);
    drmModeFreeCrtc(crtc);
    drmModeFreeEncoder(encoder);
    drmModeFreeConnector(connector);
    drmModeFreeResources(resources);
    close(framebuffer_fd);

    return EXIT_SUCCESS;
}

2. 源码编译

通过以下命令进行源码编译:

gcc framebuffer_catch.c -o screen_catch -I/usr/include/drm -ldrm

最终生成可执行文件screen_catch。

3. 源码执行及结果

执行此可执行文件,得到以下结果:

$ ./screen_catch 
connector->modes[0].hdisplay: 1920, connector->modes[0].vdisplay: 1080
framebuffer->pitch: 7680, framebuffer->height: 1080
Unable to map framebuffer!
Reason is: Invalid argument.

怀疑是权限问题,于是加上sudo,命令及结果如下:

$ sudo ./screen_catch 
connector->modes[0].hdisplay: 1920, connector->modes[0].vdisplay: 1080
framebuffer->pitch: 7680, framebuffer->height: 1080
Unable to map framebuffer!
Reason is: Invalid argument.

问题还是一样,说明并不是权限问题。

进一步,笔者将以上代码放到安装Ubuntu(22.04)系统的电脑上,也是同样的问题,说明与系统无关。

综上说明,例程也存在与笔者在KWin中所遇的相同问题。既然能够将例程放到网上,一般来说应该不会存在这么大问题,可能是针对于不同显卡或者不同代码版本,有所不同。笔者相信,原作者在他的电脑上是能够正常运行程序的。但不管怎么说,问题确实是存在的,mmap一直失败。

下一步该如何走,如何更加深入地定位问题?请看下回。

猜你喜欢

转载自blog.csdn.net/phmatthaus/article/details/133319998
今日推荐