V4L2
定义
linux视频设备驱动,V4L2(Video for linux2)是Linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写
接口
V4L2规范中不仅定义了通用API元素(Common API Elements),图像的格式(Image Formats),输入/输出方法(Input/Output),还定义了Linux内核驱动处理视频信息的一系列接口(Interfaces),这些接口主要有:
视频采集接口——Video Capture Interface;
视频输出接口—— Video Output Interface;
视频覆盖/预览接口——Video Overlay Interface;
视频输出覆盖接口——Video Output Overlay Interface;
编解码接口——Codec Interface
v4l12结构体介绍
常用的结构体在内核目录include/linux/videodev2.h中定义,要加上头文件<linux/videodev2.h>
struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP
struct v4l2_input //视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS
struct v4l2_buffer //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_crop //视频信号矩形边框
v4l2_std_id //视频制式
内容:
1. 查看视频设备功能的结构体
struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP
{
u8 driver[16]; // 驱动名字
u8 card[32]; // 设备名字
u8 bus_info[32]; // 设备在系统中的位置
u32 version; // 驱动版本号
u32 capabilities; // 设备支持的操作
u32 reserved[4]; // 保留字段
};
// 其中capability代表设备支持的操作模式,常见的值有V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示的是一个视频捕捉设备并且具有数据流控制模式;另外driver域需要和struct video_device中的name匹配
// V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
2. 查询并显示支持的所有视频格式
struct v4l2_fmtdesc
{
u32 index;// 要查询的格式序号,应用程序设置
enum v4l2_buf_type type; // 帧类型,应用程序设置
u32 flags;// 是否为压缩格式
u8 description[32]; // 格式名称
u32 pixelformat;// 格式
u32 reserved[4]; // 保留
};
3. 帧的格式结构体,数据流类型结构体
struct v4l2_format //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
{
enum v4l2_buf_type type; // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
...
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
struct v4l2_pix_format {
__u32 width; // 宽,必须是16的倍数
__u32 height; // 高,必须是16的倍数
__u32 pixelformat; // 采样类型,如YUV 4:2:2
enum v4l2_field field; // 采样区域,如隔行采样
__u32 bytesperline; // 一行图像占用的字节数
__u32 sizeimage; // 图像占用的总字节数
enum v4l2_colorspace colorspace; // 指定设备的颜色空间
__u32 priv; /* private data, depends on pixelformat */
};
// 常见的捕获模式为 V4L2_BUF_TYPE_VIDEO_CAPTURE 即视频捕捉模式,在此模式下 fmt 联合体采用域 v4l2_pix_format:其中 width 为视频的宽、height 为视频的高、pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 为一行图像占用的字节数、sizeimage 则为图像占用的总字节数、colorspace 指定设备的颜色空间。
4. 申请帧缓存结构体
struct v4l2_requestbuffers { //申请帧缓冲,对应命令VIDIOC_REQBUFS
__u32 count; // 指定根据图像占用空间大小申请的缓存区个数
enum v4l2_buf_type type; // 视频捕获模式
enum v4l2_memory memory; // 内存区使用方式
__u32 reserved[2];
};
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
};
// struct v4l2_requestbuffers,VIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求驱动申请一片连续的内存用于缓存视频信息。
5. 驱动中的一帧图像缓存结构体
struct v4l2_buffer { //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
__u32 index; // 缓存编号
enum v4l2_buf_type type; // 视频捕获模式
__u32 bytesused; // 缓存已使用空间大小
__u32 flags; // 缓存当前状态,(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)
enum v4l2_field field;
struct timeval timestamp; // 时间戳
struct v4l2_timecode timecode;
__u32 sequence; // 缓存序号
/* memory location */
enum v4l2_memory memory;
union {
__u32 offset; // 当前缓存与内存区起始地址的偏移
unsigned long userptr;
} m;
__u32 length; // 缓存大小
__u32 input;
__u32 reserved; // 一般用于传递物理地址值
};
// 另外 VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer 与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。
常用的IOCTL接口
也在include/linux/videodev2.h中定义
打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。
在Linux设备中,一般使用ioctl函数来对设备的I/O通道进行管理:
extern int ioctl(int _fd, unsigned long int _request, ...) _THROW;
_fd:设备的ID,例如刚才使用open函数打开视频通道后返回的cameraFd;
_request: 具体的命令标志符
在进行V4L2开发中,一般会用到以下的命令标志符:
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_TRY_FMT: 验证当前驱动的显示格式
VIDIOC_S_FMT: 设置当前驱动的频捕获格式
VIDIOC_G_FMT: 读取当前驱动的频捕获格式
VIDIOC_CROPCAP: 查询驱动的修剪能力
VIDIOC_S_CROP: 设置视频信号的边框
VIDIOC_G_CROP: 设置视频信号的边框
VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QBUF: 把数据放入缓存队列
VIDIOC_DQBUF: 把数据从缓存中读取出来
VIDIOC_STREAMON:开始视频显示函数
VIDIOC_STREAMOFF:结束视频显示函数
这些IO调用,有些是必须的,有些是可选择的
操作流程
1. 打开设备文件
a.查看设备名
ls /dev/video*
b.相关函数
#include<fcntl.h>
int open(const char* device_name, int flags);
c.示例
int fd_video = open("/dev/video0", O_RDWR) // 将O_RDWR 换为 O_RDWR | O_NONBLOCK ,转化为非阻塞模式
// 应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。
if (fd_video < 0)
{
perror("video0 open fail");
return fd_video;
}
2. 查看设备功能
看看设备具有什么功能,比如是否具有视频输入或者音频输入输出等。VIDIOC_QUERYCAP
a.相关函数
int ioctl(int fd, int request, struct v4l2_capability *argp);
b.示例
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap)); // 初始化
// 查询驱动功能
if (ioctl(fd_video, VIDIOC_QUERYCAP, &cap) < 0){
perror("requre VIDIOC_QUERYCAP fialed! \n");
return -1;
}
//获取成功,检查是否有视频捕获功能
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
perror("device is no video capture device\n");
return -1;
}
// 检查是否具有数据流控制模式
if(!(cap.capabilities & V4L2_CAP_STREAMING)){
perror("device does not support streaming i/o\n");
return -1;
}
// 打印信息
cout << "Driver Name: " << cap.driver << endl; // 驱动名字
cout << "Card Name: " << cap.card << endl; // 设备名字
cout << "Bus info: " << cap.bus_info << endl; // 设备在系统中的位置
printf("Driver Version:%u.%u.%u\n",(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF); //驱动版本号, %u是十进制无符号整数
3. 显示设备支持的所有视频格式
// 4.查询设备支持哪种视频格式
struct v4l2_fmtdesc fmt; //查询设备格式所用结构体
memset(&(fmt), 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.index = 0;
printf("显示当前驱动支持的视频格式format:\n");
// VIDIOC_ENUM_FMT: 获取当前驱动支持的视频格式
while(ioctl(fd_video, VIDIOC_ENUM_FMT, &fmt) != -1)
{
printf("\t%d.%s\n",fmt.index+1,fmt.description); // index是查询的格式序号, description是格式名称
fmt.index++;
}
4. 检测视频支持的制式(不用用到)
v4l2_std_id std;
do {
// VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC
ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
NTSC和PAL属于全球两大主要的电视广播制式 , 但是由于系统投射颜色影像的频率而有所不同 .
NTSC即正交平衡调幅制。PAL为逐行倒像正交平衡调幅制。
PAL电视标准
PAL电视标准,每秒25帧,电视扫描线为625线,奇场在前,偶场在后,标准的数字化PAL电视标准分辨率为720*576, 24比特的色彩位深,画面的宽高比为4:3, PAL电视标准用于中国、欧洲等国家和地区。
NTSC电视标准
NTSC电视标准,每秒29.97帧(简化为30帧),电视扫描线为525线,偶场在前,奇场在后,标准的数字化NTSC电视标准分辨率为720*486, 24比特的色彩位深,画面的宽高比为4:3。NTSC电视标准用于美、日等国家和地区。
5. 设置视频的制式和帧格式
制式包括PAL,NTSC, 帧的格式包括宽度和高度等。
v4l2_format 结构体用来设置摄像头的视频制式、帧格式等,在设置这个参数时应先填好 v4l2_format 的各个域,如 type(传输流类型), fmt.pix.width(宽),fmt.pix.heigth(高),fmt.pix.field(采样区域,如隔行采样),fmt.pix.pixelformat(采样类型,如 YUV4:2:2),然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式, 通过VIDIOC_G_FMT 操作命令读取当前驱动的频捕获格式
// 5.设置视频帧的格式
struct v4l2_format s_fmt;
memset(&(s_fmt), 0, sizeof(s_fmt));
s_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
s_fmt.fmt.pix.width = 1440;
s_fmt.fmt.pix.height = 800;
s_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 采样类型 V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUYV,
s_fmt.fmt.pix.field=V4L2_FIELD_ANY; // 采样区域,如隔行采样。系统自动设置: 帧属性
// 设置图片格式,VIDIOC_S_FMT:设置当前驱动的频捕获格式
if(ioctl(fd_video,VIDIOC_S_FMT,&s_fmt) != 0)
{
printf("set format error\n");
return -1;
}
// 得到图片格式
if(ioctl(fd_video, VIDIOC_G_FMT, &s_fmt) == -1){
perror("set format failed!");
return -1;
}
cout << "实际得到的图片格式如下:"<< endl;
// 判断摄像头是否支持YUV格式图像输出
if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV)
{
printf("当前摄像头支持YUV格式图像输出!\n");
}
else
{
printf("当前摄像头不支持YUV格式图像输出!\n");
return -3;
}
printf("fmt.type:\t\t%d\n",s_fmt.type);
// %c表示输出单个字符
printf("pix.pixelformat:\t%c%c%c%c\n", \
s_fmt.fmt.pix.pixelformat & 0xFF,\
(s_fmt.fmt.pix.pixelformat >> 8) & 0xFF, \
(s_fmt.fmt.pix.pixelformat >> 16) & 0xFF,\
(s_fmt.fmt.pix.pixelformat >> 24) & 0xFF);
printf("pix.width:\t\t%d\n",s_fmt.fmt.pix.width);
printf("pix.height:\t\t%d\n",s_fmt.fmt.pix.height);
printf("pix.field:\t\t%d\n",s_fmt.fmt.pix.field);
**注意:**如果该视频设备驱动不支持你所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该视频设备所支持的图像格式,所以在程序设计中,设定完所有的视频格式后,要获取实际的视频格式,要重新读取struct v4l2_format结构体变量。
// 设置摄像头采集的帧率
struct v4l2_streamparm streamparm;
streamparm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示视频捕获设备*/
streamparm.parm.capture.timeperframe.numerator=1;
streamparm.parm.capture.timeperframe.denominator=30;
printf("设置当前摄像头采集帧率: %d秒%d帧\n",streamparm.parm.capture.timeperframe.numerator,streamparm.parm.capture.timeperframe.denominator);
if(ioctl(uvc_video_fd,VIDIOC_S_PARM,&streamparm)) /*设置摄像头的帧率*/
{
printf("设置摄像头采集的帧率失败!\n");
return -3;
}
if(ioctl(uvc_video_fd,VIDIOC_S_PARM,&streamparm)) /*获取摄像头的帧率*/
{
printf("获取摄像头采集的帧率失败!\n");
return -3;
}
printf("当前摄像头实际采集帧率: %d秒%d帧\n",streamparm.parm.capture.timeperframe.numerator,streamparm.parm.capture.timeperframe.denominator);
6. 向驱动申请帧缓冲区
一般不超过5个,通过VIDIOC_REQBUFS控制命令申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。
//此处作用为申请缓冲区
struct v4l2_requestbuffers req;
memset(&(req), 0, sizeof(req));
req.count=4; //申请一个拥有四个缓冲帧的缓冲区
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory=V4L2_MEMORY_MMAP;
// VIDIOC_REQBUFS:分配内存, 申请一片连续的内存
ioctl(fd_video,VIDIOC_REQBUFS,&req);
printf("摄像头缓冲区申请的数量: %d\n",req.count);
// 控制命令VIDIOC_REQBUFS 功能: 请求V4L2驱动分配视频缓冲区(申请V4L2视频驱动分配内存),V4L2是视频设备的驱动层,位于内核空间,所以通过VIDIOC_REQBUFS控制命令字申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。
7. 申请物理内存
应用程序和设备有三种交换数据的方法,直接read/write、内存映射(memory mapping)和用户指针。
将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。
使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列
// 定义一个结构体来映射每个缓冲帧
struct buffer
{
void* start;
unsigned int length;
};
//申请4个struct buffer空间
buffers = (struct buffer*)calloc (req.count, sizeof (struct buffer));
if (!buffers)
{
perror ("Out of memory");
exit (EXIT_FAILURE);
}
for (unsigned int i = 0; i < req.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 (-1 == ioctl (fd_video, VIDIOC_QUERYBUF, &buf))
exit(-1);
// 转化成相应地址
buffers[i].length = buf.length;
buffers[i].start = mmap (NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_video, buf.m.offset);
// 失败时,mmap()返回MAP_FAILED[其值为(void *)-1]
if (MAP_FAILED == buffers[i].start)
exit(-1);
}
enum v4l2_buf_type type;
int i;
for (i = 0; i < 4; ++i)
{
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
// VIDIOC_QBUF: 把数据放入缓存队列
ioctl (fd_video, VIDIOC_QBUF, &buf);
}
8. 开始视频的采集
应用程序再通过内存映射方法(mmap),将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据。
(1)将帧缓冲区在视频输入队列排队,并启动视频采集
在驱动程序处理视频的过程中,定义了两个队列:视频采集输入队列(incoming queues)和视频采集输出队列(outgoing queues),前者是等待驱动存放视频数据的队列,后者是驱动程序已经放入了视频数据的队列。如图2所示。
应用程序需要将上述帧缓冲区在视频采集输入队列排队(VIDIOC_QBUF),然后可启动视频采集。
(2)循环往复,采集连续的视频数据
启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//开始捕获图像
ioctl (fd_video, VIDIOC_STREAMON, &type);
9. 出队列以取得已采集数据的帧缓冲,取得原始采集数据
VIDIOC_DQBUF: 把数据从缓存中读取出来.
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
//取出图像数据,把数据从缓存中读取出来
ioctl(fd_video, VIDIOC_DQBUF, &buf);
10. 将刚刚处理完的缓冲重新入队列,这样可以循环采集
ioctl (fd_video,VIDIOC_QBUF,&buf);
11. 停止视频的采集,解除映射, 关闭设备
printf("success\n");
int i;
for (i = 0; i < 4; i++)
{
munmap(buffers[i].start, buffers[i].length); // 解除映射
}
free(buffers); // 释放内存缓冲区
munmap(pfb, 1440*800*4); // 解除显示器设备的内存映射
close(fd_video); // 关闭摄像头
close(fd_fb); // 关闭显示器
流程描述
(1) 打开视频设备文件,int fd = open("/dev/video0",O_RDWR);
(2) 查询视频设备的能力,比如是否具有视频输入,或者音频输入输出等。ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)
(3) 设置视频采集的参数
- 设置视频的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l, VIDIOC_S_STD, &std_id)
- 设置视频图像的采集窗口的大小,使用ioctl(fd_v4l, VIDIOC_S_CROP, &crop)
- 设置视频帧格式,包括帧的点阵格式,宽度和高度等,使用ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)
- 设置视频的帧率,使用ioctl(fd_v4l, VIDIOC_S_PARM, &parm)
- 设置视频的旋转方式,使用ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)
(4) 向驱动申请视频流数据的帧缓冲区
请求/申请若干个帧缓冲区,一般为不少于3个,使用ioctl(fd_v4l, VIDIOC_REQBUFS, &req)
查询帧缓冲区在内核空间中的长度和偏移量 ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)
(5) 应用程序通过**内存映射,将帧缓冲区的地址映射到用户空间,**这样就可以直接操作采集到的帧了,而不必去复制。
buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);
(6) 将申请到的帧缓冲全部放入视频采集输出队列,以便存放采集的数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
(7) 开始视频流数据的采集。 ioctl (fd_v4l, VIDIOC_STREAMON, &type)
(8) 驱动将采集到的一帧视频数据存入输入队列第一个帧缓冲区,存完后将该帧缓冲区移至视频采集输出队列。
(9) 用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区。ioctl (fd_v4l, VIDIOC_DQBUF, &buf) ,应用程序处理该帧缓冲区的原始视频数据。
(10) 处理完后,应用程序的将该帧缓冲区重新排入输入队列,这样便可以循环采集数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
重复上述步骤8到10,直到停止采集数据。
(11)停止视频的采集。ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)
(12)释放申请的视频帧缓冲区mummap,关闭视频设备文件close(fd_v4l)。
以上的程序流程,包含了视频设备采集连续的视频数据的逻辑关系。而在实际运用中,往往还要加入对视频数据进行处理(如压缩编码)的工作,否则,视频流数据量相当大,需要很大的存储空间和传输带宽。