前提在已經存在 /dev/video0 設備,大概流程可以在其他博客看到,本文給出了可以直接運行的 c 語言完整代碼。作者使用 Jetson nano 加 羅技 c922 攝像頭測試
之後會有文章嘗試控制各種曝光參數以及編碼爲視頻流
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> /* low-level i/o */
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/mman.h>
#include <bits/types/struct_timespec.h>
#include <bits/types/struct_timeval.h>
#include <linux/videodev2.h>
int main(int argc, char* argv[])
{
int fd = open("/dev/video0", O_RDWR);
if (0)
{
//輸出所有支持的格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Support format:\n");
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
fmtdesc.index++;
}
printf("enum done\n");
}
if (0)
{
//查看當前的輸出格式
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_G_FMT, &fmt);
printf("Current data format information : \n\twidth: % d\n\theight: % d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
struct v4l2_fmtdesc fmtdesc2;
fmtdesc2.index = 0;
fmtdesc2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc2) != -1)
{
if (fmtdesc2.pixelformat & fmt.fmt.pix.pixelformat)
{
printf("\tformat: % s\n", fmtdesc2.description);
break;
}
fmtdesc2.index++;
}
}
{
//設置視頻格式
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1280;
fmt.fmt.pix.height = 720;
fmt.fmt.pix.pixelformat = 0;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
//設置設備捕獲視頻的格式
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
{
printf("set format failed\n");
close(fd);
return 0;
}
//如果攝像頭不支持我們設置的分辨率格式,則 fmt.fmt.pix.width 會被修改,所以此處建議再次檢查 fmt.fmt.pix. 的各種信息
//向驅動申請幀緩存
int CAP_BUF_NUM = 4;
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = CAP_BUF_NUM; //申請一個擁有四個緩衝幀的緩衝區
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
if (EINVAL == errno)
{
printf(" does not support memory mapping\n");
close(fd);
return 0;
}
else
{
printf("does not support memory mapping, unknow error\n");
close(fd);
return 0;
}
}
else
{
printf("alloc success\n");
}
if (req.count < CAP_BUF_NUM)
{
printf("Insufficient buffer memory\n");
close(fd);
return 0;
}
else
{
printf("get %d bufs\n", req.count);
}
//將幀緩存與本地內存關聯
typedef struct VideoBuffer { //定義一個結構體來映射每個緩衝幀
void* start;
size_t length;
} VideoBuffer;
VideoBuffer* buffers = calloc(req.count, sizeof(*buffers));
struct v4l2_buffer buf;
for (int numBufs = 0; numBufs < req.count; numBufs++) {//映射所有的緩存
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {//獲取到對應index的緩存信息,此處主要利用length信息及offset信息來完成後面的mmap操作。
printf("unexpect error %d\n", numBufs);
free(buffers);
close(fd);
return 0;
}
buffers[numBufs].length = buf.length;
// 轉換成相對地址
buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); // #include <sys/mman.h>
if (buffers[numBufs].start == MAP_FAILED) {
printf("%d map failed errno %d\n", numBufs, errno);
free(buffers);
close(fd);
return 0;
}
//addr 映射起始地址,一般爲NULL ,讓內核自動選擇
//prot 標誌映射後能否被讀寫,其值爲PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
//flags 確定此內存映射能否被其他進程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 確定被映射的內存地址 返回成功映射後的地址,不成功返回MAP_FAILED ((void*)-1)
//int munmap(void* addr, size_t length);// 最後記得斷開映射
//把緩衝幀加入緩衝隊列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0)
{
printf("add buf to queue failed %d\n", numBufs);
free(buffers);
close(fd);
return 0;
}
}
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 打開設備視頻流 */
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
printf("stream open failed\n");
free(buffers);
close(fd);
return 0;
}
int franeCount = 9;
while (franeCount--)
{
struct v4l2_buffer capture_buf;
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
/* 將已經捕獲好視頻的內存拉出已捕獲視頻的隊列 */
if (ioctl(fd, VIDIOC_DQBUF, &capture_buf) < 0)
{
printf("get frame failed %d\n", franeCount);
break;
}
else
{
//long long secc = capture_buf.timestamp.tv_sec;
//long long secc2 = capture_buf.timestamp.tv_usec;
//printf("timestamp %lld %lld", secc, secc2);
//handle frame
{
FILE* f = fopen("/tftpboot/yuv.yuv", "ab");
int wt = fwrite(buffers[capture_buf.index].start, 1, buffers[capture_buf.index].length, f);
printf("wt %d\n", wt);
fclose(f);
}
printf("get %d frame success\n", franeCount);
//把用完的幀重新插回隊列
if (ioctl(fd, VIDIOC_QBUF, &capture_buf) == -1) {
printf("insert buf failed %d\n", franeCount);
break;
}
}
}
//清理資源
int ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < CAP_BUF_NUM; i++)
{
munmap(buffers[i].start, buffers[i].length);
}
free(buffers);
close(fd);
}
return 0;
}