ARM Linux DIY(十四)摄像头捕获画面显示到屏幕

前言

前期已经调试好了摄像头和屏幕,今天我们将摄像头捕获的画面显示到屏幕上。

原理

摄像头对应 /dev/video0,屏幕对应 /dev/fb0,所以我们只要写一个应用程序,读取 video0 写入到 fb0 就可以了。

应用程序代码实例

camera_display.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <math.h>
#include <wchar.h>
#include <time.h>
#include <stdbool.h>#define CAM_WIDTH 800
#define CAM_HEIGHT 600
#define YUVToRGB(Y) ((u16)((((u8)(Y) >> 3) << 11) | (((u8)(Y) >> 2) << 5) | ((u8)(Y) >> 3)))static char *dev_video;
static char *dev_fb0;static char *yuv_buffer;
static char *rgb_buffer;
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
struct v4l2_buffer video_buffer;
int lcd_fd;
int video_fd;
unsigned char *lcd_mem_p = NULL; //保存LCD屏映射到进程空间的首地址
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
char *video_buff_buff[4]; /*保存摄像头缓冲区的地址*/
int video_height = 0;
int video_width = 0;
unsigned char *lcd_display_buff; //LCD显存空间
unsigned char *lcd_display_buff2; //LCD显存空间static void errno_exit(const char *s)
{fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));exit(EXIT_FAILURE);
}static int video_init(void)
{struct v4l2_capability cap;struct v4l2_fmtdesc dis_fmtdesc;struct v4l2_format video_format;struct v4l2_requestbuffers video_requestbuffers;struct v4l2_buffer video_buffer;ioctl(video_fd, VIDIOC_QUERYCAP, &cap);dis_fmtdesc.index = 0;dis_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;// printf("-----------------------支持格式---------------------\n");// while (ioctl(video_fd, VIDIOC_ENUM_FMT, &dis_fmtdesc) != -1) {// 	printf("\t%d.%s\n", dis_fmtdesc.index + 1, dis_fmtdesc.description);// 	dis_fmtdesc.index++;// }video_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_format.fmt.pix.width = CAM_WIDTH;video_format.fmt.pix.height = CAM_HEIGHT;video_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //使用JPEG格式帧,用于静态图像采集ioctl(video_fd, VIDIOC_S_FMT, &video_format);printf("当前摄像头支持的分辨率:%dx%d\n", video_format.fmt.pix.width, video_format.fmt.pix.height);if (video_format.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) {printf("当前摄像头不支持YUYV格式输出.\n");video_height = video_format.fmt.pix.height;video_width = video_format.fmt.pix.width;//return -3;} else {video_height = video_format.fmt.pix.height;video_width = video_format.fmt.pix.width;printf("当前摄像头支持YUYV格式输出.width %d height %d\n", video_height, video_height);}/*3. 申请缓冲区*/memset(&video_requestbuffers, 0, sizeof(struct v4l2_requestbuffers));video_requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_requestbuffers.count = 4;video_requestbuffers.memory = V4L2_MEMORY_MMAP;if (ioctl(video_fd, VIDIOC_REQBUFS, &video_requestbuffers))return -4;printf("成功申请的缓冲区数量:%d\n", video_requestbuffers.count);/*4. 得到每个缓冲区的地址: 将申请的缓冲区映射到进程空间*/memset(&video_buffer, 0, sizeof(struct v4l2_buffer));int i;for (i = 0; i < video_requestbuffers.count; i++) {video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_buffer.index = i;video_buffer.memory = V4L2_MEMORY_MMAP;if (ioctl(video_fd, VIDIOC_QUERYBUF, &video_buffer))return -5;/*映射缓冲区的地址到进程空间*/video_buff_buff[i] =mmap(NULL, video_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, video_fd, video_buffer.m.offset);printf("第%d个缓冲区地址:%#X\n", i, video_buff_buff[i]);}/*5. 将缓冲区放入到采集队列*/memset(&video_buffer, 0, sizeof(struct v4l2_buffer));for (i = 0; i < video_requestbuffers.count; i++) {video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_buffer.index = i;video_buffer.memory = V4L2_MEMORY_MMAP;if (ioctl(video_fd, VIDIOC_QBUF, &video_buffer)) {printf("VIDIOC_QBUF error\n");return -6;}}/*6. 启动摄像头采集*/printf("启动摄像头采集\n");int opt_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(video_fd, VIDIOC_STREAMON, &opt_type)) {printf("VIDIOC_STREAMON error\n");return -7;}return 0;
}int lcd_init(void)
{/*2. 获取可变参数*/if (ioctl(lcd_fd, FBIOGET_VSCREENINFO, &vinfo))return -2;printf("屏幕X:%d   屏幕Y:%d  像素位数:%d\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);//分配显存空间,完成图像显示lcd_display_buff = malloc(vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);/*3. 获取固定参数*/if (ioctl(lcd_fd, FBIOGET_FSCREENINFO, &finfo))return -3;finfo.smem_len = 115200;finfo.line_length = 480;printf("smem_len=%d Byte,line_length=%d Byte\n", finfo.smem_len, finfo.line_length);/*4. 映射LCD屏物理地址到进程空间*/lcd_mem_p =(unsigned char *)mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0); //从文件的那个地方开始映射memset(lcd_mem_p, 0xFFFFFFFF, finfo.smem_len);printf("映射LCD屏物理地址到进程空间\n");return 0;
}static void close_device(void)
{if (-1 == close(video_fd))errno_exit("close");video_fd = -1;if (-1 == close(lcd_fd))errno_exit("close");lcd_fd = -1;
}static void open_device(void)
{video_fd = open(dev_video, O_RDWR /* required */ | O_NONBLOCK, 0);if (-1 == video_fd) {fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_video, errno, strerror(errno));exit(EXIT_FAILURE);}lcd_fd = open(dev_fb0, O_RDWR, 0);if (-1 == lcd_fd) {fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_fb0, errno, strerror(errno));exit(EXIT_FAILURE);}
}/* 将YUV格式数据转为RGB */
void yuv_to_rgb(unsigned char *yuv_buffer, unsigned char *rgb_buffer, int iWidth, int iHeight)
{int x;int z = 0;unsigned char *ptr = rgb_buffer;unsigned char *yuyv = yuv_buffer;int r, g, b;int y, u, v;for (x = 0; x < iWidth * iHeight; x++) {if (!z)y = yuyv[0] << 8;elsey = yuyv[2] << 8;u = yuyv[1] - 128;v = yuyv[3] - 128;r = (y + (359 * v)) >> 8;g = (y - (88 * u) - (183 * v)) >> 8;b = (y + (454 * u)) >> 8;*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);if (z++) {z = 0;yuyv += 4;}}
}void rgb24_to_rgb565(char *rgb24, char *rgb16)
{int i = 0, j = 0;for (i = 0; i < 240 * 240 * 3; i += 3) {rgb16[j] = rgb24[i] >> 3; // Brgb16[j] |= ((rgb24[i + 1] & 0x1C) << 3); // Grgb16[j + 1] = rgb24[i + 2] & 0xF8; // Rrgb16[j + 1] |= (rgb24[i + 1] >> 5); // Gj += 2;}
}int main(int argc, char **argv)
{struct pollfd video_fds;dev_video = "/dev/video0";dev_fb0 = "/dev/fb0";open_device();video_init();lcd_init();/* 读取摄像头的数据*/video_fds.events = POLLIN;video_fds.fd = video_fd;memset(&video_buffer, 0, sizeof(struct v4l2_buffer));rgb_buffer = malloc(CAM_WIDTH * CAM_HEIGHT * 3);yuv_buffer = malloc(CAM_WIDTH * CAM_HEIGHT * 3);while (1) {/*等待摄像头采集数据*/poll(&video_fds, 1, -1);/*得到缓冲区的编号*/video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;video_buffer.memory = V4L2_MEMORY_MMAP;ioctl(video_fd, VIDIOC_DQBUF, &video_buffer);printf("当前采集OK的缓冲区编号:%d,地址:%#X num:%d\n", video_buffer.index, video_buff_buff[video_buffer.index],strlen(video_buff_buff[video_buffer.index]));/*对缓冲区数据进行处理*/yuv_to_rgb(video_buff_buff[video_buffer.index], yuv_buffer, video_height, video_width);rgb24_to_rgb565(yuv_buffer, rgb_buffer);printf("显示屏进行显示\n");//显示屏进行显示: 将显存空间的数据拷贝到LCD屏进行显示memcpy(lcd_mem_p, rgb_buffer, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);/*将缓冲区放入采集队列*/ioctl(video_fd, VIDIOC_QBUF, &video_buffer);printf("将缓冲区放入采集队列\n");}/* 关闭设备*/close_device();return 0;
}

调试

# ./camera_display2.out 
当前摄像头支持的分辨率:800x600
当前摄像头支持YUYV格式输出.width 600 height 600
[   52.859327] sun6i-csi 1cb4000.csi: Unsupported pixformat: 0x56595559 with mbus code: 0x2006!
成功申请的缓冲区数量:4
第0个缓冲区地址:0XB6D6A000
第1个缓冲区地址:0XB6C7F000
第2个缓冲区地址:0XB6B94000
第3个缓冲区地址:0XB6AA9000
启动摄像头采集
VIDIOC_STREAMON error
屏幕X:240   屏幕Y:240  像素位数:16
smem_len=115200 Byte,line_length=480 Byte
映射LCD屏物理地址到进程空间
当前采集OK的缓冲区编号:0,地址:0XB6D6A000 num:0
显示屏进行显示
将缓冲区放入采集队列
当前采集OK的缓冲区编号:0,地址:0XB6D6A000 num:0

运行报错 sun6i-csi 1cb4000.csi: Unsupported pixformat: 0x56595559 with mbus code: 0x2006!
并且屏幕显示一片绿
在这里插入图片描述

猜测 1:会不会是设备树配置不对
仔细检查设备树参数,还真发现了一处错误

&i2c1 {pinctrl-0 = <&i2c1_pins>;pinctrl-names = "default";clock-frequency = <400000>;status = "okay";ov2640: camera@30 {compatible = "ovti,ov2640";reg = <0x30>;pinctrl-names = "default";pinctrl-0 = <&csi1_mclk_pin>;clocks = <&ccu CLK_CSI1_MCLK>;clock-names = "xvclk";assigned-clocks = <&ccu CLK_CSI1_MCLK>;assigned-clock-rates = <26000000>;port {ov2640_0: endpoint {remote-endpoint = <&csi1_ep>;bus-width = <10>;};};};
};

时钟频率 26000000 是我之前使用 26MHz 晶振时改的,后来由于 USB 问题,晶振换成了 24MHz 的,这里没有同步修改,那就改成 24000000
运行,还是报同样的错误。
猜测 2:是不是应用程序中摄像头分辨率设置的是 800x600,而屏幕是 240x240 导致的
应用程序摄像头分辨率改成 240x240

#define CAM_WIDTH 240
#define CAM_HEIGHT 240

运行,结果还是报同样的错误

方案 3:上网搜索
并没有找到类似问题。

方案 4:看代码
没办法只能看代码了
根据内核报错信息 Unsupported pixformat 找到
drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c

static int sun6i_video_link_validate(struct media_link *link)
{struct video_device *vdev = container_of(link->sink->entity,struct video_device, entity);struct sun6i_video *video = video_get_drvdata(vdev);struct v4l2_subdev_format source_fmt;int ret;video->mbus_code = 0;if (!media_entity_remote_pad(link->sink->entity->pads)) {dev_info(video->csi->dev,"video node %s pad not connected\n", vdev->name);return -ENOLINK;}ret = sun6i_video_link_validate_get_format(link->source, &source_fmt);if (ret < 0)return ret;if (!sun6i_csi_is_format_supported(video->csi,video->fmt.fmt.pix.pixelformat,source_fmt.format.code)) {dev_err(video->csi->dev,"Unsupported pixformat: 0x%x with mbus code: 0x%x!\n",video->fmt.fmt.pix.pixelformat,source_fmt.format.code);return -EPIPE;}if (source_fmt.format.width != video->fmt.fmt.pix.width ||source_fmt.format.height != video->fmt.fmt.pix.height) {dev_err(video->csi->dev,"Wrong width or height %ux%u (%ux%u expected)\n",video->fmt.fmt.pix.width, video->fmt.fmt.pix.height,source_fmt.format.width, source_fmt.format.height);return -EPIPE;}video->mbus_code = source_fmt.format.code;return 0;
}

在判断 sun6i_csi_is_format_supported() 处出问题了,追了下代码,发现最后是个宏函数就不想追了,索性将这段注释掉,
运行,

# ./camera_display2.out 
当前摄像头支持的分辨率:240x240[   64.538654] sun6i-csi 1cb4000.csi: Wrong width or height 240x240 (800x600 expected)

结果又报 Wrong width or height 240x240 (800x600 expected) 错误,和上面一样,注释掉先让代码跑通,
运行,不报错了,屏幕也开始显示图像了
在这里插入图片描述
但是这图像明显不对啊,不过至少有进步了
那就继续追代码,先将那两处判断恢复。
跟着代码,一路追到获取摄像头参数的地方
drivers/media/i2c/ov2640.c

static int ov2640_get_fmt(struct v4l2_subdev *sd,struct v4l2_subdev_pad_config *cfg,struct v4l2_subdev_format *format)
{struct v4l2_mbus_framefmt *mf = &format->format;struct i2c_client  *client = v4l2_get_subdevdata(sd);struct ov2640_priv *priv = to_ov2640(client);if (format->pad)return -EINVAL;if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_APImf = v4l2_subdev_get_try_format(sd, cfg, 0);format->format = *mf;return 0;
#elsereturn -ENOTTY;
#endif}mf->width	= priv->win->width;mf->height	= priv->win->height;mf->code	= priv->cfmt_code; // 这行mf->colorspace	= V4L2_COLORSPACE_SRGB;mf->field	= V4L2_FIELD_NONE;mf->ycbcr_enc	= V4L2_YCBCR_ENC_DEFAULT;mf->quantization = V4L2_QUANTIZATION_DEFAULT;mf->xfer_func	= V4L2_XFER_FUNC_DEFAULT;return 0;
}

重点是 mf->code = priv->cfmt_code; 这行,报错信息 with mbus code: 0x2006 中的 0x2006 应该就是该值,
继续追

/** i2c_driver functions*/
static int ov2640_probe(struct i2c_client *client,const struct i2c_device_id *did)
{struct ov2640_priv	*priv;struct i2c_adapter	*adapter = client->adapter;int			ret;if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {dev_err(&adapter->dev,"OV2640: I2C-Adapter doesn't support SMBUS\n");return -EIO;}priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);if (!priv)return -ENOMEM;if (client->dev.of_node) {priv->clk = devm_clk_get(&client->dev, "xvclk");if (IS_ERR(priv->clk))return PTR_ERR(priv->clk);ret = clk_prepare_enable(priv->clk);if (ret)return ret;}ret = ov2640_probe_dt(client, priv);if (ret)goto err_clk;priv->win = ov2640_select_win(SVGA_WIDTH, SVGA_HEIGHT);priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8; // 这行

cfmt_code 是在这里被赋值的,其中 MEDIA_BUS_FMT_UYVY8_2X8 值是 0x2006

#define MEDIA_BUS_FMT_UYVY8_2X8			0x2006

而我们要使用的是 YUYV 格式,那就将这里改掉,改成 YUYV

// priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8;
priv->cfmt_code = MEDIA_BUS_FMT_YUYV8_2X8;

运行,

/root # ./camera_display2.out 
当前摄像头支持的分辨率:240x240[   55.298859] sun6i-csi 1cb4000.csi: Wrong width or height 240x240 (800x600 expected)

格式问题看起来解了,那就继续解分辨率的问题
drivers/media/i2c/ov2640.c

// mf->width	= win->width;// mf->height	= win->height;mf->width	= 240;mf->height	= 240;

将 ov2640.c 文件中关于分辨率的设置,都硬编码为 240x240
运行
在这里插入图片描述
成功了,不过发现图像方向和屏幕方向不一致,
没找到摄像头旋转的方法,最终旋转屏幕实现了方向一致
在这里插入图片描述
至此,摄像头捕获的画面可以实时显示到屏幕了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/120452.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Webshell 流量特征分析

前言&#xff1a;webshell是以asp、php、jsp或者cgi等网页文件形式存在的一种代码执行环境&#xff0c;主要用于网站管理、服务器管理、权限管理等操作。使用方法简单&#xff0c;只需上传一个代码文件&#xff0c;通过网址访问&#xff0c;便可进行很多日常操作&#xff0c;极…

【计算机网络】图解路由器(一)

本系列包含&#xff1a; 图解路由器&#xff08;一&#xff09;图解路由器&#xff08;二&#xff09; 图解路由器&#xff08;一&#xff09; 1、什么是路由器&#xff1f;2、什么是路由选择&#xff1f;3、什么是转发&#xff1f;4、路由器设备有哪些类型&#xff1f;5、根据…

基于php的物流信息公共平台设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于php的物流信息公共平…

让Pegasus天马座开发板实现超声波测距

在完成《让Pegasus天马座开发板用上OLED屏》后&#xff0c;我觉得可以把超声波测距功能也在Pegasus天马座开发板上实现。于是在箱子里找到了&#xff0c;Grove - Ultrasonic Ranger 这一超声波测传感器。 官方地址: https://wiki.seeedstudio.com/Grove-Ultrasonic_Ranger 超声…

分类预测 | MATLAB实现SSA-FS-SVM麻雀算法同步优化特征选择结合支持向量机分类预测

分类预测 | MATLAB实现SSA-FS-SVM麻雀算法同步优化特征选择结合支持向量机分类预测 目录 分类预测 | MATLAB实现SSA-FS-SVM麻雀算法同步优化特征选择结合支持向量机分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现SSA-FS-SVM麻雀算法同步优化特征选择结…

PyTorch meshgrid 生成网格坐标

torch.meshgrid(*tensors, indexingNone) 使用输入的 1-D 张量创建网格坐标 示例 indexing‘xy’ import torcha torch.arange(3) b torch.arange(3, 6)x, y torch.meshgrid(a, b, indexingxy) print(x , x, sep\n) print(y , y, sep\n)输出: x tensor([[0, 1, 2],[0, …

第七天:gec6818开发板QT和Ubuntu中QT安装连接sqlite3数据库驱动环境保姆教程

sqlite3数据库简介 帮助文档 SQL Programming 大多数关系型数的操作步骤&#xff1a;1&#xff09;连接数据库 多数关系型数据库都是C/S模型 (Client/Server)sqlite3是一个本地的单文件关系型数据库&#xff0c;同样也有“连接”的过程 2&#xff09;操作数据库 作为程序员&am…

面试问到MySQL模块划分与架构体系怎么办

面试问到Mysql模块划分与架构体系怎么办 文章目录 1. 应用层连接管理器&#xff08;Connection Manager&#xff09;安全性和权限模块&#xff08;Security and Privilege Module&#xff09; 2. MySQL服务器层2.1. 服务支持和工具集2.2. SQL Interface2.3. 解析器举个解析器 …

华为摄像头智能安防监控解决方案

云时代来袭&#xff0c;数字化正在从园区办公延伸到生产和运营的方方面面&#xff0c;智慧校园&#xff0c;柔性制造&#xff0c;掌上金融和电子政务等&#xff0c;面对各种各样的新兴业态的涌现&#xff0c;企业需要构建一张无所不联、随心体验、业务永续的全无线网络&#xf…

分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测

分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测 目录 分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测&…

ChunJun: 自定义插件

序言 Chunjun的版本兼容可能会有问题,在我们了解了自定义插件后,在修改源码以应对不同的场景就会得心应手了,针对Chunjun1.12.Release版本说明cuiyaonan2000163.com 自定义插件整体流程 从数据流的角度来看ChunJun&#xff0c;可以理解为不同数据源的数据流通过对应的ChunJu…

【iOS】引用计数与autorelease

文章目录 前言一、什么是自动引用计数二、内存管理/引用计数三、内存管理的思考方式四、release与autorelease五、赋值的引用计数 前言 最近在学习iOS内存管理之引用计数&#xff0c;特此撰写博客记录学习过程 一、什么是自动引用计数 自动引用计数&#xff08;ARC&#xff0…