目录
知识储备:
视频采集方式:
处理采集数据:
相关结构体:
对于设备的操作步骤:
V4L2较V4L有较大的改动,并已成为2.6的标准接口,函盖video\dvb\FM...,多数驱动都在向V4l2迁移。更好地了解V4L2先从应用入手,然后再深入到内核中结合物理设备/接口的规范实现相应的驱动。本文先就V4L2在视频捕捉或camera方面的应用框架。
V4L2采用流水线的方式,操作更简单直观,基本遵循打开视频设备、设置格式、处理数据、关闭设备,更多的具体操作通过ioctl函数来实现。
知识储备:
V4L2视频编程本质:IO操作
摄像头相关头文件:/usr/include/linux/videodev2.h
设备文件不能被用户创建,用户也不能直接访问内核的代码和数据
=====================================================================
V4L2使用V4L2_MEMORY_USERPTR和V4L2_MEMORY_MMAP的区别:
视频应用可以通过两种方式从V4L2驱动申请buffer
1. USERPTR, 顾名思义是用户空间指针的意思,应用层负责分配需要的内存空间,然后以指针的形式传递给V4L2驱动层,V4L2驱动会把capture的内容保存到指针所指的空间(层级切换,会慢不少)
一般来说,应用层需要确保这个内存空间物理上是连续的(IPU处理单元的需求),在android系统可以通过PMEM驱动来分配大块的连续物理内存。应用层在不需要的时候要负责释放申请的PMEM内存。
2. MMAP方式,内存映射模式,应用调用VIDIOC_REQBUFS ioctl分配设备buffers,参数标识需要的数目和类型。这个ioctl也可以用来改变buffers的数据以及释放分配的内存,当然这个内存空间一般也是连续的。在应用空间能够访问这些物理地址之前,必须调用mmap函数把这些物理空间映射为用户虚拟地址空间。
虚拟地址空间是通过munmap函数释放的; 而物理内存的释放是通过VIDIOC_REQBUFS来实现的(设置参数buf count为(0)),物理内存的释放是实现特定的,mx51 v4l2是在关闭设备时进行释放的。
所以二者都是申请连续的物理内存,只是申请和释放的方式不同V4L2使用V4L2_MEMORY_USERPTR和V4L2_MEMORY_MMAP的区别 - Lxk- - 博客园
====================================================================
视频采集方式:
操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。
一共有三种视频采集方式:使用read、write方式;内存映射方式和用户指针模式。
read、write方式,在用户空间和内核空间不断拷贝数据,占用了大量用户内存空间,效率不高。
内存映射方式:把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。
用户指针模式:内存片段由应用程序自己分配。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。
====================================================================
处理采集数据:
V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF.
====================================================================
相关结构体:
struct v4l2_fmtdesc结构体:用于获取摄像头支持的视频格式
- struct v4l2_fmtdesc {
- __u32 index; /* Format number编号*/
- __u32 type; /* enum v4l2_buf_type */
- __u32 flags;
- __u8 description[32]; /* Description string */
- __u32 pixelformat; /* Format fourcc */
- __u32 reserved[4];
};
struct v4l2_format结构体:用于设置当前摄像头的采集格式和大小
struct v4l2_format{
enum v4l2_buf_type type; // 数据流类型,固定V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
__u8 raw_data[200];
} fmt;
};struct v4l2_pix_format结构体:是struct v4l2_format结构体的子结构体
struct v4l2_pix_format{
__u32 width; // 宽,必须是16的倍数
__u32 height; // 高,必须是16的倍数
__u32 pixelformat; // 视频数据存储类型,例如是YUV4:2:2还是RGB
enum v4l2_field field; /* enum v4l2_field */
__u32 bytesperline; //对于填充,如果未使用,则为0
__u32 sizeimage;
enum v4l2_colorspace colorspace; /* enum v4l2_colorspace */
__u32 priv; //私有数据,依赖于pixelformat
};struct v4l2_requestbuffers结构体:用于在内核分配空间
struct v4l2_requestbuffers{
__u32 count; // 缓存数量,也就是说在缓存队列里保持多少张照片
enum v4l2_buf_type type; // 数据流类型,固定V4L2_BUF_TYPE_VIDEO_CAPTURE
enum v4l2_memory memory; // V4L2_MEMORY_MMAP(内存映射方式)或 V4L2_MEMORY_USERPTR(用户空间指针方式)
__u32 reserved[2];
};
=====================================================================
int ioctl(int fd,unsigned long int request,...);
常用的命令标志符:
- VIDIOC_REQBUFS:分配内存
- VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
- VIDIOC_QUERYCAP:查询驱动功能
- VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
- VIDIOC_S_FMT:设置当前驱动的频捕获格式
- VIDIOC_G_FMT:读取当前驱动的频捕获格式
- VIDIOC_TRY_FMT:验证当前驱动的显示格式
- VIDIOC_CROPCAP:查询驱动的修剪能力
- VIDIOC_S_CROP:设置视频信号的边框
- VIDIOC_G_CROP:读取视频信号的边框
- VIDIOC_QBUF:把数据从缓存中读取出来
- VIDIOC_DQBUF:把数据放回缓存队列
- VIDIOC_STREAMON:开始视频显示函数
- VIDIOC_STREAMOFF:结束视频显示函数
- VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。
====================================================================
对于设备的操作步骤:
①打开设备(阻塞或者非阻塞,驱动会将缓存(DQBUFF)里的东西返回给应用程序)②获取设备支持的视频格式(亚洲一般使用PAL(720*576)制式摄像头、欧洲NTSC(720*480)制式摄像头,使用VIDIOC_QUERYSTD来检测)
③根据获得的视频格式,设置当前摄像头的采集格式和大小(liunx编程中ioctl函数)
④设置视频捕获格式(设置fmt.fmt.pix.pixelformat为YUYV还是V4L2_PIX_FMT_MJPEG)
⑤在内核分配内存空间(设置缓存数量(count)和存储模式(内存映射、用户空间指针))
⑥获取并记录缓存的物理空间(使用VIDIOC_REQBUFS,获取count个缓存数量,其次调用VIDIOC_QUERYBUF获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列(这是用到内存映射、优势是不用频繁转换到用户或者内核层,更快速))
⑦视频采集(选择read、write方式,内存映射方式,用户指针模式 这3种模式之一)
⑧处理采集数据(V4L2的数据缓存采用FIFO方式,将采集到的视频数据缓存送出后再重新采集一张视频数据,需要用到VIDIOC_DQBUF和VIDIOC_QBUF)
⑨关闭视频设备(close、fclose或者mmap(使用mmap后还需使用munmap方法))文章来源:https://www.toymoban.com/news/detail-407664.html
#include <stdio.h>
#include <linux/videodev2.h> //摄像头相关的头文件
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
//定义用户缓冲区,定义一个结构体、用来存储 内核空间映射之后的首地址和空间大小
typedef struct VideoBuffer{
void *start;
size_t length;
}VideoBuffer;
//定义结构体数组,用来代表一个数组元素映射的缓冲区
struct VideoBuffer buffers[8];
//calloc:在堆区开辟8个sizeof(*buffers)这么大的连续空间
//VideoBuffer *buffers = calloc(fmt3.count,sizeof(*buffers));
//定义一个全局缓冲区
struct v4l2_buffer buf;
int camera_init(const char *dev,int *ismjpeg)
{
//1、以阻塞模式打开摄像头
int fd = open(dev,O_RDWR,0);
if(fd < 0){
perror("open_error");
return -1;
}
//2、获取摄像头支持的视频格式
char fmtBuf[32] = {0};
struct v4l2_fmtdesc fmt1;
memset(&fmt1,0,sizeof(fmt1));
fmt1.index = 0;
//V4L2_BUF_TYPE_VBI_CAPTURE:数据流类型,固定一直都是
fmt1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//VIDIOC_ENUM_FMT:获取当前驱动的频捕获格式
while( ioctl(fd,VIDIOC_ENUM_FMT,&fmt1) != -1){
printf("fmt1.index:%d\n",fmt1.index++);
printf("format:%s\n",fmt1.description);
strcat(fmtBuf,fmt1.description);
}
//3、根据获得的视频格式,设置当前摄像头的采集格式和大小
struct v4l2_format fmt2;
fmt2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt2.fmt.pix.width = 640;
fmt2.fmt.pix.height = 480;
//设置视频捕获格式为JPG
if(strstr(fmtBuf,"JPEG")!=NULL){
fmt2.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
*ismjpeg = 1;
}
else{
//如果是YUYV的视频格式,后续还需要转码, YUYV->RGB24->JPEG
fmt2.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
*ismjpeg = 0;
}
if( ioctl(fd,VIDIOC_S_FMT,&fmt2)==-1 ){
perror("set fmt");
return -1;
}
//4、在内核分配内存空间
struct v4l2_requestbuffers fmt3;
fmt3.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//设置缓存队列里保持8张照片
fmt3.count = 8;
fmt3.memory = V4L2_MEMORY_MMAP;
//VIDIOC_REQBUFS:分配内存
if(ioctl(fd,VIDIOC_REQBUFS,&fmt3)==-1){
perror("req fmt");
return -1;
}
//5、获取并记录缓存的物理空间
// struct v4l2_buffer buf;
for(int i=0;i<fmt3.count;++i){
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
//读取内核中的某个index编号的缓冲区
if( ioctl(fd,VIDIOC_QUERYBUF,&buf)==-1 ){
perror("VIDIOC_QUERYBUF");
return -1;
}
//将内核中读取的缓存映射到 用户空间
buffers[i].length = buf.length; //先保存编号为i的内核空间的大小到用户空间
//内存映射
buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,MAP_SHARED,fd, buf.m.offset);
if(buffers[i].start == MAP_FAILED){ //判断是否映射失败
perror("mmap");
return -1;
}
//将读取出来的缓存重新放入缓存队列
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
perror("mmap qbuf");
return -1;
}
}
return fd;
}
int camera_start(int fd)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//开始视频采集,必须指定摄像头拍摄的图片存储的缓存区的数据类型
if(ioctl(fd,VIDIOC_STREAMON,&type)==-1 ){
perror("start no");
return -1;
}
return 0;
}
int camera_eqbuf(int fd,void **jpeg,int *size,int *index)
{
// struct v4l2_buffer buf;//局部放不回去,设置为全局的
memset(&buf,0,sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0; //指定要出队的 内核缓存编号
//将编号为 0 的内核缓存出队,缓存数据映射到了 用户缓存区buffers
//如果内核缓存没有图片数据,那么出队将会永久阻塞,为了防止永久阻塞,建议使用IO多路复用来出队
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1){
perror("DQBUF");
return -1;
}
//由于缓存中的数据都已经映射到了 用户空间,因此可以将 用户缓存中的数据 读取出来
//*size = buf[0].length;
*size = buf.bytesused; //保存一张图片的实际大小
*jpeg = buffers[0].start; //保存一张图片数据的首地址
*index = buf.index; //保存当前图片缓存的索引
return 0;
}
int camera_ebuf(int fd,int index)
{
buf.index = index;
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
perror("qbuf");
return -1;
}
return 0;
}
int camera_stop(int fd)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//开始视频采集,必须指定摄像头拍摄的图片存储的缓存区的数据类型
if(ioctl(fd,VIDIOC_STREAMON,&type)==-1 ){
perror("start no");
return -1;
}
return 0;
}
int camera_quit(int fd)
{
close(fd);
return 0;
}
int main(int argc, char *argv[])
{
int ismjpeg = 0;
int index = -1,size;
char *yuv,*jpeg;//野指针,取一个地址,让野指针去存储图片首地址,会出问题
int cameraFd = camera_init("/dev/video0",&ismjpeg);
if(cameraFd<0){
return -1;
}
if( camera_start(cameraFd)==-1 ){
return -1;
}
if(ismjpeg == 1){
printf("capture format:MJPEG\n");
jpeg = (char *)malloc(640*480);
}else{
printf("capture format:YUYV\n");
}
for(int i=0;i<8;i++){
//出队,得到一张图片的 首地址和大小,以及该图片的 索引编号
if(camera_eqbuf(cameraFd,(void **)&yuv,&size,&index)==-1){
break;
}
//入队
if(camera_ebuf(cameraFd,index)==-1 ){
break;
}
}
while(1){
//出队,得到一张图片的 首地址和大小,以及该图片的 索引编号
if(camera_eqbuf(cameraFd,(void **)&yuv,&size,&index)==-1){
break;
}
printf("size:%d\n",size);
memset(jpeg,0,sizeof(jpeg));
memcpy(jpeg,yuv,size);
int fd1=open("1.jpg",O_WRONLY|O_CREAT,0644);
int count = 0;
while(count<size){
int ret = write(fd1,jpeg+count,size-count);
if(ret<size){
printf("----数据太少----\n");
}
count += ret;
}
close(fd1);
usleep(5000);
//入队
if(camera_ebuf(cameraFd,index)==-1 ){
break;
}
}
camera_stop(cameraFd);
camera_quit(cameraFd);
return 0;
}
视频格式如果是YUYV,则需要转码:YUYV=>RGB24=>JPEG文章来源地址https://www.toymoban.com/news/detail-407664.html
#include "convert.h" #define ROUND_0_255(v) ((v) < 0 ? 0 : ((v) > 255 ? 255 : (v))) typedef struct { struct jpeg_destination_mgr pub; JOCTET *buffer; unsigned char *outbuffer; int outbuffer_size; unsigned char *outbuffer_cursor; int *written; } jpeg_dest_mgr, *jpeg_dest_mgr_ptr; struct jpeg_mgr_info { unsigned long written; JSAMPROW row_pointer[1]; struct jpeg_error_mgr jerr; struct jpeg_compress_struct cinfo; }; static struct jpeg_mgr_info jinfo; static short radj[] = { -175, -174, -172, -171, -169, -168, -167, -165, -164, -163, -161, -160, -159, -157, -156, -154, -153, -152, -150, -149, -148, -146, -145, -143, -142, -141, -139, -138, -137, -135, -134, -132, -131, -130, -128, -127, -126, -124, -123, -121, -120, -119, -117, -116, -115, -113, -112, -111, -109, -108, -106, -105, -104, -102, -101, -100, -98, -97, -95, -94, -93, -91, -90, -89, -87, -86, -84, -83, -82, -80, -79, -78, -76, -75, -74, -72, -71, -69, -68, -67, -65, -64, -63, -61, -60, -58, -57, -56, -54, -53, -52, -50, -49, -47, -46, -45, -43, -42, -41, -39, -38, -37, -35, -34, -32, -31, -30, -28, -27, -26, -24, -23, -21, -20, -19, -17, -16, -15, -13, -12, -10, -9, -8, -6, -5, -4, -2, -1, 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 15, 16, 17, 19, 20, 21, 23, 24, 26, 27, 28, 30, 31, 32, 34, 35, 37, 38, 39, 41, 42, 43, 45, 46, 47, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 63, 64, 65, 67, 68, 69, 71, 72, 74, 75, 76, 78, 79, 80, 82, 83, 84, 86, 87, 89, 90, 91, 93, 94, 95, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 111, 112, 113, 115, 116, 117, 119, 120, 121, 123, 124, 126, 127, 128, 130, 131, 132, 134, 135, 137, 138, 139, 141, 142, 143, 145, 146, 148, 149, 150, 152, 153, 154, 156, 157, 159, 160, 161, 163, 164, 165, 167, 168, 169, 171, 172, 174, }; static short gadj1[] = { -89, -88, -87, -87, -86, -85, -85, -84, -83, -83, -82, -81, -80, -80, -79, -78, -78, -77, -76, -76, -75, -74, -73, -73, -72, -71, -71, -70, -69, -69, -68, -67, -67, -66, -65, -64, -64, -63, -62, -62, -61, -60, -60, -59, -58, -57, -57, -56, -55, -55, -54, -53, -53, -52, -51, -50, -50, -49, -48, -48, -47, -46, -46, -45, -44, -43, -43, -42, -41, -41, -40, -39, -39, -38, -37, -36, -36, -35, -34, -34, -33, -32, -32, -31, -30, -30, -29, -28, -27, -27, -26, -25, -25, -24, -23, -23, -22, -21, -20, -20, -19, -18, -18, -17, -16, -16, -15, -14, -13, -13, -12, -11, -11, -10, -9, -9, -8, -7, -6, -6, -5, -4, -4, -3, -2, -2, -1, 0, 0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13, 14, 15, 16, 16, 17, 18, 18, 19, 20, 20, 21, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 32, 33, 34, 34, 35, 36, 36, 37, 38, 39, 39, 40, 41, 41, 42, 43, 43, 44, 45, 46, 46, 47, 48, 48, 49, 50, 50, 51, 52, 53, 53, 54, 55, 55, 56, 57, 57, 58, 59, 60, 60, 61, 62, 62, 63, 64, 64, 65, 66, 67, 67, 68, 69, 69, 70, 71, 71, 72, 73, 73, 74, 75, 76, 76, 77, 78, 78, 79, 80, 80, 81, 82, 83, 83, 84, 85, 85, 86, 87, 87, 88, }; static short gadj2[] = { -43, -42, -42, -42, -41, -41, -41, -40, -40, -40, -39, -39, -39, -38, -38, -38, -37, -37, -37, -36, -36, -36, -35, -35, -35, -34, -34, -34, -33, -33, -33, -32, -32, -32, -31, -31, -31, -30, -30, -30, -29, -29, -29, -28, -28, -28, -27, -27, -27, -26, -26, -25, -25, -25, -24, -24, -24, -23, -23, -23, -22, -22, -22, -21, -21, -21, -20, -20, -20, -19, -19, -19, -18, -18, -18, -17, -17, -17, -16, -16, -16, -15, -15, -15, -14, -14, -14, -13, -13, -13, -12, -12, -12, -11, -11, -11, -10, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6, -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, }; static short badj[] = { -221, -220, -218, -216, -214, -213, -211, -209, -207, -206, -204, -202, -200, -199, -197, -195, -194, -192, -190, -188, -187, -185, -183, -181, -180, -178, -176, -174, -173, -171, -169, -168, -166, -164, -162, -161, -159, -157, -155, -154, -152, -150, -148, -147, -145, -143, -142, -140, -138, -136, -135, -133, -131, -129, -128, -126, -124, -123, -121, -119, -117, -116, -114, -112, -110, -109, -107, -105, -103, -102, -100, -98, -97, -95, -93, -91, -90, -88, -86, -84, -83, -81, -79, -77, -76, -74, -72, -71, -69, -67, -65, -64, -62, -60, -58, -57, -55, -53, -51, -50, -48, -46, -45, -43, -41, -39, -38, -36, -34, -32, -31, -29, -27, -25, -24, -22, -20, -19, -17, -15, -13, -12, -10, -8, -6, -5, -3, -1, 0, 1, 3, 5, 6, 8, 10, 12, 13, 15, 17, 19, 20, 22, 24, 25, 27, 29, 31, 32, 34, 36, 38, 39, 41, 43, 45, 46, 48, 50, 51, 53, 55, 57, 58, 60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77, 79, 81, 83, 84, 86, 88, 90, 91, 93, 95, 97, 98, 100, 102, 103, 105, 107, 109, 110, 112, 114, 116, 117, 119, 121, 123, 124, 126, 128, 129, 131, 133, 135, 136, 138, 140, 142, 143, 145, 147, 148, 150, 152, 154, 155, 157, 159, 161, 162, 164, 166, 168, 169, 171, 173, 174, 176, 178, 180, 181, 183, 185, 187, 188, 190, 192, 194, 195, 197, 199, 200, 202, 204, 206, 207, 209, 211, 213, 214, 216, 218, 220, }; void convert_yuv_to_rgb(void *yuv, void *rgb, unsigned int width, unsigned int height, unsigned int bps) { unsigned int i; int y1, y2, u, v; unsigned char *src = yuv; unsigned char *dst = rgb; unsigned int count = width * height / 2; switch (bps) { case 24: for (i = 0; i < count; i++) { y1 = *src++; u = *src++; y2 = *src++; v = *src++; *dst++ = ROUND_0_255(y1 + radj[v]); *dst++ = ROUND_0_255(y1 - gadj1[u] - gadj2[v]); *dst++ = ROUND_0_255(y1 + badj[u]); *dst++ = ROUND_0_255(y2 + radj[v]); *dst++ = ROUND_0_255(y2 - gadj1[u] - gadj2[v]); *dst++ = ROUND_0_255(y2 + badj[u]); } break; } } void convert_rgb_to_jpg_init(void) { memset(&jinfo, 0, sizeof(struct jpeg_mgr_info)); jinfo.cinfo.err = jpeg_std_error(&jinfo.jerr); jpeg_create_compress(&jinfo.cinfo); } int convert_rgb_to_jpg_work(void *rgb, void *jpeg, unsigned int width, unsigned int height, unsigned int bpp, int quality) { jinfo.written = width * height * bpp / 3; jpeg_mem_dest(&jinfo.cinfo, (unsigned char **)&jpeg, &jinfo.written); jinfo.cinfo.image_width = width; jinfo.cinfo.image_height = height; jinfo.cinfo.input_components = bpp / 8; jinfo.cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&jinfo.cinfo); jpeg_set_quality(&jinfo.cinfo, quality, TRUE); jpeg_start_compress(&jinfo.cinfo, TRUE); while(jinfo.cinfo.next_scanline < height) { jinfo.row_pointer[0] = rgb + jinfo.cinfo.next_scanline * width * bpp / 8; jpeg_write_scanlines(&jinfo.cinfo, jinfo.row_pointer, 1); } jpeg_finish_compress(&jinfo.cinfo); return (jinfo.written); } void convert_rgb_to_jpg_exit(void) { jpeg_destroy_compress(&jinfo.cinfo); }
到了这里,关于项目之利用 V4L2应用程序框架 进行视频录制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!