视频监控—在LCD上显示摄像头图像
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:USB_Video_Example 1.5、UVC 1.5 Class specification
- 开发环境:Linux-4.13.0-41内核(虚拟机)、arm-linux-gcc-4.3.2工具链
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
目录
一、框架
1、程序框架
仿照之前数码相册的框架来进行改造,完成在开发板上动态显示摄像头的数据信息:
对于上述完成主要功能的5个部分:display显示部分、debug调试信息输出部分、render渲染部分、video视频设备部分、convert格式转换部分
- video视频设备部分:负责获得摄像头的原始数据;
- convert格式转换部分:负责对于摄像头的原始数据,进行格式转换,对于不同的摄像头有不同的数据格式,需要包含多个文件来支持多种转换方式;
- render渲染部分:负责对转换得到的数据格式,进行压缩、合并成可以在LDC上显示的数据;
- display显示部分:合并后的数据显示在LCD上;
- debug调试部分:设置打印等级和打印通道,通过打印等级来控制程序打印的调试信息、错误信息、警告信息等,通过打印通道设置来控制程序打印的输出的流向是标准输出还是网络打印输出。
2、Makefile框架
分为如下3部分:
- 顶层目录的Makefile:定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链、编译参数、链接参数。
- 顶层目录的Makefile.build:把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为
built-in.o
。 - 各级子目录的Makefile:把当前目录下的
.c
文件编进程序里。
二、数据的流向
如上图所示:
- 在 video视频设备部分,会从硬件中读取出原始的摄像头数据,存在
videobuf
中,这些数据可能是YUYV
、MJPEG
、RGB565
格式等,需要convert格式转换部分转换成rgb格式 - 在convert格式转换部分,会根据video视频设备部分传来的数据格式
videobuf
,调用适合的转换文件来把原始的摄像头数据转换成rgb格式,存储在convertbuf
中。 - 在render渲染部分中,对已经转为rgb格式的数据
convertbuf
进行缩放,随后渲染成可以直接可以在LCD上显示的数据,存储在zoombuf
中,把整理好的数据数据流入到Framebuffer
中 - 在display显示部分中,对
Framebuffe
的数据进行最后的排版与处理,最终显示在LCD上。
三、video视频设备部分代码编写
对于这个部分,采用面向对象的编程思想,抽取了如下的结构体:
typedef struct VideoDevice T_VIDEODEVICE, *PT_VIDEODEVICE;
typedef struct VideoBuf T_VIDEOBUF, *PT_VIDEOBUF;
typedef struct VdieoOpr T_VIDEOOPR, *PT_VIDEOOPR;
/*!
* 摄像头的描述信息
*/
struct VideoDevice {
int fd; /**< 文件句柄 */
int pixel_format; /**< 像素格式 */
int width; /**< 图像宽度 */
int height; /**< 图像高度 */
int videobuf_num; /**< 分配缓冲区个数 */
int videeobuf_maxlen; /**< 每个缓存区的大小 */
int cur_bufindex; /**< 存储从队列取出缓冲区的index */
/**! 所分配的缓冲区 */
unsigned char *videobuf[REQ_BUFFER_NUM];
PT_VIDEOOPR video_opr; /**< 数据处理 */
};
/*!
* 存储视频数据
*/
struct VideoBuf {
T_PIXELDATAS pixel_data; /**< 借用T_PixelDatas */
int pixel_format; /**< 像素格式 */
};
/*!
* 视频数据处理结构体
*/
struct VdieoOpr {
char *name; /**< 设备名 */
/* 初始化设备 */
int (*InitDevice)(char *dev_name, PT_VIDEODEVICE video_device);
/* 退出设备 */
int (*ExitDevice)(PT_VIDEODEVICE video_device);
/* 启动设备 */
int (*StartDevice)(PT_VIDEODEVICE video_device);
/* 停止设备 */
int (*StopDevice)(PT_VIDEODEVICE video_device);
/* 获得视频数据 */
int (*GetFrame)(PT_VIDEODEVICE video_device, PT_VIDEOBUF video_buf);
/* 获得视频格式 */
int (*GetFormat)(PT_VIDEODEVICE video_device);
/* 把视频数据放回队列 */
int (*PutFrame)(PT_VIDEODEVICE video_device, PT_VIDEOBUF video_buf);
struct VdieoOpr *ptNext;
};
1、video_manager.c
对于其的管理模式,也是采用链表的方式进行管理。
在这个文件中,向下提供注册函数,供每个支持的设备注册进链表中,进行统一的管理。
向上提供设备的初始化函数,供上层应用来调用初始化整个video部分
。
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file video_manager.c
* @brief 视频数据管理者文件,函数的实现
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-26 | v0.0.1 | Kcode | 视频数据管理者文件
* -----------------------------------------------------------------------------
******************************************************************************/
#include <string.h>
#include "config.h"
#include "video_manager.h"
#include "debug_manager.h"
static PT_VIDEOOPR s_ptVideoOprHead = NULL; /**< 支持设备链表头 */
/*!
* @brief 注册函数
* @param ptVideoOpr[in] 要注册的设备结构体结点
* @return 0:成功 -1:失败
*/
int RegisterVideoOpr(PT_VIDEOOPR ptVideoOpr)
{
PT_VIDEOOPR ptTmp;
if (!s_ptVideoOprHead)
{
s_ptVideoOprHead = ptVideoOpr;
ptVideoOpr->ptNext = NULL;
}
else
{
ptTmp = s_ptVideoOprHead;
while (ptTmp->ptNext)
{
ptTmp = ptTmp->ptNext;
}
ptTmp->ptNext = ptVideoOpr;
ptVideoOpr->ptNext = NULL;
}
return 0;
}
/*!
* @brief 显示所支持的设备
* @param [in] 无
* @return 无
*/
void ShowVideoOpr(void)
{
int i = 0;
PT_VIDEOOPR ptTmp = s_ptVideoOprHead;
while (ptTmp)
{
printf("%02d %s\n", i++, ptTmp->name);
ptTmp = ptTmp->ptNext;
}
}
/*!
* @brief 根据name得到指定的设备文件
* @param pName[in] 名字
* @return 成功:返回设备的结构体指针 失败:返回空指针
*/
PT_VIDEOOPR GetVideoOpr(char *pName)
{
PT_VIDEOOPR ptTmp = s_ptVideoOprHead;
while (ptTmp)
{
if (strcmp(ptTmp->name, pName) == 0)
{
return ptTmp;
}
ptTmp = ptTmp->ptNext;
}
return NULL;
}
/*!
* @brief 初始化设备函数,把对支持的各个设备进行初始化工作
* @param devname[in] 名字
* @param video_dev[in] 视频设备
* @return 0:初始化成功 -1:失败
*/
int VideoDeviceInit(char * devname, PT_VIDEODEVICE video_dev)
{
int error;
PT_VIDEOOPR ptTmp = s_ptVideoOprHead;
while (ptTmp)
{
error = ptTmp->InitDevice(devname, video_dev);
if (!error)
return 0;
ptTmp = ptTmp->ptNext;
}
return -1;
}
/*!
* @brief 初始化函数,把对支持的各个设备注册进链表进行统一管理
* @param [in] 无
* @return 无
*/
int VideoInit(void)
{
int error;
error = V4l2Init();
if (error)
{
DBG_PRINTF("V4l2Init error!\n");
return -1;
}
return 0;
}
/*!
* @brief 退出函数,对设备进行具体的清理工作
* @param [in] 无
* @return 无
*/
void VideoExit(char *name, PT_VIDEOBUF video_buf)
{
PT_VIDEOOPR ptTmp = s_ptVideoOprHead;
while (ptTmp)
{
if (strcmp(ptTmp->name, name) == 0)
ptTmp->ExitDevice(video_buf);
ptTmp = ptTmp->ptNext;
}
}
2、v4l2.c
对于这个文件,是具体的设备文件。
在这个文件中,实现对摄像头数据的捕获、处理与存储。
整个执行流程与uvc驱动程序差不多,仿照luvcview
来进行编写,涉及的函数开发大致如下:
- 打开驱动
open
- 获取该设备的类型 VIDIOC_QUERYCAP
- 列举摄像头的数据格式 VIDIOC_ENUM_FMT
- 获得摄像头的数据格式 VIDIOC_G_FMT
- 测试是否支持该摄像头的数据格式 VIDIOC_TRY_FMT
- 设置参数VIDIOC_S_FMT
- 请求系统分配缓冲区VIDIOC_REQBUFS
- 查询所分配的缓冲区的信息 VIDIOC_QUERYBUF
- 映射缓冲区mmap
- 把缓冲区放入队列 VIDIOC_QBUF
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file v4l2.c
* @brief v4l2设备的文件,参考luvcview
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-26 | v0.0.1 | Kcode | v4l2设备的文件
* -----------------------------------------------------------------------------
******************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include "config.h"
#include "debug_manager.h"
#include "video_manager.h"
#include "disp_manager.h"
static T_VIDEOOPR s_V4l2VideoOpr;
static int V4l2GetFrameForReadWrite(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf);
static int V4l2PutFrameForReadWrite(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf);
static int V4l2PutFrameForStreaming(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf);
static int V4l2GetFrameForStreaming(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf);
/* 所支持的格式 */
static int s_isSupportedFormat[] = {
V4L2_PIX_FMT_YUYV,
V4L2_PIX_FMT_MJPEG,
V4L2_PIX_FMT_RGB565,
};
/*!
* @brief 判断是否支持此格式
* @return 0:不支持 1:支持
*/
static int isSupportThisFormat(int pixel_format)
{
int i;
int size;
size = sizeof(s_isSupportedFormat)/sizeof(s_isSupportedFormat[0]);
for(i = 0; i < size; i++)
{
if (s_isSupportedFormat[i] == pixel_format)
return 1;
}
return 0;
}
/*!
* @brief 初始化设备
* 1、确定是否为视频捕获设备、支持何种接口(streaming/read、write)
* 2、查询支持何种属性
* 3、设置摄像头格式
* 4、向驱动程序申请buffer
* 5、确定每个buffer的信息且mmap
* 6、把buffer放入队列
* 7、启动设备
* 8、poll机制等待数据
* 9、有数据则从队列中取出并处理
* 10、再次放入队列,重复步骤
* 11、不操作时停止设备
*/
static int V4l2InitDevice(char *dev_name, PT_VIDEODEVICE video_device)
{
int i;
int fd;
int ret;
int lcd_width;
int lcd_height;
int lcd_bpp;
struct v4l2_capability v4l2cap;
struct v4l2_fmtdesc v4l2fmt_desc;
struct v4l2_format v4l2fmt;
struct v4l2_requestbuffers v4l2req_buf;
struct v4l2_buffer v4l2buf;
/*!
* 打开设备
*/
fd = open(dev_name, O_RDWR);
if (fd < 0)
{
DebugPrint(APP_ERR"open error! Function:%s Line:%d\n",
__FUNCTION__, __LINE__);
return -1;
}
video_device->fd = fd;
/*!
* 调用VIDIOC_QUERYCAP的ioctl
*/
memset(&v4l2cap, 0, sizeof(struct v4l2_capability));
ret = ioctl(fd, VIDIOC_QUERYCAP, &v4l2cap);
if (ret < 0)
{
DebugPrint(APP_ERR"Error opening device %s: unable to query device.\n",
dev_name);
goto err_eixt;
}
/*!
* 判断是否为视频捕获设备
*/
if (!(v4l2cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
DebugPrint(APP_ERR"%s is not a video capture device\n", dev_name);
goto err_eixt;
}
if (v4l2cap.capabilities & V4L2_CAP_STREAMING)
DebugPrint(APP_NOTICE"%s supports streaming i/o\n", dev_name);
if (v4l2cap.capabilities & V4L2_CAP_READWRITE)
DebugPrint(APP_NOTICE"%s supports read i/o\n", dev_name);
/*!
* 枚举所支持的格式
*/
memset(&v4l2fmt_desc, 0, sizeof(v4l2fmt_desc));
v4l2fmt_desc.index = 0;
v4l2fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4l2fmt_desc)) == 0)
{
if(isSupportThisFormat(v4l2fmt_desc.pixelformat))
{
video_device->pixel_format = v4l2fmt_desc.pixelformat;
break;
}
v4l2fmt_desc.index++;
}
/*!
* 无支持的设备格式
*/
if (!video_device->pixel_format)
{
DebugPrint(APP_ERR"Can not support the format of this device\n");
goto err_eixt;
}
/*!
* 设置格式
*/
GetDispResolution(&lcd_width, &lcd_height, &lcd_bpp);
memset(&v4l2fmt, 0, sizeof(struct v4l2_format));
v4l2fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2fmt.fmt.pix.width = lcd_width;
v4l2fmt.fmt.pix.height = lcd_height;
v4l2fmt.fmt.pix.pixelformat = video_device->pixel_format;
v4l2fmt.fmt.pix.field = V4L2_FIELD_ANY;
/* 如果驱动参数无法支持某些参数(分辨率),会调整这些参数,并返回给应用程序 */
ret = ioctl(fd, VIDIOC_S_FMT, &v4l2fmt);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to set format: %d Function:%s Line:%d\n",
__FUNCTION__, __LINE__);
goto err_eixt;
}
/* 重新记录分辨率 */
video_device->width = v4l2fmt.fmt.pix.width;
video_device->height = v4l2fmt.fmt.pix.height;
/*!
* 申请buffer
*/
memset(&v4l2req_buf, 0, sizeof(struct v4l2_requestbuffers));
v4l2req_buf.count = REQ_BUFFER_NUM;
v4l2req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &v4l2req_buf);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to allocate buffers Function:%s Line:%d\n",
__FUNCTION__, __LINE__);
goto err_eixt;
}
/* 重新记录缓冲区个数 */
video_device->videobuf_num = v4l2req_buf.count;
/*!
* 对于streaming接口的设备,需要查询缓冲区信息且mmap缓冲区
* 对于read/write接口的设置,只需分配内存,调用read
*/
if (v4l2cap.capabilities & V4L2_CAP_STREAMING)
{
/* 查询并映射每一个缓冲区 */
for (i = 0; i < video_device->videobuf_num; i++)
{
/* 设置 */
memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
v4l2buf.index = i;
v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2buf.memory = V4L2_MEMORY_MMAP;
/* 查询 */
ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2buf);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to query buffer\n");
goto err_eixt;
}
/* mmap */
video_device->videeobuf_maxlen = v4l2buf.length;
video_device->videobuf[i] = mmap(0 /* start anywhere */ ,
v4l2buf.length, PROT_READ, MAP_SHARED, fd, v4l2buf.m.offset);
if (video_device->videobuf[i] == MAP_FAILED)
{
DebugPrint(APP_ERR"Unable to map buffer\n");
goto err_eixt;
}
}
/*!
* 缓冲区逐个放入队列
*/
for (i = 0; i < video_device->videobuf_num; ++i)
{
/* 设置 */
memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
v4l2buf.index = i;
v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2buf.memory = V4L2_MEMORY_MMAP;
/* 放入队列 */
ret = ioctl(fd, VIDIOC_QBUF, &v4l2buf);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to queue buffer\n");
goto err_eixt;
}
}
}
else if (v4l2cap.capabilities & V4L2_CAP_READWRITE)
{
s_V4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;
s_V4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;
video_device->videobuf_num = 1;
/* 在程序所能支持的格式中,一个像素最多占4字节 */
video_device->videeobuf_maxlen = video_device->width * \
video_device->height * 4;
video_device->videobuf[0] = (unsigned char *)malloc(
video_device->videeobuf_maxlen);
}
video_device->video_opr = &s_V4l2VideoOpr;
return 0;
err_eixt:
close(fd);
return -1;
}
/*!
* @brief 退出设备
* @return 0
*/
static int V4l2ExitDevice(PT_VIDEODEVICE video_device)
{
int i;
if (s_V4l2VideoOpr.GetFrame == V4l2GetFrameForStreaming)
{
for(i = 0; i < video_device->videobuf_num; i++)
{
if (video_device->videobuf[i])
{
munmap(video_device->videobuf[i], video_device->videeobuf_maxlen);
video_device->videobuf[i] = NULL;
}
}
}
else if (s_V4l2VideoOpr.GetFrame == V4l2GetFrameForReadWrite)
free(video_device->videobuf[0]);
close(video_device->fd);
return 0;
}
/*!
* @brief 启动设备
* @return 0:成功 -1:失败
*/
static int V4l2StartDevice(PT_VIDEODEVICE video_device)
{
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret;
ret = ioctl(video_device->fd, VIDIOC_STREAMON, &type);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to start capture\n");
return -1;
}
return 0;
}
/*!
* @brief 停止设备
* @return 0:成功 -1:失败
*/
static int V4l2StopDevice(PT_VIDEODEVICE video_device)
{
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret;
ret = ioctl(video_device->fd, VIDIOC_STREAMOFF, &type);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to stop capture\n");
return -1;
}
return 0;
}
/*!
* @brief 获得视频数据,Streaming接口
* poll,VIDIOC_DQBUF
* @return 0:成功 -1:失败
*/
static int V4l2GetFrameForStreaming(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf)
{
int ret;
struct pollfd poll_fd[1];
struct v4l2_buffer v4l2buf;
/*!
* 调用poll机制
*/
poll_fd[0].fd = video_device->fd;
poll_fd[0].events = POLLIN;
/* 永远等待 */
ret = poll(poll_fd, 1, -1);
if (ret <= 0)
{
DebugPrint(APP_ERR"Poll error!\n");
return -1;
}
else
{
/*!
* VIDIOC_DQBUF,从队列中取出缓冲区
*/
memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(video_device->fd, VIDIOC_DQBUF, &v4l2buf);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to dequeue buffer\n");
return -1;
}
/* 记录取出缓冲区的index */
video_device->cur_bufindex = v4l2buf.index;
/* 设置传入的buf */
video_buf->pixel_format = video_device->pixel_format;
video_buf->pixel_data.width = video_device->width;
video_buf->pixel_data.height = video_device->height;
video_buf->pixel_data.bpp = (video_device->pixel_format == V4L2_PIX_FMT_YUYV)? 16 :\
(video_device->pixel_format == V4L2_PIX_FMT_MJPEG)? 0 :\
(video_device->pixel_format == V4L2_PIX_FMT_RGB565)? 16 : 0;
video_buf->pixel_data.linebytes = video_device->width * video_buf->pixel_data.bpp / 8;
video_buf->pixel_data.TotalBytes = v4l2buf.bytesused;
video_buf->pixel_data.PixelDatas = video_device->videobuf[v4l2buf.index];
}
return 0;
}
/*!
* @brief 获得视频数据,read/write接口
* VIDIOC_REQBUF
* @return 0:成功 -1:失败
*/
static int V4l2GetFrameForReadWrite(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf)
{
int ret;
ret = read(video_device->fd, video_device->videobuf[0],
video_device->videeobuf_maxlen);
if (ret <= 0)
return -1;
/* 设置传入的buf */
video_buf->pixel_format = video_device->pixel_format;
video_buf->pixel_data.width = video_device->width;
video_buf->pixel_data.height = video_device->height;
video_buf->pixel_data.bpp = (video_device->pixel_format == V4L2_PIX_FMT_YUYV)? 16 :\
(video_device->pixel_format == V4L2_PIX_FMT_MJPEG)? 0 :\
(video_device->pixel_format == V4L2_PIX_FMT_RGB565)? 16 : 0;
video_buf->pixel_data.linebytes = video_device->width * video_buf->pixel_data.bpp / 8;
video_buf->pixel_data.TotalBytes = ret;
video_buf->pixel_data.PixelDatas = video_device->videobuf[0];
return 0;
}
/*!
* @brief 把视频数据重新放入队列,Streaming接口,VIDIOC_QBUF
* @return 0:成功 -1:失败
*/
static int V4l2PutFrameForStreaming(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf)
{
int ret;
struct v4l2_buffer v4l2buf;
memset(&v4l2buf, 0, sizeof(struct v4l2_buffer));
v4l2buf.index = video_device->cur_bufindex;
v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(video_device->fd, VIDIOC_QBUF, &v4l2buf);
if (ret < 0)
{
DebugPrint(APP_ERR"Unable to queue buffer\n");
return -1;
}
return 0;
}
/*!
* @brief 获得视频数据的原始格式
* @return 0:成功 -1:失败
*/
static int V4l2GetFormat(PT_VIDEODEVICE video_device)
{
return video_device->pixel_format;
}
/*!
* @brief 把视频数据重新放入,read/write接口,无操作
* @return 0:成功 -1:失败
*/
static int V4l2PutFrameForReadWrite(PT_VIDEODEVICE video_device,
PT_VIDEOBUF video_buf)
{
return 0;
}
/*!
* 构造T_VIDEOOPR结构体
*/
static T_VIDEOOPR s_V4l2VideoOpr = {
.name = "v4l2",
.InitDevice = V4l2InitDevice,
.ExitDevice = V4l2ExitDevice,
.StartDevice = V4l2StartDevice,
.StopDevice = V4l2StopDevice,
.GetFrame = V4l2GetFrameForStreaming,
.GetFormat = V4l2GetFormat,
.PutFrame = V4l2PutFrameForStreaming,
};
/*!
* @brief 初始化函数,把s_V412VideoOpr结构体注册进链表中
* @param [in] 无
* @return 0:成功 -1:失败
*/
int V4l2Init(void)
{
return RegisterVideoOpr(&s_V4l2VideoOpr);
}
四、convert转换部分代码编写
对于这个部分,采用面向对象的编程思想,抽取了如下的结构体:
/*!
* 数据转换操作
*/
typedef struct VideoConvertOpr {
char *name;
/* 支持的格式 */
int (*isSupport)(int pixel_format_in, int pixel_format_out);
/* 格式转换 */
int (*Convert)(PT_VIDEOBUF videobuf_in, PT_VIDEOBUF videobuf_out);
/* 退出 */
int (*ConvertExit)(PT_VIDEOBUF videobuf_out);
struct VideoConvertOpr *ptNext;
}T_VIDEOCONVERTOPR, *PT_VIDEOCONVERTOPR;
1、convert_manager.c
对于其的管理模式,也是采用链表的方式进行管理。
在这个文件中,向下提供注册函数,供每个支持的设备注册进链表中,进行统一的管理。
向上提供设备的初始化函数,供上层应用来调用初始化整个convert部分
。
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file yuv2rgb.c
* @brief 转换视频数据格式,把摄像头原始的yuv格式转换成rgb565或32格式
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-27 | v0.0.1 | Kcode | 转换视频数据格式
* -----------------------------------------------------------------------------
******************************************************************************/
#include <linux/videodev2.h>
#include <stdlib.h>
#include "color.h"
#include "debug_manager.h"
#include "convert_manager.h"
/*!
* @brief 判断输入输出格式是否为yuv和rgb
* @return 0:不支持 1:支持
*/
static int isSupportYuv2Rgb(int pixel_format_in, int pixel_format_out)
{
if (pixel_format_in != V4L2_PIX_FMT_YUYV)
return 0;
if ((pixel_format_out != V4L2_PIX_FMT_RGB565) && \
(pixel_format_out != V4L2_PIX_FMT_RGB32))
return 0;
return 1;
}
/*!
* @brief 支持的格式转换yuv为输出格式rgb565
* 参考luvcview
* @return 0:成功
*/
static unsigned int
Pyuv422torgb565(unsigned char * input_ptr, unsigned char * output_ptr,
unsigned int image_width, unsigned int image_height)
{
unsigned int i, size;
unsigned char Y, Y1, U, V;
unsigned char *buff = input_ptr;
unsigned char *output_pt = output_ptr;
unsigned int r, g, b;
unsigned int color;
size = image_width * image_height /2;
for (i = size; i > 0; i--) {
/* bgr instead rgb ?? */
Y = buff[0] ;
U = buff[1] ;
Y1 = buff[2];
V = buff[3];
buff += 4;
r = R_FROMYV(Y,V);
g = G_FROMYUV(Y,U,V); //b
b = B_FROMYU(Y,U); //v
/*!
* 第一个颜色,把r,g,b三色构造为rgb565的16位值
*/
r = r >> 3;
g = g >> 2;
b = b >> 3;
color = (r << 11) | (g << 5) | b;
*output_pt++ = color & 0xff;
*output_pt++ = (color >> 8) & 0xff;
r = R_FROMYV(Y1,V);
g = G_FROMYUV(Y1,U,V); //b
b = B_FROMYU(Y1,U); //v
/*!
* 第二个颜色,把r,g,b三色构造为rgb565的16位值
*/
r = r >> 3;
g = g >> 2;
b = b >> 3;
color = (r << 11) | (g << 5) | b;
*output_pt++ = color & 0xff;
*output_pt++ = (color >> 8) & 0xff;
}
return 0;
}
/*!
* @brief 支持的格式转换yuv为输出格式rgb32
* 参考luvcview
* @return 0:成功
*/
static unsigned int
Pyuv422torgb32(unsigned char * input_ptr, unsigned char * output_ptr,
unsigned int image_width, unsigned int image_height)
{
unsigned int i, size;
unsigned char Y, Y1, U, V;
unsigned char *buff = input_ptr;
unsigned int *output_pt = (unsigned int *)output_ptr;
unsigned int r, g, b;
unsigned int color;
size = image_width * image_height /2;
for (i = size; i > 0; i--) {
/* bgr instead rgb ?? */
Y = buff[0] ;
U = buff[1] ;
Y1 = buff[2];
V = buff[3];
buff += 4;
/*!
* 第一个颜色,把r,g,b三色构造为rgb888的32位值
*/
r = R_FROMYV(Y,V);
g = G_FROMYUV(Y,U,V); //b
b = B_FROMYU(Y,U); //v
color = (r << 16) | (g << 8) | b;
*output_pt++ = color;
/*!
* 第二个颜色,把r,g,b三色构造为rgb888的32位值
*/
r = R_FROMYV(Y1,V);
g = G_FROMYUV(Y1,U,V); //b
b = B_FROMYU(Y1,U); //v
color = (r << 16) | (g << 8) | b;
*output_pt++ = color;
}
return 0;
}
/*!
* @brief 支持的格式转换yuv为输出格式rgb
* 参考luvcview
* @return 0:转换成功 -1:分配内存失败
*/
static int Yuv2RgbConvert(PT_VIDEOBUF videobuf_in,
PT_VIDEOBUF videobuf_out)
{
PT_PIXELDATAS pixeldatas_in = &videobuf_in->pixel_data;
PT_PIXELDATAS pixeldatas_out = &videobuf_out->pixel_data;
pixeldatas_out->width = pixeldatas_in->width;
pixeldatas_out->height = pixeldatas_in->height;
/*!
* 判断支持哪种输出格式,调用对应的转换函数
*/
if (videobuf_out->pixel_format == V4L2_PIX_FMT_RGB565)
{
pixeldatas_out->bpp = 16;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->linebytes * pixeldatas_out->height;
if (!pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (!pixeldatas_out->PixelDatas)
{
DebugPrint(APP_ERR"pixeldatas_out->PixelDatas malloc err\n");
return -1;
}
}
Pyuv422torgb565(pixeldatas_in->PixelDatas, pixeldatas_out->PixelDatas,
pixeldatas_out->width, pixeldatas_out->height);
return 0;
}
else if (videobuf_out->pixel_format == V4L2_PIX_FMT_RGB32)
{
pixeldatas_out->bpp = 32;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->linebytes * pixeldatas_out->height;
if (!pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (!pixeldatas_out->PixelDatas)
{
DebugPrint(APP_ERR"pixeldatas_out->PixelDatas malloc err\n");
return -1;
}
}
Pyuv422torgb32(pixeldatas_in->PixelDatas, pixeldatas_out->PixelDatas,
pixeldatas_out->width, pixeldatas_out->height);
return 0;
}
return -1;
}
/*!
* @brief 退出函数,释放内存
* @return 0
*/
static int Yuv2RgbConvertExit(PT_VIDEOBUF videobuf_out)
{
if (videobuf_out->pixel_data.PixelDatas)
{
free(videobuf_out->pixel_data.PixelDatas);
videobuf_out->pixel_data.PixelDatas = NULL;
}
return 0;
}
static T_VIDEOCONVERTOPR s_Yuv2RgbConvert = {
.name = "yuv2rgb",
.isSupport = isSupportYuv2Rgb,
.Convert = Yuv2RgbConvert,
.ConvertExit = Yuv2RgbConvertExit,
};
extern void initLut(void);
/*!
* @brief 初始化函数,把对支持的各个转换格式进行初始化工作
* 对应的结构体放入链表统一管理
* @return 0:初始化成功 1:失败
*/
int Yuv2RgbInit(void)
{
initLut();
return RegisterVideoConvertOpr(&s_Yuv2RgbConvert);
}
2、yuv2rgb.c
这个文件参考了部分luvcview
的源码,对于视频的原始格式到转换的格式为yuv---->rgb565/rgb32
通过判断视频源数据格式是否为V4L2_PIX_FMT_YUYV
,以及指定的转换格式是否为V4L2_PIX_FMT_RGB565 / V4L2_PIX_FMT_RGB32
查看是否支持此转换文件**。
在进行具体的转换时,判断用户所要求的转换格式为V4L2_PIX_FMT_RGB565 / V4L2_PIX_FMT_RGB32
,根据情况提前设置好转换格式的bpp、行宽、总的数据大小、像素存储的空间
,最后调用具体的转换函数。
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file yuv2rgb.c
* @brief 转换视频数据格式,把摄像头原始的yuv格式转换成rgb565或32格式
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-27 | v0.0.1 | Kcode | 转换视频数据格式
* -----------------------------------------------------------------------------
******************************************************************************/
#include <linux/videodev2.h>
#include <stdlib.h>
#include "color.h"
#include "debug_manager.h"
#include "convert_manager.h"
/*!
* @brief 判断输入输出格式是否为yuv和rgb
* @return 0:不支持 1:支持
*/
static int isSupportYuv2Rgb(int pixel_format_in, int pixel_format_out)
{
if (pixel_format_in != V4L2_PIX_FMT_YUYV)
return 0;
if ((pixel_format_out != V4L2_PIX_FMT_RGB565) && \
(pixel_format_out != V4L2_PIX_FMT_RGB32))
return 0;
return 1;
}
/*!
* @brief 支持的格式转换yuv为输出格式rgb565
* 参考luvcview
* @return 0:成功
*/
static unsigned int
Pyuv422torgb565(unsigned char * input_ptr, unsigned char * output_ptr,
unsigned int image_width, unsigned int image_height)
{
unsigned int i, size;
unsigned char Y, Y1, U, V;
unsigned char *buff = input_ptr;
unsigned char *output_pt = output_ptr;
unsigned int r, g, b;
unsigned int color;
size = image_width * image_height /2;
for (i = size; i > 0; i--) {
/* bgr instead rgb ?? */
Y = buff[0] ;
U = buff[1] ;
Y1 = buff[2];
V = buff[3];
buff += 4;
r = R_FROMYV(Y,V);
g = G_FROMYUV(Y,U,V); //b
b = B_FROMYU(Y,U); //v
/*!
* 第一个颜色,把r,g,b三色构造为rgb565的16位值
*/
r = r >> 3;
g = g >> 2;
b = b >> 3;
color = (r << 11) | (g << 5) | b;
*output_pt++ = color & 0xff;
*output_pt++ = (color >> 8) & 0xff;
r = R_FROMYV(Y1,V);
g = G_FROMYUV(Y1,U,V); //b
b = B_FROMYU(Y1,U); //v
/*!
* 第二个颜色,把r,g,b三色构造为rgb565的16位值
*/
r = r >> 3;
g = g >> 2;
b = b >> 3;
color = (r << 11) | (g << 5) | b;
*output_pt++ = color & 0xff;
*output_pt++ = (color >> 8) & 0xff;
}
return 0;
}
/*!
* @brief 支持的格式转换yuv为输出格式rgb32
* 参考luvcview
* @return 0:成功
*/
static unsigned int
Pyuv422torgb32(unsigned char * input_ptr, unsigned char * output_ptr,
unsigned int image_width, unsigned int image_height)
{
unsigned int i, size;
unsigned char Y, Y1, U, V;
unsigned char *buff = input_ptr;
unsigned int *output_pt = (unsigned int *)output_ptr;
unsigned int r, g, b;
unsigned int color;
size = image_width * image_height /2;
for (i = size; i > 0; i--) {
/* bgr instead rgb ?? */
Y = buff[0] ;
U = buff[1] ;
Y1 = buff[2];
V = buff[3];
buff += 4;
/*!
* 第一个颜色,把r,g,b三色构造为rgb888的32位值
*/
r = R_FROMYV(Y,V);
g = G_FROMYUV(Y,U,V); //b
b = B_FROMYU(Y,U); //v
color = (r << 16) | (g << 8) | b;
*output_pt++ = color;
/*!
* 第二个颜色,把r,g,b三色构造为rgb888的32位值
*/
r = R_FROMYV(Y1,V);
g = G_FROMYUV(Y1,U,V); //b
b = B_FROMYU(Y1,U); //v
color = (r << 16) | (g << 8) | b;
*output_pt++ = color;
}
return 0;
}
/*!
* @brief 支持的格式转换yuv为输出格式rgb
* 参考luvcview
* @return 0:转换成功 -1:分配内存失败
*/
static int Yuv2RgbConvert(PT_VIDEOBUF videobuf_in,
PT_VIDEOBUF videobuf_out)
{
PT_PIXELDATAS pixeldatas_in = &videobuf_in->pixel_data;
PT_PIXELDATAS pixeldatas_out = &videobuf_out->pixel_data;
pixeldatas_out->width = pixeldatas_in->width;
pixeldatas_out->height = pixeldatas_in->height;
/*!
* 判断支持哪种输出格式,调用对应的转换函数
*/
if (videobuf_out->pixel_format == V4L2_PIX_FMT_RGB565)
{
pixeldatas_out->bpp = 16;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->linebytes * pixeldatas_out->height;
if (!pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (!pixeldatas_out->PixelDatas)
{
DebugPrint(APP_ERR"pixeldatas_out->PixelDatas malloc err\n");
return -1;
}
}
Pyuv422torgb565(pixeldatas_in->PixelDatas, pixeldatas_out->PixelDatas,
pixeldatas_out->width, pixeldatas_out->height);
}
else if (videobuf_out->pixel_format == V4L2_PIX_FMT_RGB32)
{
pixeldatas_out->bpp = 32;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->linebytes * pixeldatas_out->height;
if (!pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (!pixeldatas_out->PixelDatas)
{
DebugPrint(APP_ERR"pixeldatas_out->PixelDatas malloc err\n");
return -1;
}
}
Pyuv422torgb32(pixeldatas_in->PixelDatas, pixeldatas_out->PixelDatas,
pixeldatas_out->width, pixeldatas_out->height);
}
return 0;
}
/*!
* @brief 退出函数,释放内存
* @return 0
*/
static int Yuv2RgbConvertExit(PT_VIDEOBUF videobuf_out)
{
if (videobuf_out->pixel_data.PixelDatas)
{
free(videobuf_out->pixel_data.PixelDatas);
videobuf_out->pixel_data.PixelDatas = NULL;
}
return 0;
}
static T_VIDEOCONVERTOPR s_Yuv2RgbConvert = {
.name = "yuv2rgb",
.isSupport = isSupportYuv2Rgb,
.Convert = Yuv2RgbConvert,
.ConvertExit = Yuv2RgbConvertExit,
};
extern void initLut(void);
/*!
* @brief 初始化函数,把对支持的各个转换格式进行初始化工作
* 对应的结构体放入链表统一管理
* @return 0:初始化成功 1:失败
*/
int Yuv2RgbInit(void)
{
initLut();
return RegisterVideoConvertOpr(&s_Yuv2RgbConvert);
}
3、rgb2rgb.c
对于此文件,主要针对的是判断视频源数据格式是否为V4L2_PIX_FMT_RGB565
,以及指定的转换格式是否为V4L2_PIX_FMT_RGB565 / V4L2_PIX_FMT_RGB32
。
在转换格式时,根据情况提前设置好转换格式的bpp、行宽、总的数据大小、像素存储的空间
,若源数据格式与转换格式都为V4L2_PIX_FMT_RGB565
,则直接进行memcpy
;若不一致,则逐行处理像素的颜色信息,实现RGB565->RGB32
的转换。
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file rgb2rgb.c
* @brief 转换视频数据格式,把摄像头原始的rgb565格式转换成rgb565或32格式
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-27 | v0.0.1 | Kcode | 转换视频数据格式
* -----------------------------------------------------------------------------
******************************************************************************/
#include <linux/videodev2.h>
#include <stdlib.h>
#include <string.h>
#include "convert_manager.h"
/*!
* @brief 判断输入输出格式是否为rgb和rgb
* @return 0:不支持 1:支持
*/
static int isSupportRgb2Rgb(int pixel_format_in, int pixel_format_out)
{
if (pixel_format_in != V4L2_PIX_FMT_RGB565)
return 0;
if ((pixel_format_out != V4L2_PIX_FMT_RGB565) && \
(pixel_format_out != V4L2_PIX_FMT_RGB32))
return 0;
return 1;
}
/*!
* @brief 支持的格式转换rgb为输出格式rgb565
* 参考luvcview
* @return 0:成功 -1:失败
*/
static int Rgb2RgbConvert(PT_VIDEOBUF videobuf_in, PT_VIDEOBUF videobuf_out)
{
PT_PIXELDATAS pixeldatas_in = &videobuf_in->pixel_data;
PT_PIXELDATAS pixeldatas_out = &videobuf_out->pixel_data;
int x, y;
int r, g, b;
int color;
unsigned short *pSrc = (unsigned short *)pixeldatas_in->PixelDatas;
unsigned int *pDest = (unsigned int *)pixeldatas_in->PixelDatas;
if (videobuf_in->pixel_format != V4L2_PIX_FMT_RGB565)
{
return -1;
}
/*!
* 根据输入格式,调用不同的转换
*/
if (videobuf_in->pixel_format == V4L2_PIX_FMT_RGB565)
{
/*!
* 设置
*/
pixeldatas_out->width = pixeldatas_in->width;
pixeldatas_out->height = pixeldatas_in->height;
pixeldatas_out->bpp = pixeldatas_in->bpp;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->linebytes * pixeldatas_out->height;
if (NULL == pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (pixeldatas_out->PixelDatas == NULL)
return -1;
}
/*!
* 处理数据
*/
memcpy(pixeldatas_out->PixelDatas, pixeldatas_in->PixelDatas,
pixeldatas_out->TotalBytes);
}
else if (videobuf_in->pixel_format == V4L2_PIX_FMT_RGB32)
{
/*!
* 设置
*/
pixeldatas_out->width = pixeldatas_in->width;
pixeldatas_out->height = pixeldatas_in->height;
pixeldatas_out->bpp = 16;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->linebytes * pixeldatas_out->height;
if (NULL == pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (pixeldatas_out->PixelDatas == NULL)
return -1;
}
/*!
* 逐行处理数据
*/
pDest = (unsigned int *)pixeldatas_out->PixelDatas;
for (y = 0; y < pixeldatas_out->height; y++)
{
for (x = 0; y < pixeldatas_out->width; x++)
{
color = *pSrc++;
/* 从RGB565格式的数据中提取出R,G,B */
r = color >> 11;
g = (color >> 5) & 0x3F;
b = color & 0x1F;
/* 把提取出的R,G,B转为0x00RRGGBB的32位数据 */
color = ((r << 3) << 16) | ((g << 2) << 8) | (b << 3);
/* 存入目的地址 */
*pDest = color;
pDest++;
}
}
}
return 0;
}
/*!
* @brief 退出函数,释放内存
* @return 0
*/
static int Rgb2RgbConvertExit(PT_VIDEOBUF videobuf_out)
{
if (videobuf_out->pixel_data.PixelDatas == NULL)
{
free(videobuf_out->pixel_data.PixelDatas);
videobuf_out->pixel_data.PixelDatas = NULL;
}
return 0;
}
static T_VIDEOCONVERTOPR s_Rgb2RgbConvert = {
.name = "rgb2rgb",
.isSupport = isSupportRgb2Rgb,
.Convert = Rgb2RgbConvert,
.ConvertExit = Rgb2RgbConvertExit,
};
/*!
* @brief 初始化函数,把对支持的各个转换格式进行初始化工作
* 对应的结构体放入链表统一管理
* @return 0:初始化成功 1:失败
*/
int Rgb2RgbInit(void)
{
return RegisterVideoConvertOpr(&s_Rgb2RgbConvert);
}
4、mjpeg2rgb.c
此文件是对于视频源数据格式为V4L2_PIX_FMT_MJPEG
,转换格式为V4L2_PIX_FMT_RGB565 / V4L2_PIX_FMT_RGB32
的需求。
对于mjpeg
的视频格式,其每一帧的图像的编码都为jpeg
,所以可以使用libjpeg-turbo
库来对视频数据进行提取,逐帧逐行提取图像信息,根据情况提前设置好转换格式的bpp、行宽、总的数据大小、像素存储的空间,调用之前数码相框项目编写的
CovertOneLine(),处理每一行的像素信息
。
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file mjpeg2rgb.c
* @brief 转换视频数据格式,把摄像头原始的mjpeg格式转换成rgb565格式
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-27 | v0.0.1 | Kcode | 转换视频数据格式
* -----------------------------------------------------------------------------
******************************************************************************/
#include <linux/videodev2.h>
#include <convert_manager.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <jpeglib.h>
#include "debug_manager.h"
typedef struct MyErrorMgr
{
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
}T_MyErrorMgr, *PT_MyErrorMgr;
extern void jpeg_mem_src_tj(j_decompress_ptr cinfo,
unsigned char * inbuffer, unsigned long insize);
/*!
* @brief 判断输入输出格式是否为mhpeg和rgb
* @return 0:不支持 1:支持
*/
static int isSupportMjpeg2Rgb(int pixel_format_in, int pixel_format_out)
{
if (pixel_format_in != V4L2_PIX_FMT_MJPEG)
return 0;
if ((pixel_format_out != V4L2_PIX_FMT_RGB565) && \
(pixel_format_out != V4L2_PIX_FMT_RGB32))
return 0;
return 1;
}
/*!
* @brief 判断输入输出格式是否为mhpeg和rgb
* @return 0:不支持 1:支持
*/
static void MyErrorExit(j_common_ptr ptCInfo)
{
static char errStr[JMSG_LENGTH_MAX];
PT_MyErrorMgr ptMyErr = (PT_MyErrorMgr)ptCInfo->err;
/* Create the message */
(*ptCInfo->err->format_message) (ptCInfo, errStr);
DBG_PRINTF("%s\n", errStr);
longjmp(ptMyErr->setjmp_buffer, 1);
}
/*!
* @brief 判断输入输出格式是否为mhpeg和rgb
* @return 0:不支持 1:支持
*/
static int CovertOneLine(int width, int SrcBpp, int DstBpp,
unsigned char *pSrcDatas, unsigned char *pDstDatas)
{
unsigned int red;
unsigned int green;
unsigned int blue;
unsigned int color;
unsigned short *pDstDatas16bpp = (unsigned short *)pDstDatas;
unsigned int *pDstDatas32bpp = (unsigned int *)pDstDatas;
int i;
int pos = 0;
if (SrcBpp != 24)
{
return -1;
}
if (DstBpp == 24)
{
memcpy(pDstDatas, pSrcDatas, width*3);
}
else
{
for (i = 0; i < width; i++)
{
red = pSrcDatas[pos++];
green = pSrcDatas[pos++];
blue = pSrcDatas[pos++];
if (DstBpp == 32)
{
color = (red << 16) | (green << 8) | blue;
*pDstDatas32bpp = color;
pDstDatas32bpp++;
}
else if (DstBpp == 16)
{
/* 565 */
red = red >> 3;
green = green >> 2;
blue = blue >> 3;
color = (red << 11) | (green << 5) | (blue);
*pDstDatas16bpp = color;
pDstDatas16bpp++;
}
}
}
return 0;
}
/*!
* @brief 支持的格式转换mjpeg为输出格式rgb
* mjpeg实际是上多个jpeg
* @return 0:转换成功 -1:分配内存失败
*/
static int
Mjpeg2RgbConvert(PT_VIDEOBUF videobuf_in, PT_VIDEOBUF videobuf_out)
{
struct jpeg_decompress_struct tDInfo;
//struct jpeg_error_mgr tJErr;
int ret;
int RowStride;
unsigned char *LineBuffer = NULL;
unsigned char *pDest;
T_MyErrorMgr tJerr;
PT_PIXELDATAS pixeldatas_out = &videobuf_out->pixel_data;
// 分配和初始化一个decompression结构体
//tDInfo.err = jpeg_std_error(&tJErr);
tDInfo.err = jpeg_std_error(&tJerr.pub);
tJerr.pub.error_exit = MyErrorExit;
if(setjmp(tJerr.setjmp_buffer))
{
/* 如果程序能运行到这里, 表示JPEG解码出错 */
jpeg_destroy_decompress(&tDInfo);
if (LineBuffer)
{
free(LineBuffer);
}
if (pixeldatas_out->PixelDatas)
{
free(pixeldatas_out->PixelDatas);
}
return -1;
}
jpeg_create_decompress(&tDInfo);
// 把数据源设为内存中的数据
jpeg_mem_src_tj(&tDInfo, videobuf_in->pixel_data.PixelDatas,
videobuf_in->pixel_data.TotalBytes);
ret = jpeg_read_header(&tDInfo, TRUE);
// 设置解压参数,比如放大、缩小
tDInfo.scale_num = tDInfo.scale_denom = 1;
// 启动解压:jpeg_start_decompress
jpeg_start_decompress(&tDInfo);
// 一行的数据长度
RowStride = tDInfo.output_width * tDInfo.output_components;
LineBuffer = malloc(RowStride);
if (NULL == LineBuffer)
{
return -1;
}
pixeldatas_out->width = tDInfo.output_width;
pixeldatas_out->height = tDInfo.output_height;
//pixeldatas_out->bpp = bpp;
pixeldatas_out->linebytes = pixeldatas_out->width * pixeldatas_out->bpp / 8;
pixeldatas_out->TotalBytes = pixeldatas_out->height * pixeldatas_out->linebytes;
if (NULL == pixeldatas_out->PixelDatas)
{
pixeldatas_out->PixelDatas = (unsigned char *)malloc(pixeldatas_out->TotalBytes);
if (pixeldatas_out->PixelDatas == NULL)
return -1;
}
pDest = pixeldatas_out->PixelDatas;
// 循环调用jpeg_read_scanlines来一行一行地获得解压的数据
while (tDInfo.output_scanline < tDInfo.output_height)
{
/* 得到一行数据,里面的颜色格式为0xRR, 0xGG, 0xBB */
(void) jpeg_read_scanlines(&tDInfo, &LineBuffer, 1);
// 转到ptPixelDatas去
CovertOneLine(pixeldatas_out->width, 24, pixeldatas_out->bpp, LineBuffer, pDest);
pDest += pixeldatas_out->linebytes;
}
free(LineBuffer);
jpeg_finish_decompress(&tDInfo);
jpeg_destroy_decompress(&tDInfo);
return 0;
}
/*!
* @brief 退出函数,释放内存
* @return 0
*/
static int Mjpeg2RgbConvertExit(PT_VIDEOBUF videobuf_out)
{
if (videobuf_out->pixel_data.PixelDatas == NULL)
{
free(videobuf_out->pixel_data.PixelDatas);
videobuf_out->pixel_data.PixelDatas = NULL;
}
return 0;
}
static T_VIDEOCONVERTOPR s_Mjpeg2RgbConvert = {
.name = "mjpeg2rgb",
.isSupport = isSupportMjpeg2Rgb,
.Convert = Mjpeg2RgbConvert,
.ConvertExit = Mjpeg2RgbConvertExit,
};
/*!
* @brief 初始化函数,把对支持的各个转换格式进行初始化工作
* 对应的结构体放入链表统一管理
* @return 0:初始化成功 1:失败
*/
int Mjpeg2RgbInit(void)
{
return RegisterVideoConvertOpr(&s_Mjpeg2RgbConvert);
}
五、main.c
对于显示部分和渲染部分,采用的代码为之前数码相框的代码,所以就不过多介绍了。
对于整个程序的工作流程如下:
- 初始化调试系统并设置好打印通道
- 注册显示模块,把支持的具体显示设备放入链表进行统一管理
- 指定使用的显示设备,并获取此设备的分辨率、bpp、显存、格式
- 注册摄像头模块,把支持的具体摄像头设备放入链表进行统一管理
- 初始化摄像头模块,对于支持的摄像头设备进行初始化:
5.1. 打开驱动open
5.2. 获取该设备的类型 VIDIOC_QUERYCAP
5.3. 列举摄像头的数据格式 VIDIOC_ENUM_FMT
5.4. 获得摄像头的数据格式 VIDIOC_G_FMT
5.5. 测试是否支持该摄像头的数据格式 VIDIOC_TRY_FMT
5.6. 设置参数VIDIOC_S_FMT
5.7. 请求系统分配缓冲区VIDIOC_REQBUFS
5.8. 查询所分配的缓冲区的信息 VIDIOC_QUERYBUF
5.9. 映射缓冲区mmap
5.10. 把缓冲区放入队列 VIDIOC_QBUF
以上是对V4L2_CAP_STREAMING
接口的设置,对于V4L2_CAP_READWRITE
接口:
5.1. 打开驱动open
5.2. 获取该设备的类型 VIDIOC_QUERYCAP
5.3. 列举摄像头的数据格式 VIDIOC_ENUM_FMT
5.4. 获得摄像头的数据格式 VIDIOC_G_FMT
5.5. 测试是否支持该摄像头的数据格式 VIDIOC_TRY_FMT
5.6. 设置参数VIDIOC_S_FMT
5.7. 请求系统分配缓冲区VIDIOC_REQBUFS
5.8. 分配一个video_buffer - 获取摄像头的数据格式
- 注册转换模块,把支持的具体转换方式放入链表进行统一管理
- 根据6中获得的摄像头的数据格式与3中获得显示设备格式,获取支持格式的转换处理结构体
- 启动摄像头设备
- 处理摄像头数据:
10.1. 读出摄像头数据 ,存放在一个video_buf
中(有数据则唤醒,从队列中把buf取出,无数据进程继续休眠)
10.2. 根据6中获得的摄像头的数据格式与3中获得显示设备格式,进行转换操作,得到的转换数据存放在convert_buf
中
10.3. 比较得到的转换数据的分辨率与显示设备的分辨率,进行缩放设置,得到的缩放后数据存放在zoom_buf
中
10.4 对数据进行合并处理
10.5 最后把数据刷新到LCD屏幕 - 重新把数据放入到队列中,继续进行poll等待数据
- 最后进行一些清理工作
/*******************************************************************************
* Copyleft (c) 2021 Kcode
*
* @file main.c
* @brief 主程序,配合多个模块,实现在LCD显示屏上显示USB摄像头图像
* @author K
* @version 0.0.1
* @date 2021-07-26
* @license MulanPSL-1.0
*
* 文件修改历史:
* <时间> | <版本> | <作者> | <描述>
* 2021-07-28 | v0.0.1 | Kcode | 主程序
* -----------------------------------------------------------------------------
******************************************************************************/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "config.h"
#include "disp_manager.h"
#include "debug_manager.h"
#include "pic_operation.h"
#include "render.h"
#include "convert_manager.h"
#include "video_manager.h"
int main(int argc, char **argv)
{
int error;
int video_pixel_format;
int display_pixel_format;
int lcd_width;
int lcd_height;
int lcd_bpp;
int topleft_x;
int topleft_y;
float k;
T_VIDEODEVICE video_dev;
T_VIDEOBUF video_buf;
T_VIDEOBUF convert_buf;
T_VIDEOBUF zoom_buf;
T_VIDEOBUF framebuf;
PT_VIDEOBUF cur_video_buf;
PT_VIDEOCONVERTOPR video_convert_opr;
/*!
* 初始化调试系统
*/
error = DebugInit();
if (error) {
printf(APP_ERR"DebugInit error! File:%s Line:%d\n", __FILE__, __LINE__);
return -1;
}
error = InitDebugChanel();
if (error) {
printf(APP_ERR"InitDebugChanel error! File:%s Line:%d\n", __FILE__, __LINE__);
return -1;
}
/*!
* 操作信息提示:video2lcd </dev/video0>
*/
if (argc != 2)
{
DebugPrint(APP_NOTICE"Usage:\n");
DebugPrint(APP_NOTICE"%s </dev/video0,1.....>\n", argv[0]);
return -1;
}
/*!
* 注册显示模块
*/
error = DisplayInit();
if (error)
{
DebugPrint(APP_ERR"DisplayInit err\n");
return -1;
}
/* 选择显示设备 */
SelectAndInitDefaultDispDev("fb");
/* 获取显示屏参数 */
GetDispResolution(&lcd_width, &lcd_height, &lcd_bpp);
/* 获取显示屏显存 */
GetVideoBufForDisplay(&framebuf);
/* 设置显示器格式 */
display_pixel_format = framebuf.pixel_format;
/*!
* 注册摄像头模块
*/
error = VideoInit();
if (error)
{
DebugPrint(APP_ERR"VideoInit err\n");
return -1;
}
/*!
* 初始化指定摄像头视频设备
*/
error = VideoDeviceInit(argv[1], &video_dev);
if (error < 0)
{
DebugPrint(APP_ERR"VideoDeviceInit for %s err\n", argv[1]);
return -1;
}
/* 获取摄像头数据格式 */
video_pixel_format = video_dev.video_opr->GetFormat(&video_dev);
/*!
* 注册转换模块
*/
error = VideoConvertInit();
if (error)
{
DebugPrint(APP_ERR"VideoConvertInit err\n");
return -1;
}
/* 获取支持格式的转换处理结构体 */
video_convert_opr = GetVIdeoConvertForFormats(video_pixel_format,
display_pixel_format);
if (video_convert_opr == NULL)
{
DebugPrint(APP_ERR"Can not support this format convert\n");
return -1;
}
/*!
* 启动摄像头设备
*/
error = video_dev.video_opr->StartDevice(&video_dev);
if (error)
{
DebugPrint(APP_ERR"StartDevice for %s err\n", argv[1]);
return -1;
}
memset(&zoom_buf, 0, sizeof(T_VIDEOBUF));
memset(&video_buf, 0, sizeof(T_VIDEOBUF));
memset(&convert_buf, 0, sizeof(T_VIDEOBUF));
convert_buf.pixel_data.bpp = lcd_bpp;
convert_buf.pixel_format = display_pixel_format;
/*!
* 处理摄像头数据
*/
while(1)
{
/*!
* 读出摄像头数据
*/
error = video_dev.video_opr->GetFrame(&video_dev, &video_buf);
if (error)
{
DebugPrint(APP_ERR"GetFrame for %s err\n", argv[1]);
return -1;
}
cur_video_buf = &video_buf;
/*!
* 转换为RGB
*/
if (video_pixel_format != display_pixel_format)
{
error = video_convert_opr->Convert(&video_buf, &convert_buf);
if (error)
{
DebugPrint(APP_ERR"Convert for %s err\n", argv[1]);
return -1;
}
cur_video_buf = &convert_buf;
}
/*!
* 若图像分辨率大于LCD,缩放
*/
if (cur_video_buf->pixel_data.height > lcd_height || \
cur_video_buf->pixel_data.width > lcd_width)
{
/* 设置缩放参数 */
k = (float)cur_video_buf->pixel_data.height / (float)cur_video_buf->pixel_data.width;
zoom_buf.pixel_data.width = lcd_width;
zoom_buf.pixel_data.height = lcd_width * k;
if (zoom_buf.pixel_data.height > lcd_height)
{
zoom_buf.pixel_data.width = lcd_height * k;
zoom_buf.pixel_data.height = lcd_height;
}
zoom_buf.pixel_data.bpp = lcd_bpp;
zoom_buf.pixel_data.linebytes = zoom_buf.pixel_data.width * zoom_buf.pixel_data.bpp / 8;
zoom_buf.pixel_data.TotalBytes = zoom_buf.pixel_data.linebytes * zoom_buf.pixel_data.height;
if (zoom_buf.pixel_data.PixelDatas == NULL)
{
zoom_buf.pixel_data.PixelDatas = (unsigned char *)malloc(zoom_buf.pixel_data.TotalBytes);
}
/* 进行缩放 */
PicZoom(&cur_video_buf->pixel_data, &zoom_buf.pixel_data);
cur_video_buf = &zoom_buf;
}
/*!
* 合并到framebuffer
*/
/* 居中显示,计算此时的左上角坐标 */
topleft_x = (lcd_width - cur_video_buf->pixel_data.width) / 2;
topleft_y = (lcd_height - cur_video_buf->pixel_data.height) / 2;
PicMerge(topleft_x, topleft_y, &cur_video_buf->pixel_data, &framebuf.pixel_data);
/*!
* 把framebuffer的数据刷到LCD,显示
*/
FlushPixelDatasToDev(&framebuf.pixel_data);
/*!
* 把数据放回队列
*/
error = video_dev.video_opr->PutFrame(&video_dev, &video_buf);
if (error)
{
DebugPrint(APP_ERR"PutFrame for %s err\n", argv[1]);
return -1;
}
}
/*!
* 进行清理工作
*/
VideoConvertExit(video_convert_opr->name, &convert_buf);
VideoExit("v4l2", &video_buf);
}