在Linux上开发HDMI端口

8 投票
1 回答
10613 浏览
提问于 2025-04-17 14:53

如何才能让一个应用程序独占地控制HDMI输出,而不让操作系统自动配置它用于显示输出呢?

举个例子,使用标准的DVI/VGA作为主显示器,但把Mplayer的视频输出发送到HDMI上,使用一个设备文件。

这个问题在网上很难找到答案。几乎所有的结果都与HDMI的音频功能有关。

(这里进行了编辑)

下面有个评论提到可以使用不同的Xorg服务器。虽然这是个不错的主意,但它没有回答我问的一个问题,以及我暗示的一个问题:

1) 如果在其他显示器之前加载了这个显示器,或者当只有SSH登录时,它是唯一的显示器,我该如何阻止Linux把控制台放到这个显示器上?

2) 如果没有X怎么办?我想直接把图形输出到适配器上。我能否通过代码使用标准功能来做到这一点,而不直接与驱动程序交互(可能有点过时,但可以使用SVGALib或其他非X的图形层)?

(这里进行了编辑)

我查看了SVGALib(这个比较旧)和SDL。后者在X内外都能工作,甚至可以访问OpenGL。我在一个论坛链接上找到了1.3版本,但网站和FTP似乎只提供到1.2版本。总体来说,SDL是个很好的解决方案,但它有以下两个具体缺点:

1) 通用的创建设备调用接受一个设备索引,但完全忽略它:

(src/video/bwindow/SDL_bvideo.cc)
BE_CreateDevice(int devindex)

特定驱动程序的调用似乎也有同样的问题。例如,DirectFB(我假设它在控制台下提供图形):

(src/video/directfb/SDL_DirectFB_video.c)
DirectFB_CreateDevice(int devindex)

这两个函数的实现似乎都没有设置设备索引的地方……这无疑是因为它们所基于的标准接口缺乏支持。

2) 在被选中的适配器上,SDL似乎会自动把所有显示器连接在一起。示例“testsprite2.c”(随库提供)接受一个“--display”参数,这个参数在“common.c”(所有示例的通用功能)中处理。你可以看到它对“--display”参数的处理只是计算那个屏幕在一个大画布中的X/Y坐标:

if (SDL_strcasecmp(argv[index], "--display") == 0) {
    ++index;
    if (!argv[index]) {
        return -1;
    }
    state->display = SDL_atoi(argv[index]);
    if (SDL_WINDOWPOS_ISUNDEFINED(state->window_x)) {
        state->window_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(state->display);
        state->window_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(state->display);
    }
    if (SDL_WINDOWPOS_ISCENTERED(state->window_x)) {
        state->window_x = SDL_WINDOWPOS_CENTERED_DISPLAY(state->display);
        state->window_y = SDL_WINDOWPOS_CENTERED_DISPLAY(state->display);
    }
    return 2;
}

所以,如果它们在同一个适配器上,就没有办法把一个显示器和另一个显示器隔离开。SDL是行不通的。

除非有一个与SDL相当的解决方案,或者发现设置特定设备(devindex)在合适位置是很简单的(这可能不太可能,因此很可能是它没有实现的原因),否则,似乎为了独占和完全专用地使用屏幕,最好的选择是在分配给第二个设备的单独Xorg实例下编写自己的窗口管理器。

1 个回答

6

你可以直接写入帧缓冲设备 /dev/fb(假设你的控制台默认使用这个设备)。如果你想让控制台不显示在上面,只需禁用所有虚拟终端(这样你只能通过远程登录)。如果你有多个适配器,应该会有不止一个帧缓冲设备(这需要确认一下)。

这里有一个C语言的例子,它在帧缓冲上绘制一个矩形:

通过Linux帧缓冲绘制像素到屏幕

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

int main()
{
    int fbfd = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    long int screensize = 0;
    char *fbp = 0;
    int x = 0, y = 0;
    long int location = 0;

    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd == -1) {
        perror("Error: cannot open framebuffer device");
        exit(1);
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) {
        perror("Error reading fixed information");
        exit(2);
    }

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
        perror("Error reading variable information");
        exit(3);
    }

    printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);

    // Figure out the size of the screen in bytes
    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

    // Map the device to memory
    fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if ((int)fbp == -1) {
        perror("Error: failed to map framebuffer device to memory");
        exit(4);
    }
    printf("The framebuffer device was mapped to memory successfully.\n");

    x = 100; y = 100;       // Where we are going to put the pixel

    // Figure out where in memory to put the pixel
    for (y = 100; y < 300; y++)
        for (x = 100; x < 300; x++) {

            location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
                       (y+vinfo.yoffset) * finfo.line_length;

            if (vinfo.bits_per_pixel == 32) {
                *(fbp + location) = 100;        // Some blue
                *(fbp + location + 1) = 15+(x-100)/2;     // A little green
                *(fbp + location + 2) = 200-(y-100)/5;    // A lot of red
                *(fbp + location + 3) = 0;      // No transparency
        //location += 4;
            } else  { //assume 16bpp
                int b = 10;
                int g = (x-100)/6;     // A little green
                int r = 31-(y-100)/16;    // A lot of red
                unsigned short int t = r<<11 | g << 5 | b;
                *((unsigned short int*)(fbp + location)) = t;
            }

        }
    munmap(fbp, screensize);
    close(fbfd);
    return 0;
}

只要你有构建工具和系统的头文件,它就应该能编译。为了增加点乐趣,可以通过SSH运行它,看看它是如何在你没有登录的物理屏幕上绘制的。

需要注意的是,有很多工具可以在帧缓冲上工作,除了X11之外,但它们不会直接访问帧缓冲。相反,它们通过一个叫做DirectFB的额外抽象层来工作。DirectFB允许相同的应用程序在X11内外运行……包括MPlayer、GStreamer,以及任何使用SDL(它调用DirectFB)的应用程序,还有一个轻量级、流行的伪X11容器叫做XDirectFB(我认为它可以运行X11应用,但不会像典型的窗口管理器那样繁重)。

撰写回答