一、V4L2簡介
V4L2(video for linux 2的縮寫)是Linux下關于采集圖檔、視訊和音頻資料相關裝置的驅動架構,為驅動和應用程式提供了一套統一的接口規範。使應用層跟硬體層分離,硬體層的驅動操作都交給V4L2,應用層隻需要調用V4L2的接口即可,如下圖
二、QT通過V4L2接口采集視訊的步驟
1、首先打開攝像頭
2、配置裝置(采集的頻率,圖像寬高, 圖像格式(jpeg, yuv--422,420)),擷取裝置資訊,確定配置成功
3、在核心空間申請緩沖區隊列(2-4個),把申請好的緩沖區對象一一映射到使用者空間
4、開始 / 結束采集
三、具體實作過程
1、打開攝像頭,可通過open函數來打開攝像頭
this->vfd = ::open(deviceName.c_str(), O_RDWR);
if(this->vfd < 0)
{
perror("open fail");
VideoException vexp("open fail");//建立異常對象
//抛異常
throw vexp;
}
2、配置裝置 和 擷取裝置資訊
在配置時,用到了linux下videodev2.h(路徑為/usr/include/linux/videodev2.h)頭檔案中的V4l2_format 和 v4l2_pix_format 結構體。
V4l2_format
v4l2_pix_format
将上面的結構體資訊寫入裝置即可,通過ioctl函數将屬性寫入裝置,該函數的參數用到了一些宏定義 (videodev2.h中)
VIDIOC_S_FMT ---設定格式屬性
VIDIOC_G_FMT ---擷取格式屬性
函數參數說明
int ioctl(檔案描述符, 指令--上的宏, 參數--上宏定義裡面的結構體對象)
配置屬性和擷取屬性的實作的代碼
truct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //使用第一個結構體
fmt.fmt.pix.width = 640;
vfmt.fmt.pix.height = 480;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//(設定視訊輸出格式,但是要攝像頭支援)
//通過ioctl把屬性寫入裝置
int ret = ioctl(this->vfd, VIDIOC_S_FMT, &vfmt);
//通過ioctl從裝置擷取屬性
memset(&vfmt, 0, sizeof(vfmt)); //清空
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(this->vfd, VIDIOC_G_FMT, &vfmt)
3、申請緩沖區隊列, 映射到使用者空間
1、使用的結構體 (同樣也在videodev2.h中)
申請緩沖區隊列
映射 ---從隊列中拿出一個空間做映射
2、使用到的宏定義
3、通過ioctl函數進行操作
申請隊列長度
ioctl(檔案描述符, VIDIOC_REQBUFS(宏定義), struct v4l2_requestbuffer 對象位址)
從隊列拿出一個空間做映射
ioctl(檔案描述符, VIDIOC_QUERYBUF(宏定義), struct v4l2_buffer 對象位址)
放回隊列
ioctl(檔案描述符, VIDIOC_QBUF(宏定義), struct v4l2_buffer 對象位址)
4、具體代碼實作
一、申請緩沖區隊列
//1申請緩沖區隊列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.count = this->count;//申請緩沖區隊列長度
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.memory = V4L2_MEMORY_MMAP;
int ret = ioctl(this->vfd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
VideoException vexp("req buffer fail");//建立異常對象
throw vexp;
}
二、映射緩沖區隊列到使用者空間
for(int i=0; i<this->count; i++)
{
struct VideoFrame frame;
struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
mapbuffer.index = i;
mapbuffer.memory = V4L2_MEMORY_MMAP;
//從隊列中拿到核心空間
ret = ioctl(this->vfd, VIDIOC_QUERYBUF, &mapbuffer);
if(ret < 0)
{
perror("query fail");
}
//映射
frame.length = mapbuffer.length;
frame.start = (char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, this->vfd, mapbuffer.m.offset);
//空間放回隊列中(核心空間)
ret = ioctl(this->vfd, VIDIOC_QBUF, &mapbuffer);
//把frame添加到容器framebuffers
framebuffers.push_back(frame);
}
4、開始/結束采集資料
1、使用到的宏定義
2、通過ioctl函數進行操作
enum v4l2_buf_type type; //指派為:V4L2_BUF_TYPE_VIDEO_CAPTURE
開始采集 ioctl(檔案描述符, VIDIOC_STREAMON(宏定義), &type)
結束采集 ioctl(檔案描述符, VIDIOC_STREAMOFF(宏定義), &type)
3、采集資料的步驟
1、從隊列中拿出一個緩沖區
2、從映射使用者空間中把緩沖區資料取走
3、把緩沖區放回隊列
3、具體代碼實作
一、開始采集
//開始采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(this->vfd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("start fail");
}
二、結束采集
//結束采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(this->vfd, VIDIOC_STREAMOFF, &type);
if(ret < 0)
{
perror("stop fail");
}
//釋放映射
for(int i=0; i<this->framebuffers.size(); i++)
{
munmap(framebuffers.at(i).start, framebuffers.at(i).length);
}
三、采集資料
struct v4l2_buffer readbuf;
readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(this->vfd, VIDIOC_DQBUF, &readbuf)<0)//取一針資料
{
perror("read image fail");
exit(1);
}
printf("%ld\n", readbuf.length);
*length = readbuf.length;
memcpy(imageBuffer,framebuffers[readbuf.index].start, framebuffers[readbuf.index].length); //拷貝資料
//把用完的隊列空間放回隊列中重複使用
if(ioctl(vfd, VIDIOC_QBUF, &readbuf)<0)
{
perror("destroy fail");
exit(1);
}
5、測試采集資料是否成功
int main()
{
V4l2Api mV4l2("/dev/video0");
mV4l2.open();
char image[1000000];
int length=0;
while(1)
{
mV4l2.grapImage(image, &length);
FILE * file = fopen("./my.jpg", "w+");
fwrite(image,length, 1, file);
fclose(file);
break;
}
mV4l2.close();
return 0;
}
注意:我這裡采集的視訊輸出格式為MJPEG,是以才能直接存儲為.jpg格式的圖檔。