一、问题背景
在学习研究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一直失败。
下一步该如何走,如何更加深入地定位问题?请看下回。