当前位置:   article > 正文

《Linux操作系统 - RK3568开发笔记》第5章 基于V4L2拍照_rk read_image_jpeg

rk read_image_jpeg

开发环境:
主机:Ubuntu 18.04
开发板:OK3568-C开发板

Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。本文将基于V4L2使用usb摄像头(UVC)拍照。

5.1启用linux内核对usb摄像头的支持

1.配置内核
进入内核目录,配置linux内核

$ make ARCH=arm64 menuconfig
  • 1

2.启用摄像头支持

最后一步时根据自己需要进行选择摄像头配置。

  • Linux 4.19
Device Drivers --->
<*>Multimedia support --->
[*] Cameras/video grabbers support
[*] Media usb adapters----> 
<*> USB video class (uvc)
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

在这里插入图片描述

Device Drivers --->
[*]usb support
     		[*] usb announce new device
  • 1
  • 2
  • 3

在这里插入图片描述

【注】选项框内为星号*表示开启并编译进内核,空白表示不开启,M表示开启并编译为模块

3.编译
修改完后,可以开始编译linux源码 执行以下命令:

退出后将修改内容保存到配置文件:

$ make ARCH=arm64 savedefconfig
$ mv defconfig arch/arm64/configs/OK3568-C-linux_defconfig
  • 1
  • 2

回到 SDK 根目录进行编译:

# 选择板型配置文件
$./build.sh BoardConfig-ok3568.mk
# 编译内核
./build.sh kernel
  • 1
  • 2
  • 3
  • 4

这样就把 UVC 编译进内核,当然也可把 UVC 编译成模块。

当插入UVC摄像头就会有相应的设备。

在这里插入图片描述

如果插入多个摄像头,设备名后缀数字依次增加,如: video1 video2 video3。

5.2 V4L2拍照应用实现

5.2.1 V4L2拍照原理

在Linux下,所有外设都被看成一种特殊的文件,也就是一切皆文件,Linux中所有的外设均可像访问普通文件一样对其进行读写操作。

V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。

在Linux中V4L2拍照的调用过程如下图所示。

在这里插入图片描述

V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集。

主要分为五个步骤:

首先,打开设备文件,参数初始化,通过V4L2接口设置图像的采集窗口、采集的点阵大小和格式。
其次,申请若干图像采集的帧缓冲区,便于应用程序读取/处理视频数据。
第三,将申请到的帧缓冲区在数据采集输入队列排队,并启动图片采集。
第四,驱动开始图像数据的采集,应用程序从数据采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入数据采集输入队列,循环往复采集连续的数据;
第五,停止数据采集。

完整代码如下:

【usb_camera.c】

/**
  ******************************************************************************
  * @file                usb_camera.c
  * @author              BruceOu
  * @version             V1.0
  * @date                2022-04-24
  * @blog                https://blog.bruceou.cn/
  * @Official Accounts   嵌入式实验楼
  * @brief               USB CAMERA
  ******************************************************************************
  */
/**Includes*********************************************************************/
#include "usb_camera.h"

#define DEBUG

/**【全局变量声明】*************************************************************/
buffer *user_buf = NULL;
static unsigned int n_buffer = 0;
static unsigned long file_length;
char   picture_name[20] ="rk_picture"; 
int    num = 0;

/**
  * @brief     打开摄像头设备函数
  * @param     None
  * @retval    fd    摄像头设备 
  */
int open_camer_device(char * videoDev)
{
	int fd;
	
	/*1.打开设备文件。*/
	if((fd = open(videoDev,O_RDWR | O_NONBLOCK)) < 0)
	{
		perror("Fail to open");
		pthread_exit(NULL);
	} 
	return fd;
}

/**
  * @brief     初始化视频设备函数
  * @param     fd    摄像头设备 
  * @retval    
  */
int init_camer_device(int fd)
{
	struct v4l2_fmtdesc fmt;
	struct v4l2_capability cap;
	struct v4l2_format stream_fmt;
	int ret;
	
	/*2.取得设备的capability,查询视频设备驱动的功能
	比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability*/
	ret = ioctl(fd,VIDIOC_QUERYCAP,&cap);
	if(ret < 0)
	{
		perror("FAIL to ioctl VIDIOC_QUERYCAP");
		exit(EXIT_FAILURE);
	}

	//判断是否是一个视频捕捉设备
	if(!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))
	{
		perror("The Current device is not a video capture device\n");
		exit(EXIT_FAILURE);
	
	}

	//判断是否支持视频流形式
	if(!(cap.capabilities & V4L2_CAP_STREAMING))
	{
		perror("The Current device does not support streaming i/o\n");
		exit(EXIT_FAILURE);
	}

	/*3.设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。*/
	memset(&fmt,0,sizeof(fmt));
	fmt.index = 0;
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	while((ret = ioctl(fd,VIDIOC_ENUM_FMT,&fmt)) == 0)
	{
		fmt.index ++ ;
#ifdef DEBUG
		printf("{pixelformat = %c%c%c%c},description = '%s'\n",
				fmt.pixelformat & 0xff,(fmt.pixelformat >> 8)&0xff,
				(fmt.pixelformat >> 16) & 0xff,(fmt.pixelformat >> 24)&0xff,
				fmt.description);
#endif
	}
	
	//设置摄像头采集数据格式,如设置采集数据的
	//长,宽,图像格式(JPEG,YUYV,MJPEG等格式)
	stream_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
	stream_fmt.fmt.pix.width = 680;//宽,必须是16的倍数
	stream_fmt.fmt.pix.height = 480;//高,必须是16的倍数
	stream_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//视频数据存储类型//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;
	stream_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

	//设置当前驱动的频捕获格式 
	if(-1 == ioctl(fd,VIDIOC_S_FMT,&stream_fmt))
	{
		perror("Fail to ioctl");
		exit(EXIT_FAILURE);
	}
		//计算图片大小
    file_length = stream_fmt.fmt.pix.bytesperline * stream_fmt.fmt.pix.height; 
	
	//初始化视频采集方式(mmap)
	init_mmap(fd);

	return 0;
}

/**
  * @brief     初始化视频采集方式(mmap)
  * @param     fd    摄像头设备 
  * @retval    
  */
int init_mmap(int fd)
{
	int i = 0;
	struct v4l2_requestbuffers reqbuf;

	/*4.向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers*/
	bzero(&reqbuf,sizeof(reqbuf));
	reqbuf.count = 4;//缓存数量,也就是说在缓存队列里保持多少张照片
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory = V4L2_MEMORY_MMAP;//或V4L2_MEMORY_USERPTR
	
	//申请视频缓冲区(这个缓冲区位于内核空间,需要通过mmap映射)
	//这一步操作可能会修改reqbuf.count的值,修改为实际成功申请缓冲区个数
	if(-1 == ioctl(fd,VIDIOC_REQBUFS,&reqbuf))
	{
		perror("Fail to ioctl 'VIDIOC_REQBUFS'");
		exit(EXIT_FAILURE);
	}
	
	n_buffer = reqbuf.count;
#ifdef DEBUG
	printf("n_buffer = %d\n",n_buffer);
#endif
	user_buf = calloc(reqbuf.count,sizeof(*user_buf));//内存中建立对应空间
	if(user_buf == NULL)
	{
		fprintf(stderr,"Out of memory\n");
		exit(EXIT_FAILURE);
	}

	/*5.将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,
	而不必去复制。mmap*/
	for(i = 0; i < n_buffer; i ++)
	{
		struct v4l2_buffer buf;//驱动中的一帧
		
		bzero(&buf,sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		//查询申请到内核缓冲区的信息
		if(-1 == ioctl(fd,VIDIOC_QUERYBUF,&buf)) //映射用户空间
		{
			perror("Fail to ioctl : VIDIOC_QUERYBUF");
			exit(EXIT_FAILURE);
		}

		user_buf[i].length = buf.length;
		user_buf[i].start = 
			mmap(
					NULL,/*start anywhere*/
					buf.length,
					PROT_READ | PROT_WRITE,
					MAP_SHARED,
					fd,buf.m.offset//通过mmap建立映射关系,返回映射区的起始地址
				);
		if(MAP_FAILED == user_buf[i].start)
		{
			perror("Fail to mmap");
			exit(EXIT_FAILURE);
		}
	}	

	return 0;
}

int start_capturing(int fd)
{
	unsigned int i;
	enum v4l2_buf_type type;

	/*6.将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer*/
	for(i = 0;i < n_buffer;i ++)
	{
		struct v4l2_buffer buf;

		bzero(&buf,sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		
		//把数据从缓存中读取出来 
		if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))//申请到的缓冲进入列队
		{
			perror("Fail to ioctl 'VIDIOC_QBUF'");
			exit(EXIT_FAILURE);
		}
	}

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	/*7.开始视频的采集。VIDIOC_STREAMON*/
	if(-1 == ioctl(fd,VIDIOC_STREAMON,&type)) //开始捕捉图像数据
	{
		perror("Fail to ioctl 'VIDIOC_STREAMON'");
		exit(EXIT_FAILURE);
	}

	return 0;
}

int mainloop(int fd)
{ 
	int count = 2;

	/*8.循环采集图片。*/
	while(count-- > 0)
	{
		for(;;)
		{
			fd_set fds;
			struct timeval tv;
			int r;

			FD_ZERO(&fds);//将指定的文件描述符集清空
			FD_SET(fd,&fds);//在文件描述符集合中增加新的文件描述符

			/*Timeout*/
			tv.tv_sec = 2;
			tv.tv_usec = 0;
		
			r = select(fd + 1,&fds,NULL,NULL,&tv);//判断是否可读(即摄像头是否准备好),tv是定时

			if(-1 == r)
			{
				if(EINTR == errno)
					continue;
				
				perror("Fail to select");
				exit(EXIT_FAILURE);
			}

			if(0 == r)
			{
				fprintf(stderr,"select Timeout\n");
				exit(EXIT_FAILURE);
			}

			if(read_frame(fd))//如果可读,执行read_frame ()函数,并跳出循环  
				break;
		}
	}
	return 0;
}

//将采集好的数据放到文件中
int process_image(void *addr,int length)
{
	FILE *fp;
	char name[20];
	
	sprintf(name,"%s%d.jpg",picture_name,num ++);
	
	if((fp = fopen(name,"w")) == NULL)
	{
		perror("Fail to fopen");
		exit(EXIT_FAILURE);
	}

	fwrite(addr,length,1,fp);
	usleep(500);

	fclose(fp);

	return 0;
}

int read_frame(int fd)
{
	struct v4l2_buffer buf;
	unsigned int i;

	bzero(&buf,sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	/*9.出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF*/
	if(-1 == ioctl(fd,VIDIOC_DQBUF,&buf))
	{
		perror("Fail to ioctl 'VIDIOC_DQBUF'");
		exit(EXIT_FAILURE);
	}

	assert(buf.index < n_buffer);
	{
#ifdef DEBUG
		printf ("buf.index dq is %d,\n",buf.index);
#endif
	}
	//读取进程空间的数据到一个文件中
	process_image(user_buf[buf.index].start,user_buf[buf.index].length);
	
	/*10.将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF*/ 
	if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))//把数据从缓存中读取出来
	{
		perror("Fail to ioctl 'VIDIOC_QBUF'");
		exit(EXIT_FAILURE);
	}

	return 1;
}

void stop_capturing(int fd)
{
	enum v4l2_buf_type type;
	/*11.停止视频的采集。VIDIOC_STREAMOFF*/
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&type))
	{
		perror("Fail to ioctl 'VIDIOC_STREAMOFF'");
		exit(EXIT_FAILURE);
	}
	return;
}

void uninit_camer_device()
{
	unsigned int i;

	for(i = 0;i < n_buffer;i ++)
	{
		if(-1 == munmap(user_buf[i].start,user_buf[i].length))
		{
			exit(EXIT_FAILURE);
		}
	}
	
	free(user_buf);

	return;
}

void close_camer_device(int fd)
{
	if(-1 == close(fd))
	{
		perror("Fail to close fd");
		exit(EXIT_FAILURE);
	}

	return;
}

/**
  * @brief     摄像头拍照函数
  * @param     void
  * @retval    Nono
  */
int main(int argc, char* argv[])
{	
	int camera_fd;       
	if(argc == 2 )
	{
        camera_fd = open_camer_device(argv[1]);
        init_camer_device(camera_fd);
        start_capturing(camera_fd);

	    num = 0;
		
	    mainloop(camera_fd);

        stop_capturing(camera_fd);
        uninit_camer_device(camera_fd);
        close_camer_device(camera_fd);

        printf("Camera get pic success!\n");
    }
    else
    {
        printf("Please input video device!\n");
    }
    return 0;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394

【usb_camera.h】

#ifndef _USB_CAMERA_H_
#define _USB_CAMERA_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
#include <getopt.h> 
#include <fcntl.h> 
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h> 
#include <linux/videodev2.h>

#define VIDEO_DEV "/dev/video9"//摄像头设备名

typedef struct _buffer
{
	void *start;
	size_t length;
}buffer;

int open_camer_device(char * videoDev);
int init_mmap(int fd);
int init_camer_device(int fd);
int start_capturing(int fd);
int process_image(void *addr,int length);
int read_frame(int fd);
int mainloop(int fd);
void stop_capturing(int fd);
void uninit_camer_device();
void close_camer_device(int fd);

void camera_get_image(void);

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

【Makefile】

ARCH=arm64
CROSS=aarch64-linux-gnu-

all: usb_camera
	sudo scp usb_camera root@192.168.101.10:/root
usb_camera:usb_camera.c
	$(CROSS)gcc -o usb_camera usb_camera.c 
	$(CROSS)strip usb_camera
clean:
	@rm -vf usb_camera *.o *~
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

值得注意的是,Makefile中通过scp将编译好的程序拷贝到开发板,需要根据修改相应开发板的IP地址。当然也可通过其他方式拷贝程序。

5.2.2编译测试

接下来就是编译下载测试了。

1.编译

在这里插入图片描述

值得注意的是,上面的IP地址是开发板的IP,密码是开发板的登陆密码。

2.测试
接下来在RK3568中运行拍照程序。

在这里插入图片描述

笔者一次拍两张,当然也可以连续拍很多,在代码中可以修改。最后将照片传到主机查看。

在这里插入图片描述

这样就是可以查看前面拍得的图片了。

值得注意的是,上面的IP地址是主机的IP,密码是主机开发板的登陆密码。

我们在Windows中查看拍的照片。

在这里插入图片描述

照片大小在代码中可以调整,可以通过参数传进去。



欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎


欢迎订阅我的微信公众号

关注公众号[嵌入式实验楼]获取更多资讯

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号