本项目是一个基于 Linux V4L2 框架的入门级视频采集示例,用来帮助初学者,在嵌入式 Linux 下,进行摄像头应用开发的核心流程。
通过这个 Demo,可以掌握以下内容:
/dev/video0mmap 缓冲区QBUF / DQBUF 完成采集队列管理STREAMON / STREAMOFF 启停视频流.yuv 文件这个 Demo 适合用于:
V4L2 在嵌入式 Linux 中常见的使用场景包括:
使用 UVC 摄像头,通过 /dev/video0 获取图像,用于预览、拍照、识别等。
板载 Sensor 经过 CSI/ISP 链路后,最终通过 V4L2 视频节点输出图像。
采集到的 YUV 图像进一步送给编码器进行 H.264/H.265 压缩。
将采集帧传给 OpenCV、目标检测、人脸识别、OCR 等算法模块。
采集数据送给 DRM、SDL、Qt、OpenCV 等显示框架做实时预览。
视频走 V4L2,音频走 ALSA,再由 GStreamer 或 FFmpeg 做封装、推流和同步。
本次运行环境中,程序已经成功跑通,日志如下特征说明系统工作正常:
uvcvideoHD Camera: HD Camerausb-sunxi-ehci-1.1说明当前设备是一个 USB UVC 摄像头。
支持格式为:
MJPG:Motion-JPEGYUYV:YUYV 4:2:2本次选择的是 YUYV,更适合初学者学习原始图像采集流程。
实际设置结果:
640 x 480YUYVbytesperline = 1280sizeimage = 614400计算关系如下:
640 × 2 = 1280640 × 480 × 2 = 614400这说明格式设置正确。
程序申请了 4 个缓冲区,并用 mmap 映射到用户空间。
这说明以下步骤已经成功:
VIDIOC_REQBUFSVIDIOC_QUERYBUFmmap程序能够连续抓到 5 帧图像,并且打印了:
indexbytesusedsequencetimestamp说明以下流程全部正常:
QBUFSTREAMONDQBUFQBUFSTREAMOFF最终生成了:
frame_000.yuvframe_001.yuvframe_002.yuvframe_003.yuvframe_004.yuv本 Demo 的核心流程如下:
open() 打开 /dev/video0
VIDIOC_QUERYCAP 查询能力
VIDIOC_ENUM_FMT 枚举支持格式
VIDIOC_S_FMT 设置分辨率和像素格式
VIDIOC_REQBUFS 申请缓冲区
VIDIOC_QUERYBUF 查询每个缓冲区信息
mmap() 映射缓冲区到用户空间
VIDIOC_QBUF 将缓冲区入队
VIDIOC_STREAMON 开启视频流
进入循环:
VIDIOC_DQBUF 取出一帧.yuv 文件VIDIOC_QBUF 重新入队VIDIOC_STREAMOFF 停止视频流
munmap() 释放映射
close() 关闭设备
这是 V4L2 最经典、最重要的学习主线。
VIDIOC_QUERYCAP查询设备能力,例如:
VIDIOC_ENUM_FMT枚举摄像头支持的像素格式,例如:
VIDIOC_S_FMT设置采集格式,包括:
VIDIOC_REQBUFS向驱动申请一组缓冲区,用于流式采集。
VIDIOC_QUERYBUF获取缓冲区长度、偏移地址等信息,为 mmap 做准备。
VIDIOC_QBUF将空缓冲区交给驱动,表示该缓冲区可以用于接收下一帧图像。
VIDIOC_DQBUF从驱动取回已经采满数据的缓冲区。
VIDIOC_STREAMON开始视频流,驱动开始真正采集。
VIDIOC_STREAMOFF停止视频流,结束采集。
在 V4L2 中,常见 I/O 模式有:
read/writemmapuserptrdmabuf本 Demo 选择 mmap,原因是:
对于高分辨率和高帧率采集,mmap 通常比 read() 更合适。
程序抓帧时会打印类似:
indexbytesusedsequencetimestamp含义如下:
index表示当前出队的是哪个缓冲区。
bytesused表示当前这帧实际使用的字节数。
本例中通常为 614400,等于一帧完整 YUYV 图像大小。
sequence表示帧序号。
如果序号偶尔跳变,通常说明:
少量跳帧并不一定是错误。
timestamp表示驱动给出的该帧时间戳,可用于:
本 Demo 保存的是 .yuv 原始数据文件,而不是 .jpg 或 .bmp。
这是因为 V4L2 当前输出格式是 YUYV,它属于原始像素数据,不能直接双击查看。
要查看它,必须告诉播放器:
640x480yuyv422例如在 Ubuntu 上用 ffplay:
ffplay -f rawvideo -pixel_format yuyv422 -video_size 640x480 frame_000.yuv注意:如果文件大小为 0 字节,播放器虽然能识别输入参数,但不会显示有效画面。
出现过以下错误:
V4L2_BUF_TYPE_VIDIOC_CAPTUREVIDEO_ENUM_FMT这些都不是合法的 V4L2 宏。
正确写法是:
V4L2_BUF_TYPE_VIDEO_CAPTUREVIDIOC_ENUM_FMT这个问题说明 V4L2 中两类宏容易写混:
VIDIOC_*:ioctl 命令V4L2_*:类型、属性、枚举值Ubuntu 主机执行 scp 时,远端板子报:
ash: scp: not found说明开发板系统中没有安装 scp 程序,因此不能用标准 scp 方式拉取文件。
ssh "cat ..." > file 时中断执行:
ssh root@板子IP "cat /root/frame_000.yuv" > frame_000.yuv中途按下 Ctrl+C,导致本地得到的 frame_000.yuv 文件大小为 0。
后续调试确认开发板端原始文件是正常的,问题仅在文件传输链路,而不是 V4L2 采集逻辑。
通过本项目,应该重点理解以下内容:
采集不是一次性读数据,而是:
QBUFSTREAMONDQBUF 取帧QBUF 回去如果只有一个缓冲区,驱动采集和应用处理无法并行,容易阻塞和掉帧。
mmap 是 V4L2 最常见的入门方式理解了 mmap,就理解了应用层如何直接访问驱动分配的采集缓冲区。
bytesperline 和 sizeimage 很重要不能只按理论值想当然处理图像,应优先参考驱动返回的真实参数。
V4L2 采集到的是原始像素数据,需要配合分辨率和格式参数才能正确显示。
如果面试官问“你做过 V4L2 吗”,可以这样描述:
我做过一个基于 V4L2 的视频采集 Demo,能够打开 /dev/video0,查询设备能力,枚举摄像头支持的格式,并将摄像头设置为 640x480 YUYV。程序采用 mmap 方式申请和映射 4 个缓冲区,通过 QBUF / STREAMON / DQBUF / QBUF 完成采集循环,并把每一帧保存成 .yuv 原始图像文件。在调试过程中,我也处理过 V4L2 宏名写错导致的编译问题,以及开发板文件传输工具不完整带来的调试问题。
这段表述可以体现你:
在当前 Demo 基础上,可以继续做以下升级:
这样可以直接双击查看图像,更适合初学者。
摄像头支持 MJPG,可以直接保存压缩帧,减少文件体积。
可结合:
实现采集线程、处理线程、保存线程分离,构建生产者消费者模型。
将采集帧送给硬件或软件编码器,生成 H.264/H.265 码流。
统计:
这个 V4L2_DEMO 已经完成了一个标准的摄像头采集闭环:
从学习价值来看,这个 Demo 已经覆盖了嵌入式 Linux V4L2 应用开发最核心的主流程。
只要把这个 Demo 中的每一步都理解透,再继续往图像显示、格式转换、编码和推流方向扩展,就能够逐步建立完整的嵌入式 Linux 音视频开发能力。
V4L2_Demo: main.c v4l2_capture.c v4l2_capture.h Makefilemain.c#include "v4l2_capture.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>static void usage(const char *prog){printf("Usage: %s [-d device] [-W width] [-H height] [-f pixfmt] [-n frame_count]\n", prog);printf("Example:\n");printf(" %s -d /dev/video0 -W 640 -H 480 -f YUYV -n 5\n", prog);}static uint32_t parse_pixfmt(const char *s){if (strcmp(s, "YUYV") == 0)return v4l2_fourcc('Y', 'U', 'Y', 'V');if (strcmp(s, "MJPG") == 0)return v4l2_fourcc('M', 'J', 'P', 'G');if (strcmp(s, "NV12") == 0)return v4l2_fourcc('N', 'V', '1', '2'); fprintf(stderr, "unsupported pixel format: %s\n", s);return 0;}int main(int argc, char *argv[]){ v4l2_ctx_t ctx; uint32_t frame_count = 5; const char *save_prefix = "frame"; int opt; int ret = -1; memset(&ctx, 0, sizeof(ctx)); ctx.fd = -1; ctx.dev_name = "/dev/video0"; ctx.width = 640; ctx.height = 480; ctx.pixfmt = v4l2_fourcc('Y', 'U', 'Y', 'V');while ((opt = getopt(argc, argv, "d:W:H:f:n:h")) != -1) { switch (opt) {case'd': ctx.dev_name = optarg;break;case'W': ctx.width = (uint32_t)atoi(optarg);break;case'H': ctx.height = (uint32_t)atoi(optarg);break;case'f': ctx.pixfmt = parse_pixfmt(optarg);if (ctx.pixfmt == 0)return -1;break;case'n': frame_count = (uint32_t)atoi(optarg);break;case'h': default: usage(argv[0]);return 0; } }printf("========== V4L2 Demo Start ==========\n");printf("device : %s\n", ctx.dev_name);printf("width : %u\n", ctx.width);printf("height : %u\n", ctx.height);printf("pixfmt : %c%c%c%c\n", ctx.pixfmt & 0xFF, (ctx.pixfmt >> 8) & 0xFF, (ctx.pixfmt >> 16) & 0xFF, (ctx.pixfmt >> 24) & 0xFF);printf("frame_count : %u\n", frame_count);if (v4l2_open_device(&ctx) < 0) goto out;if (v4l2_query_capability(&ctx) < 0) goto out; v4l2_enum_formats(&ctx);if (v4l2_set_format(&ctx) < 0) goto out;if (v4l2_init_mmap(&ctx, 4) < 0) goto out;if (v4l2_start_stream(&ctx) < 0) goto out;if (v4l2_capture_frames(&ctx, frame_count, save_prefix) < 0) goto stop; ret = 0;stop: v4l2_stop_stream(&ctx);out: v4l2_uninit(&ctx); v4l2_close_device(&ctx);if (ret == 0)printf("========== V4L2 Demo Success ==========\n");elseprintf("========== V4L2 Demo Failed ==========\n");return ret;}v4l2_capture.h#ifndef V4L2_CAPTURE_H#define V4L2_CAPTURE_H#include <stdint.h>#include <linux/videodev2.h>/* * 表示一个mmap映射出来的缓冲区 * start:用户空间可访问的起始地址 * length:缓冲区长度 * */typedef struct{ void *start; uint32_t length;}buffer_t;/* * v4l2 设备上下文 * * fd:打开的设备文件描述符 * dev_name:设备节点名称,例如dev/video0 * width/height:采集分辨率 * pixfmt:像素格式,例如:V4L2_PIX_FMT_YUYV / V4L2_PIX_FMT_NV12 / V4L2_PIX_FMT_MJPEG * buffers: mmap 缓冲区数组 * buffer_count: 缓冲区个数 * */typedef struct{ int fd; const char *dev_name; uint32_t width; uint32_t height; uint32_t pixfmt; buffer_t *buffers; uint32_t buffer_count;}v4l2_ctx_t;int v4l2_open_device(v4l2_ctx_t *ctx);int v4l2_query_capability(v4l2_ctx_t *ctx);int v4l2_set_format(v4l2_ctx_t *ctx);int v4l2_enum_formats(v4l2_ctx_t *ctx); //枚举格式功能函数int v4l2_init_mmap(v4l2_ctx_t *ctx, uint32_t req_count);int v4l2_start_stream(v4l2_ctx_t *ctx);int v4l2_capture_frames(v4l2_ctx_t *ctx,uint32_t frame_count,const char *save_prefix);int v4l2_stop_stream(v4l2_ctx_t *ctx);void v4l2_uninit(v4l2_ctx_t *ctx);void v4l2_close_device(v4l2_ctx_t *ctx);#endifv4l2_capture.c#include "v4l2_capture.h"#include <stdio.h>#include <stdint.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <fcntl.h>#include <sys/ioctl.h>#include <sys/mman.h>#include <sys/select.h>#include <sys/time.h>/* * 封装ioctl 避免信号中断时直接失败 * linux下 ioctl 可能因为 eintr 被中断 * 因此这里做一个小封装,遇到eintr就重试 * */static int xioctl(int fd,int request,void *arg){ int ret;do{ ret = ioctl(fd,request,arg); }while(ret == -1 && errno == EINTR);return ret;}/* * 打开v4l2设备节点 * */int v4l2_open_device(v4l2_ctx_t *ctx){if(ctx == NULL || ctx -> dev_name == NULL) { fprintf(stderr,"invalid ctx or device name\n");return -1; } ctx->fd = open (ctx->dev_name,O_RDWR | O_NONBLOCK, 0);if(ctx->fd < 0) { perror("open video device failed");return -1; }printf("open video successfully : %s\n",ctx->dev_name);return 0;}/* * 查询设备能力 * * 重点检查: * 1.是否是视频采集设备 * 2.是否支持streaming模式 * */int v4l2_query_capability(v4l2_ctx_t *ctx){ struct v4l2_capability cap; memset(&cap,0,sizeof(cap));if(xioctl(ctx->fd,VIDIOC_QUERYCAP,&cap) < 0) { perror("VIDIOC_QUERYCAP failed");return -1; }printf("Driver Name : %s\n", cap.driver);printf("Card Name : %s\n", cap.card);printf("Bus Info : %s\n", cap.bus_info);printf("Version : %u.%u.%u\n", (cap.version >> 16) & 0xFF, (cap.version >> 8) & 0xFF, cap.version & 0xFF);printf("Capabilities : 0x%08x\n", cap.capabilities);if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { fprintf(stderr, "device does not support video capture\n");return -1; }if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "device does not support streaming i/o\n");return -1; }return 0;}/* * 设置采集格式 * 注意: * 1. 应用请求的 width/height/pixfmt 不一定被驱动完全接受 * 2. 驱动可能会返回调整后的参数 * 3. bytesperline 和 sizeimage 一定要打印出来 * * */int v4l2_set_format(v4l2_ctx_t *ctx){ struct v4l2_format fmt; memset(&fmt,0,sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = ctx->width; fmt.fmt.pix.height = ctx->height; fmt.fmt.pix.pixelformat = ctx->pixfmt; fmt.fmt.pix.field = V4L2_FIELD_ANY;if (xioctl(ctx->fd, VIDIOC_S_FMT, &fmt) < 0) { perror("VIDIOC_S_FMT failed");return -1; } /* * 用驱动实际返回的值更新上下文 * 因为驱动可能修正了分辨率和格式 */ ctx->width = fmt.fmt.pix.width; ctx->height = fmt.fmt.pix.height; ctx->pixfmt = fmt.fmt.pix.pixelformat;printf("set format success\n");printf("Actual Width : %u\n", fmt.fmt.pix.width);printf("Actual Height : %u\n", fmt.fmt.pix.height);printf("Pixel Format : %c%c%c%c\n", fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF, (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);printf("Bytes Per Line : %u\n", fmt.fmt.pix.bytesperline);printf("Size Image : %u\n", fmt.fmt.pix.sizeimage);printf("Field : %u\n", fmt.fmt.pix.field);return 0;}/* * 枚举格式功能函数 * * 作用: * 可以在设置格式之前,先打印摄像头可以支持哪些格式 * * */int v4l2_enum_formats(v4l2_ctx_t *ctx){ struct v4l2_fmtdesc fmtdesc; memset(&fmtdesc,0,sizeof(fmtdesc)); fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;printf("Support formats\n");while(xioctl(ctx->fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0) {printf(" [%u] %c%c%c%c %s\n", fmtdesc.index, fmtdesc.pixelformat & 0xFF, (fmtdesc.pixelformat >> 8) & 0xFF, (fmtdesc.pixelformat >> 16) & 0xFF, (fmtdesc.pixelformat >> 24) & 0xFF, fmtdesc.description); fmtdesc.index++; }return 0;}/* * 初始化 mmap 缓冲区 * 流程: * 1.向驱动申请若干buffer * 2.逐个查询buffer 的 offset 和 length * 3.用mmap把每个buffer映射到用户空间 * */int v4l2_init_mmap(v4l2_ctx_t *ctx,uint32_t req_count){ struct v4l2_requestbuffers req; uint32_t i; memset(&req,0,sizeof(req)); req.count = req_count; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP;if(xioctl(ctx->fd,VIDIOC_REQBUFS,&req) < 0) { perror("VIDIOC_REQBUFS failed");return -1; }if (req.count < 2) { fprintf(stderr, "insufficient buffer memory, got %u buffers\n", req.count);return -1; } ctx->buffers = (buffer_t *)calloc(req.count, sizeof(buffer_t));if (ctx->buffers == NULL) { fprintf(stderr, "calloc buffers failed\n");return -1; } ctx->buffer_count = req.count;for (i = 0; i < ctx->buffer_count; i++) { struct v4l2_buffer buf; memset(&buf,0,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i;if(xioctl(ctx->fd,VIDIOC_QUERYBUF,&buf) < 0) { perror("VIDIOC_QUERYBUF failed");return -1; } ctx->buffers[i].length = buf.length; ctx->buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, ctx->fd, buf.m.offset);if(ctx->buffers[i].start == MAP_FAILED ) { perror("mmap failed\n");return -1; }printf("buffer[%u]: start=%p length=%u offset=%u\n", i, ctx->buffers[i].start, ctx->buffers[i].length, buf.m.offset); }return 0;}/* * 启动视频流 * * 正确流程是: * 1. 先把全部 buffer QBUF 入队 * 2. 再调用 STREAMON * * 原因: * 硬件一旦开始采集,驱动必须立刻有空闲 buffer 可写入。 * * * */int v4l2_start_stream(v4l2_ctx_t *ctx){ uint32_t i; enum v4l2_buf_type type;for (i = 0; i < ctx->buffer_count; i++) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i;if (xioctl(ctx->fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF failed");return -1; } }type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (xioctl(ctx->fd, VIDIOC_STREAMON, &type) < 0) { perror("VIDIOC_STREAMON failed");return -1; }printf("stream on success\n");return 0;}/* * 保存一帧原始数据到文件 * * 注意: * 保存的是原始像素数据,不是 jpg/png。 * 例如 YUYV/NV12 保存下来通常是 .yuv 文件。 * 可以用 ffplay 或者 ffmpeg 再验证。 */static int save_frame_to_file(const char *filename, const void *data, uint32_t size){ FILE *fp = fopen(filename, "wb");if (fp == NULL) { perror("fopen frame file failed");return -1; }if (fwrite(data, 1, size, fp) != size) { perror("fwrite frame failed"); fclose(fp);return -1; } fclose(fp);return 0;}/* * 抓取若干帧 * * 使用 select 等待设备就绪,然后 DQBUF 取帧。 * * 为什么这里不用死循环疯狂 DQBUF? * 因为设备可能暂时没有帧准备好,select/poll 更合理。 */int v4l2_capture_frames(v4l2_ctx_t *ctx, uint32_t frame_count, const char *save_prefix){ uint32_t captured = 0;while (captured < frame_count) { fd_set fds; struct timeval tv; int ret; FD_ZERO(&fds); FD_SET(ctx->fd, &fds); /* * 设置等待超时,避免永久卡死 */ tv.tv_sec = 2; tv.tv_usec = 0; ret = select(ctx->fd + 1, &fds, NULL, NULL, &tv);if (ret < 0) {if (errno == EINTR)continue; perror("select failed");return -1; }elseif (ret == 0) { fprintf(stderr, "select timeout\n");return -1; }if (FD_ISSET(ctx->fd, &fds)) { struct v4l2_buffer buf; char filename[256]; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP;if (xioctl(ctx->fd, VIDIOC_DQBUF, &buf) < 0) { /* * 对非阻塞 fd 来说,如果当前没有可取的 buffer, * 可能返回 EAGAIN,这里直接继续即可。 */if (errno == EAGAIN)continue; perror("VIDIOC_DQBUF failed");return -1; }printf("frame %u: index=%u bytesused=%u sequence=%u timestamp=%ld.%06ld\n", captured, buf.index, buf.bytesused, buf.sequence, buf.timestamp.tv_sec, buf.timestamp.tv_usec); /* * 为了演示,这里把每一帧都单独保存。 * 实际项目中通常不会每帧都落盘,因为会严重影响性能。 */ snprintf(filename, sizeof(filename), "%s_%03u.yuv", save_prefix, captured);if (save_frame_to_file(filename, ctx->buffers[buf.index].start, buf.bytesused) < 0) {return -1; } /* * 处理完这块 buffer 后,一定要重新 QBUF 入队。 * 否则驱动可用 buffer 会越来越少,最终采集停住。 */if (xioctl(ctx->fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF requeue failed");return -1; } captured++; } }return 0;}/* * 停止视频流 */int v4l2_stop_stream(v4l2_ctx_t *ctx){ enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (xioctl(ctx->fd, VIDIOC_STREAMOFF, &type) < 0) { perror("VIDIOC_STREAMOFF failed");return -1; }printf("stream off success\n");return 0;}/* * 释放 mmap 资源 */void v4l2_uninit(v4l2_ctx_t *ctx){ uint32_t i;if (ctx == NULL || ctx->buffers == NULL)return;for (i = 0; i < ctx->buffer_count; i++) {if (ctx->buffers[i].start != NULL && ctx->buffers[i].start != MAP_FAILED) { munmap(ctx->buffers[i].start, ctx->buffers[i].length); } } free(ctx->buffers); ctx->buffers = NULL; ctx->buffer_count = 0;}/* * 关闭设备节点 */void v4l2_close_device(v4l2_ctx_t *ctx){if (ctx && ctx->fd >= 0) { close(ctx->fd); ctx->fd = -1; }}MakefileCC = arm-openwrt-linux-gccCFLAGS = -Wall -Wextra -O2TARGET = v4l2_demoSRCS = main.c v4l2_capture.cOBJS = $(SRCS:.c=.o)all: $(TARGET)$(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^%.o: %.c $(CC) $(CFLAGS) -c $< -o $@clean: rm -f $(TARGET) $(OBJS) frame_*.yuv1tammy@ubuntu:~/camera/v4l2_demo01$ makearm-openwrt-linux-gcc -Wall -Wextra -O2 -c main.c -o main.oarm-openwrt-linux-gcc -Wall -Wextra -O2 -c v4l2_capture.c -o v4l2_capture.oarm-openwrt-linux-gcc -Wall -Wextra -O2 -o v4l2_demo main.o v4l2_capture.otammy@ubuntu:~/camera/v4l2_demo01$ lsmain.c main.o Makefile v4l2_capture.c v4l2_capture.h v4l2_capture.o v4l2_demotammy@ubuntu:~/camera/v4l2_demo01$ adb push v4l2_demo /rootv4l2_demo: 1 file pushed. 1.1 MB/s (32124 bytes in 0.028s)root@Linux:~# ./v4l2_demo========== V4L2 Demo Start ==========device : /dev/video0width : 640height : 480pixfmt : YUYVframe_count : 5open video successfully : /dev/video0Driver Name : uvcvideoCard Name : HD Camera: HD CameraBus Info : usb-sunxi-ehci-1.1Version : 5.4.61Capabilities : 0x84a00001Support formats [0] MJPG Motion-JPEG [1] YUYV YUYV 4:2:2set format successActual Width : 640Actual Height : 480Pixel Format : YUYVBytes Per Line : 1280Size Image : 614400Field : 1buffer[0]: start=0xb6e92000 length=614400 offset=0buffer[1]: start=0xb6dfc000 length=614400 offset=614400buffer[2]: start=0xb6d66000 length=614400 offset=1228800buffer[3]: start=0xb6cd0000 length=614400 offset=1843200stream on successframe 0: index=0 bytesused=614400 sequence=0 timestamp=48.244409frame 1: index=1 bytesused=614400 sequence=2 timestamp=48.328476frame 2: index=2 bytesused=614400 sequence=3 timestamp=48.364455frame 3: index=3 bytesused=614400 sequence=4 timestamp=48.396453frame 4: index=0 bytesused=614400 sequence=5 timestamp=48.428473stream off success========== V4L2 Demo Success ==========采集到的图像文件:
root@Linux:~# lsframe_001.yuv frame_003.yuv frame_000.yuv frame_002.yuv frame_004.yuv v4l2_demo