iMX8MP HDMI图像输出 & V4L2生成MJPEG流

发布时间 2023-08-10 14:55:46作者: 阿风小子

飞凌嵌入式OKMX8MP-C开发板基于NXP i.MX 8M Plus处理器开发设计,该系列处理器专注于机器学习与视觉、高级多媒体以及具有高可靠性的工业自动化。旨在满足智慧城市工业互联网、智能医疗智慧交通等应用的需求。强大的四核或双核Arm® Cortex®-A53处理器,主频高达1.6GHz,带有神经处理单元(NPU),最高运行速率可达2.3TOPS。本文采用的硬件板卡为飞凌嵌入式OKMX8MP-C开发板,系统版本Linux5.4.70+Qt5.15.0,主要介绍iMX8MP HDMI输出编程方法以及V4L2相机生成MJPEG流画面。

 

---------------------------------

 

飞凌嵌入式OKMX8MP-C开发板默认将三种显示接口LVDS/MIPI/HDMI全部打开,要对HDMI输出编程的话需要将MIPI显示关闭,关闭方式是进入uboot菜单的display选项进行操作:

 

iMX8MP 官方手册截图

 

iMX8MP上电之后,在串口终端uboot启动时狂按空格键,即可进入uboot菜单:

 

imx8mp uboot菜单

 

uboot菜单中的Display select选项就是设置iMX8MP输出接口的,可以分别对这三种接口进行设置,默认情况下三个输出接口都打开,而如果要进行FrameBuffer编程的话,经过实际测试,需要将LVDS和HDMI选项都打开,HDMI接口才能正常输出/dev/fb0外设的映射图像:

 

imx8mp Display select选项 1

 

如果将iMX8MP 的 LVDS接口关闭的话,HDMI接口输出不正常,/dev/fb0也不会生成,也就无法对

FrameBuffer进行编程:

 

FrameBuffer进行编程

 

imx8mp

 

 

在飞凌厂商提供的iMX8MP文档中,已经写明了iMX8MP HDMI接口输出的分辨率上限为1280*800*32,这个是由HDMI输出芯片决定的,毕竟IMX8的定位是工业控制而不是多媒体应用,对/dev/fb0外设进行ioctl,输出的分辨率也是1280*800,32位色彩:

 

使用命令

 

x11vnc -rawfb /dev/fb0 -clip 1280*800

 

可搭建x11vnc服务器,将FrameBuffer画面输出至vnc软件客户端,也就可以在不使用HDMI输出设备的FrameBuffer分辨率只有1280*800,意味着,V4L2相机生成的流画面,分辨率高于或等于这个数无法生成显示,甚至在实测中,低于这个数一点点也无法生成(1000*750),会提示段错误终止进程,因此我经过反复调试,最终将V4L2生成的流分辨率设置为900*675,无法对相机物尽其用(相机最大分辨率为1080P),但这是板子的Framebuffer最大支持的输出分辨率,无法再设置更大了。

 

#define  IMAGEWIDTH    900
#define  IMAGEHEIGHT   675

 

这次的V4L2推流,我不采用之前已经熟练掌握的V4L2生成YUYV流,而是直接生成MJPEG流,生成此流有2个好处,一是生成MJPEG流无需经过YUYV转RGB的步骤,帧生成时间更短,相比起之前生成YUYV流的方式,流畅度有非常明显的提升;二是生成MJPEG流可直接保存为JPEG文件,在确保IO读取锁无冲突的前提下,可供外部程序进行访问。要使用V4L2驱动库生成MJPEG流,初始化步骤要写对:

 

    struct v4l2_format fmt;

    fmt.type = V4L2_CAP_VIDEO_CAPTURE;
    fmt.fmt.pix.width = IMAGEWIDTH;
    fmt.fmt.pix.height = IMAGEHEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    IF (ioctl (fd_video, VIDIOC_S_FMT, &fmt) == -1)
    {
        perror("ERROR camera VIDIOC_S_FMT FaiLED.");
        return -1;
    }

 

检查写入参数是否正确:

 

    if (ioctl (fd_video, VIDIOC_G_FMT, &fmt) == -1)
    {
        perror("ERROR camera VIDIOC_G_FMT Failed.");
        return -1;
    }

 

使用mmap()进行物理内存地址到用户内存地址的映射,即使用一个用户定义缓存来读取物理内存中的摄像头缓存数据:

 

    struct v4l2_buffer v4l2_buf;
    v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buf.memory = V4L2_MEMORY_MMAP;
    for(i = 0; i < 4; i++)
    {
        v4l2_buf.index = i;
        if(ioctl(fd_video, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
        {
            perror("Unable to query buffer.");
            return -1;
        }

        pic.tmpbuffer[i] = (unsigned char*)mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, fd_video, v4l2_buf.m.offset);
        if(pic.tmpbuffer[i] == MAP_FAILED)
        {
             perror("Unable to map buffer.");
             return -1;
        }
        if(ioctl(fd_video, VIDIOC_QBUF, &v4l2_buf) < 0)
        {
            perror("Unable to queue buffer.");
            return -1;
        }
    }

 

开启捕捉:

 

    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(ioctl(fd_video , VIDIOC_STREAMON , &type) < 0)
    {
        perror("Unable to start capture.");
        return -1;
    }

 

捕捉并生成JPEG文件,循环执行的函数:


<div><div><span>int</span><span> </span><span>V4l2_Grab_Mjpeg</span><span>(</span><span>char</span><span> </span><span>*</span><span> </span><span>filename</span><span>)</span>
</div></div>{
    struct v4l2_buffer buff;
    buff.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buff.memory = V4L2_MEMORY_MMAP;
    if(ioctl(fd_video , VIDIOC_DQBUF, &buff) < 0)
    {
        printf("camera VIDIOC_DQBUF Failed.\n");
        usleep(1000*1000);
        return -1;
    }

    pic.tmpbytesused[buff.index] = buff.bytesused;
    printf("size : %d\n",pic.tmpbytesused[buff.index]);

    int jpg_fd = open("1.jpeg" , O_RDWR | O_CREAT , 00700);
    if(jpg_fd == -1)
    {
        printf("open ipg Failed!\n ");
        return -1;
    }
    int writesize = write(jpg_fd , pic.tmpbuffer[buff.index] , pic.tmpbytesused[buff.index]);
    printf("Write successfully size : %d\n" , writesize);
    close(jpg_fd);

    //10、Queue the buffers.
    if(ioctl(fd_video , VIDIOC_QBUF, &buff) < 0)
    {
        printf("camera VIDIOC_QBUF Failed.");
        usleep(1000*1000);
        return -1;
    }
    return 0;
}

 

主循环运行:

 

    while(1)
    {
        V4l2_Grab_Mjpeg(MJPEG_FILE_NAME);
        LCD_RGB888_Show_JPG_File(FB_DEV, 0 , 0 , MJPEG_FILE_NAME);
    }

 

运行效果:

 

imx8mp 运行结果