最近在做视频采集方面的工作, 对V4L2稍微研究了下,写下一些自己的心得和体会,同时做一下总结, 如果有那方面写错的话请多多指教
我们都知道硬件千千万万,比如有A厂家的摄像头,B厂家的摄像头,那对应得就有A厂家摄像头驱动,B厂家
摄像头的驱动,那HAL就得通过A厂家驱动获取数据,B厂家驱动获取数据.那有无数厂家了?所以就抽象出
来了一套驱动框架,不管你那个厂家的我摄像头,上面只用调接口,通过驱动框架来获取数据.
官方解释:
下面的是总结的白话:
Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。
凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。
就像公司的老板一般都不会直接找底层的员工谈话,而是找部门经理了解情况,一个是因为底层屌丝人数多,
意见各有不同,措辞也不准,部门经理会把情况汇总后再向上汇报;二个是老板时间宝贵。
camera中运用的框架是是这样的: 位于HAL下层, 以后我会分析v4l2在HAL层的运用获取数据
至此我们大概明白了V4L2大概是做什么的.
思考:: 假如我不通过V4L2那么我怎么获取camera数据,并且把数据保存下来,视频应该获取不了,因为
视频会在HAL里面把一帧一帧数据传送给codec,然后经过编解码,才生成视频,我们通过V4l2能获取到
一帧一帧数据,并且保存为图片,其实HAL的数据就是通过v4l2拿到原始数据的,然后HAL层在经过算法
等的处理,返回给cameraservice,然后返回给App,App在去保存。所以就生成了图片
一个大致的流程类似这样的
fd = open(deviceName, O_RDWR | O_NONBLOCK, 0);
struct v4l2_capability cap; //这个设备的功能,比如是否是视频输入设备
struct v4l2_cropcap cropcap; //裁剪能力
struct v4l2_crop crop; // 裁剪
struct v4l2_format fmt; // 设置视频捕获格式
struct v4l2_input input; //视频输入
一些参数的意思
// VIDIOC_REQBUFS:分配内存
// VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
// VIDIOC_QUERYCAP:查询驱动功能
// VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
// VIDIOC_S_FMT:设置当前驱动的频捕获格式
// VIDIOC_G_FMT:读取当前驱动的频捕获格式
// VIDIOC_TRY_FMT:验证当前驱动的显示格式
// VIDIOC_CROPCAP:查询驱动的修剪能力
// VIDIOC_S_CROP:设置视频信号的边框
// VIDIOC_G_CROP:读取视频信号的边框
// VIDIOC_QBUF:把数据从缓存中读取出来
// VIDIOC_DQBUF:把数据放回缓存队列
// VIDIOC_STREAMON:开始视频显示函数
// VIDIOC_STREAMOFF:结束视频显示函数
// VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。
//查询能力
if (-1 == px_ioctrl(fd, VIDIOC_QUERYCAP, &cap)){
SKY_LOG("VIDIOC_QUERYCAP ERROR ");
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
SKY_LOG("can't capture for V4L2_CAP_VIDEO_CAPTURE");
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
SKY_LOG("V4L2_CAP_STREAMING error");
}
//把结构体所占的内存全部初始化为0, 方便以后的赋值操作
//这个地方可以定义一个宏函数暂时先这样
memset(&input, 0x00, sizeof(struct v4l2_input));
memset(&cropcap, 0x00, sizeof(struct v4l2_cropcap));
memset(&fmt, 0, sizeof(struct v4l2_format));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = cap_rect.width;
fmt.fmt.pix.height = cap_rect.height;
fmt.fmt.pix.pixelformat = CAPTURE_PIXELFORMAT;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED_BT;
//设置图像的格式
int result = ioctrl(fd, VIDIOC_TRY_FMT, &fmt);
if (result < 0){
SKY_LOG("VIDIOC_TRY_FMT error");
}
result = ioctrl(fd, VIDIOC_S_FMT, &fmt);
if (result < 0){
SKY_LOG("VIDIOC_S_FMT error");
}
memset(&crop, 0x00, sizeof(struct v4l2_crop));
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c.left = cropcap.defrect.left;
crop.c.top = cropcap.defrect.top;
crop.c.width = cropcap.defrect.width;
crop.c.height = cropcap.defrect.height;
result = ioctrl(fd, VIDIOC_S_CROP, &crop);
if (result < 0){
SKY_LOG("VIDIOC_S_CROP error");
}
SLOGD("sky init userptr");
int result;
struct v4l2_requestbuffers req;
memset(&req, 0x00, sizeof(struct v4l2_requestbuffers));
req.count = USE_BUF_COUNT; // buffer size if 4
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // video capture type
req.memory = V4L2_MEMORY_MMAP; //mmap 方式
result = px_ioctrl(fd, VIDIOC_REQBUFS, &req); //申请空间
if (req.count < 2) {
SKY_LOG("insufficient buf memory\n"); //不足的
}
if (req.count > USE_BUF_COUNT){
SKY_LOG("request buf memory\n"); //申请error
}
buffers = (struct buffer*)calloc(req.count, sizeof(*buffers));
for (n_buffers = 0; n_buffers < req.count; n_buffers++)
{
struct v4l2_buffer buf;
//clear buffer
memset(&buf, 0x00, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
//查询分配的buffer信息
int result = px_ioctrl(fd, VIDIOC_QUERYBUF, &buf);
if (-1 == result)
{
SKY_LOG("VIDIOC_QUERYBUF error");
}
//void *mmap(void *start, size_t length, int prot, int flags,
//int fd, off_t offset);
//start:: 映射区的开始地址
//length:: 映射区的长度
//prot :: 期望的内存保护标志,不能与文件的打开模式冲突
//flags:: 指定映射类型
//offset:被映射对象内容的起点。
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
}
SLOGD("sky start capturing");
enum v4l2_buf_type type;
int result;
//n_buffers 在初始化buffer的时候已经自动+1了
for (size_t i = 0; i < n_buffers; i++){
struct v4l2_buffer buf;
memset(&buf, 0x00, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
//VIDIOC_QBUF 把buffer放入放进输入队列,等待填充数据
result = px_ioctrl(fd, VIDIOC_QBUF, &buf);
if (-1 == result){
SKY_LOG("VIDIOC_QBUF error");
return;
}
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//开始
result = px_ioctrl(fd, VIDIOC_STREAMON, &type);
if (-1 == result){
SKY_LOG("VIDIOC_STREAMON error");
}
不断的loop查询 ```java void mainloop() { SLOGD(“sky start main loop”); unsigned int count; count = MAX_FRAME_COUNT; //获取200帧数据
while (count – >0) { for (;;) { fd_set fds; struct timeval tv; int r;
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec = 2;
tv.tv_usec = 0;
//检测摄像头是否有数据可以读取
//select 和 poll 来对文件描述符进行监听是否有数据可读。
r = select(fd+1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
continue;
SKY_LOG("system error");
}
if (0 == r) {
SKY_LOG("select time out");
}
read_frame();
}
}
}
6.关掉设备
```java
void close_device()
{
SLOGD("sky close device");
int close_id = close(fd);
if (-1 == close_id){
SKY_LOG("close error");
}
}
c语言可以定义全局变量 int a; int a = 20;
但是在c++中这样定义就不可以,因为检测增强了
1.在c中,没有返回值和形参检测,可以编译过
int getRec(w, h)
{
}
int main()
{
getRec(10, 20, 30);//也可以编译通过
}
2.在c++中这些是不可以的
双冒号运算符,作用域运算符号 ::
int a = 10;
int main()
{
int a = 66;
cout << ::a << endl;//把作用域提高到全局的,会打印10
return 0;
}
namespace 命名空间
解决名称冲突问题
必须在全局作用域下声明
命名空间可以放函数,结构体,类,变量
命名空间是开放的,可以随时加入新的成员
using声明,
namespace testA
{
int a = 80;
}
namespace testB
{
int a = 100;
}
void test
{
//using 编译指令
using namespace testA;
using namespace testB;
cout << testA::a <<endl; //要指定那个命名空间下的
}
int main()
{
int a = 50;
//using 声明
using testA::a
cout << a <<endl; //出错,编译器有就近原则,不知道用那个,就报错了,所以我们要避免二义性
return 0;
}
我们都知道在mk文件里面做copy操作可以通过 PRODUCT_COPY_FILES 这个变量来做copy操作
由于项目需要我需要对同一个文件,名字相同,内容不同的文件进行copy,类似这样
a.mk -> copy a.txt
b.mk -> copy a.txt
c.mk
-- include a.mk
-- include b.mk
我想当然的以为肯定在b.mk里面文件会覆盖掉a.mk文件里面的东西, 因为mk文件执行是树形结构从上到下
执行,本以为b.mk文件里面的会覆盖掉a.mk, 编译,刷机, 验证结果,目瞪狗呆!为什么是a.mk文件里面
的文件会覆盖掉b.mk文件
这个目录下 /build/core/Makefile
# filter out the duplicate <source file>:<dest file> pairs.
unique_product_copy_files_pairs :=
$(foreach cf,$(PRODUCT_COPY_FILES), \
$(if $(filter $(unique_product_copy_files_pairs),$(cf)),,\
$(eval unique_product_copy_files_pairs += $(cf))))
unique_product_copy_files_destinations :=
$(foreach cf,$(unique_product_copy_files_pairs), \
$(eval _src := $(call word-colon,1,$(cf))) \
$(eval _dest := $(call word-colon,2,$(cf))) \
$(call check-product-copy-files,$(cf)) \
$(if $(filter $(unique_product_copy_files_destinations),$(_dest)), \
$(info PRODUCT_COPY_FILES $(cf) ignored.), \
$(eval _fulldest := $(call append-path,$(PRODUCT_OUT),$(_dest))) \
$(if $(filter %.xml,$(_dest)),\
$(eval $(call copy-xml-file-checked,$(_src),$(_fulldest))),\
$(eval $(call copy-one-file,$(_src),$(_fulldest)))) \
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(_fulldest)) \
$(eval unique_product_copy_files_destinations += $(_dest))))
从去除重复的算法来看,是从第一个字符串开始,如果目标中没有就添加,如果已经有就不做任何处理,因此只有最先描述的目标有效
至此明白了为什么,只有第一个copy的有效. 因为如果检测到已经有了这个文件的话,就不会对第二个文件做处理了.
PS: 如何在mk里面加入log,
输出信息的方式为::
$(warning xxxxx) 或者 $(error xxxxx)
输出变量的方式为::
$(warning $(XXX))
如果是输出error的话::
如果是$(error xxxxx)将会停止编译