~~~~
之前在linux下使用v4l2打开相机,现在记录一下,写博客是个好习惯,要好好培养,要坚持。
~~~~
使用v4l2打开相机,如果相机支持输出yuv,其实很多时候,写起来,感觉还是很爽的,但是有些相机yuv输出的帧率太低了,这个时候你会发现还支持一个格式,叫Motion-JPEG,这就很烦了,虽然帧率够了,但是需要解码呀,需要使用第三方解码库,这里我使用的就是ffmpeg,但是这就比较耗cpu的性能了。
~~~~
比较喜欢输出格式是nv21,或者yuyv的,当然这是个人看法。
~~~~ 下面我们开始打开v4l2的设备,我们需要找到设备的节点,这里我们在dev下面找就行了,我这里是/dev/video0就是我要找的设备
// open V4L2 device
int mCameraFd = open(dev_name, O_RDWR | O_NONBLOCK, 0);
if (mCameraFd == -1) {
//打开相机失败,可以通过strerror(errno),获取到错误的信息
privtf("error:%s\n", strerror(errno));
return -1;
}
打开设备成功之后,我们需要获取设备的信息,这样我们可以判断这个设备是不是我们想要打开的设备,得到一些设备信息,同时可以查询设备支持得操作模式。
//这个是v4l2获取的信息的结构体
struct v4l2_capability {
__u8 driver[16]; //驱动名。
__u8 card[32]; // Device名
__u8 bus_info[32]; //在Bus系统中存放位置
__u32 version; //driver 版本
__u32 capabilities; //能力集 通常为:V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
__u32 reserved[4]; //保留字段
};
能力集里面类型很多,但是我们取摄像头视频,只需要看它是否支持V4L2_CAP_VIDEO_CAPTURE和V4L2_CAP_STREAMING就行了,通常也只有这两个,这两个的意思就是这是一个视频捕捉设备和具有数据流控制模式,只要有这两个,我们就可以从设备获取码流。
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004
#define V4L2_CAP_VBI_CAPTURE 0x00000010
#define V4L2_CAP_VBI_OUTPUT 0x00000020
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080
#define V4L2_CAP_RDS_CAPTURE 0x00000100
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400
#define V4L2_CAP_RDS_OUTPUT 0x00000800
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE 0x00001000
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE 0x00002000
#define V4L2_CAP_VIDEO_M2M_MPLANE 0x00004000
#define V4L2_CAP_VIDEO_M2M 0x00008000
#define V4L2_CAP_TUNER 0x00010000
#define V4L2_CAP_AUDIO 0x00020000
#define V4L2_CAP_RADIO 0x00040000
#define V4L2_CAP_MODULATOR 0x00080000
#define V4L2_CAP_SDR_CAPTURE 0x00100000
#define V4L2_CAP_EXT_PIX_FORMAT 0x00200000
#define V4L2_CAP_SDR_OUTPUT 0x00400000
#define V4L2_CAP_META_CAPTURE 0x00800000
#define V4L2_CAP_READWRITE 0x01000000
#define V4L2_CAP_ASYNCIO 0x02000000
#define V4L2_CAP_STREAMING 0x04000000
#define V4L2_CAP_TOUCH 0x10000000
#define V4L2_CAP_DEVICE_CAPS 0x80000000
我们可以使用VIDIOC_QUERYCAP 命令通过结构 v4l2_capability 获取设备支持的操作模式:
ret = ioctl(mCameraFd, VIDIOC_QUERYCAP, &cap);
if (ret < 0) {
//无法查看设备
close(mCameraFd);
mCameraFd = -1;
return -1;
}
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
//设备不支持视频捕捉
close(mCameraFd);
mCameraFd = -1;
return-1;
}
if ((cap.capabilities & V4L2_CAP_STREAMING) == 0) {
//设备不支持数据流控制模式
close(mCameraFd);
mCameraFd = -1;
return -1;
}
然后我们需要查询设备支持得格式,同时设置设备输出的格式,当然我们设置格式,需要是设备支持得格式才能成功
//获取所有支持得格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
LOGE("Support format:");
while(ioctl(mCameraFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
printf("description = %s. pixelformat = %d", fmtdesc.description, fmtdesc.pixelformat);
fmtdesc.index++;
}
//获取当前格式
struct v4l2_format fmt;
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(mCameraFd, VIDIOC_G_FMT, &fmt);
if(ret != -1) {
printf("format information : width:%d. height:%d. pixelformat = %d.",
fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.pixelformat);
}
//设置当前格式
int ret = 0;
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = width;
format.fmt.pix.height = height;
format.fmt.pix.pixelformat = pix_fmt; //nv21 825382478
format.fmt.pix.field = V4L2_FIELD_NONE; //V4L2_FIELD_NONE 1
ret = ioctl(mCameraFd, VIDIOC_S_FMT, &format);
if (ret < 0)
{
//失败,设置格式失败, 可以通过strerror(errno),获取到错误的信息
privtf("error:%s\n", strerror(errno));
return ret;
}
然后我们可以设置帧率,有时候我们不需要太高的帧率,这样可以减低系统运算的负荷,保证实际帧率不会超过你设置的帧率。
int ret = 0;
struct v4l2_streamparm params;
params.parm.capture.timeperframe.numerator = 1;
params.parm.capture.timeperframe.denominator = (__u32)30;//帧率默认是30
params.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
params.parm.capture.capturemode = 4;//V4L2_MODE_PREVIEW;
ret = ioctl(mCameraFd, VIDIOC_S_PARM, ¶ms);
if (ret < 0) {
printf("v4l2setCaptureParams failed, %s", strerror(errno));
}
else {
printf("v4l2setCaptureParams ok");
}
return ret;
然后就是向设备申请缓冲区了,我这申请了7个。
int ret = 0;
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(rb));
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
rb.count = 7;
ret = ioctl(mCameraFd, VIDIOC_REQBUFS, &rb);
if (ret < 0)
{
printf("Init: VIDIOC_REQBUFS failed: %s", strerror(errno));
return ret;
}
return 0;
设置好了缓冲区,我们就需要去映射内存区,这里我们先定义一个结构体,用来映射内存区,保存图像信息。
typedef struct v4l2_mem_map_t{
void * mem[NB_BUFFER];//这里就是对应你申请了几个缓冲区
int length;
}v4l2_mem_map_t;
/**
* 内存映射
*/
v4l2_mem_map_t mMapMem;//在下面会使用到
struct v4l2_buffer buf;
for (int i = 0; i < NB_BUFFER; i++)//NB_BUFFER 为7,这就是之前申请的缓冲区数量
{
memset (&buf, 0, sizeof (struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = (__u32)i;
ret = ioctl (mCameraFd, VIDIOC_QUERYBUF, &buf);
if (ret < 0)
{
printf("Unable to query buffer (%s)", strerror(errno));
return ret;
}
mMapMem.mem[i] = mmap (0, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
mCameraFd,
buf.m.offset);
mMapMem.length = buf.length;
if (mMapMem.mem[i] == MAP_FAILED)
{
printf("Unable to map buffer (%s)", strerror(errno));
return -1;
}
// start with all buffers in queue
ret = ioctl(mCameraFd, VIDIOC_QBUF, &buf);
if (ret < 0)
{
printf("VIDIOC_QBUF Failed");
return ret;
}
}
我们前面申请了我们就要释放:
int ret = 0;
for (int i = 0; i < NB_BUFFER; i++)
{
if(mMapMem.mem[i] != NULL) {
ret = munmap(mMapMem.mem[i], mMapMem.length);
if (ret < 0) {
printf("v4l2CloseBuf Unmap failed");
return ret;
}
mMapMem.mem[i] = NULL;
}
}
然后就是打开视频流开关,设备就开始给我们传输数据了。
int ret = 0;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl (mCameraFd, VIDIOC_STREAMON, &type);
if (ret < 0)
{
printf("StartStreaming: Unable to start capture: %s", strerror(errno));
return ret;
}
对应一个关闭视频流传输:
int ret = 0;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl (mCameraFd, VIDIOC_STREAMOFF, &type);
if (ret < 0)
{
printf("StopStreaming: Unable to stop capture: %s", strerror(errno));
return ret;
}
printf("V4L2Camera::v4l2StopStreaming OK");
视频传输出来了,在内存映射的地方,我们就需要取出来,取出来也是有方法的:
//等待相机传输码流出来
int TestCamera::v4l2WaitCameraReady() {
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds);
FD_SET(mCameraFd, &fds);
//超时的时间
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select(mCameraFd + 1, &fds, NULL, NULL, &tv);
if (r == -1)
{
printf("select err, %s", strerror(errno));
return -1;
}
else if (r == 0)
{
printf("select timeout");
return -2;
}
return 0;
}
//获取一帧数据,其实就是告诉v4l2,我要访问下一个内存区,访问之后就需要释放,然后设备才可以写你访问的内存区
//如果你不释放,就会发现,前几帧有数据,然后就卡住不动了
int TestCamera::getPreviewFrame(v4l2_buffer *buf){
int ret = 0;
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->memory = V4L2_MEMORY_MMAP;
ret = ioctl(mCameraFd, VIDIOC_DQBUF, buf);
if (ret < 0)
{
printf("GetPreviewFrame: VIDIOC_DQBUF Failed, %s, line : %d", strerror(errno), __LINE__);
return ERROR_LOCAL; // can not return false
}
return SUCCESS_LOCAL;
}
//释放我们之前申请访问的内存区
void TestCamera::releasePreviewFrame(int index){
int ret = 0;
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = index;
ret = ioctl(mCameraFd, VIDIOC_QBUF, &buf);
if (ret != 0)
{
printf("releasePreviewFrame: VIDIOC_QBUF Failed: index = %d, ret = %d, %s",
buf.index, ret, strerror(errno));
}
}
取数据的实例:
int ret = 0;
ret = v4l2WaitCameraReady();
if(ret != SUCCESS_LOCAL || m_threadRun != 1){
return false;
}
// get one video frame
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(v4l2_buffer));
buf.index = (__u32)-1;
ret = getPreviewFrame(&buf);
if(ret != SUCCESS_LOCAL){
//失败返回重新等待
return false;
}
if(m_threadRun != 1){
//释放release
releasePreviewFrame(buf.index);
return true;
}
//指针p保存所有Y的位置
uint8_t *p = NULL;
//指针p,保存的就是码流了
p = (uint8_t *) mMapMem.mem[buf.index];
//操作码流
//释放release
releasePreviewFrame(buf.index);
至于码流怎么处理,就是不同的事情,做不同的操作了。