Linux下V4L2捕捉画面+H264压缩视频+帧缓冲显示视频————V4L2捕捉画面

V4L2打开摄像头主要步骤是

  1. 打开设备文件, 比如/dev/video0
  2. 查询摄像头信息
  3. 设置摄像头参数, 如曝光、分辨率、帧率
  4. 映射内存

需要注意的是

  1. 分辨率、帧率不一定能达到自己想要的值
  2. 设置的参数最好再读出来确认一次
/* camera.h */
#ifndef CAMERA_H
#define CAMERA_H

#include <stdint.h>

typedef void (*vCameraFrameProcess)(uint8_t* pData, uint32_t Width, uint32_t Height, uint32_t Length) ;

/**
 *  打开摄像头
 * @pDevName 设备节点
 *  返回值: 0成功 -1失败
 */
int CameraOpen(const char* pDevName);

/**
 * 关闭摄像头
 * 返回值: 0成功 -1失败
 */
int CameraClose(void);

/**
 * 设置捕获帧回调
 * 返回值: 0成功 -1失败
*/
int CameraCaptureCallbackSet(vCameraFrameProcess pCallBack);

/**
 * 开始捕获
 * 返回值: 0成功 -1失败
*/
int CameraCaptureStart(void);

/**
 * 停止捕获
 * 返回值: 0成功 -1失败
*/
int CameraCaptureStop(void);

#endif
/* config.h */
#ifndef CONFIG_H
#define CONFIG_H

#define CONFIG_CAPTURE_WIDTH		640
#define CONFIG_CAPTURE_HEIGHT		480
#define CONFIG_CAPTURE_FPS			30
#define CONFIG_CAPTURE_BUF_CNT		10
#define CONFIG_ENCODE_BITRATE		2500

#define CONFIG_DEVNAME_LEN			32
#define CONFIG_IOCTL_RETRY			5
#define CONFIG_CAPTURE_DEVICE		"/dev/video0"
#define CONFIG_DISPLAY_DEV			"/dev/fb0"

#define CONFIG_QUEUE_SIZE			50
#define CONFIG_QUEUE_LIMIT			2

#endif
/* camera.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <assert.h>
#include <linux/videodev2.h>
#include <pthread.h>

#include "config.h"
#include "camera.h"

static int Sign3 = 1;

#define IOCTL_VIDEO(fd, req, value) ioctl(fd, req, value)

typedef struct{  
	void* pStart;
	int Length;
} sBufferType;

typedef enum {
	CAMERA_STATE_CLOSE,
	CAMERA_SATTE_OPEN,
	CAMERA_SATTE_CAP,
	CAMERA_STATE_ERR
} eCAMERA_STATE;

static struct {
	char 		DevName[CONFIG_DEVNAME_LEN];
	int 		DevFd;
	int 		BufferCnt;
	uint32_t 	Width;
	uint32_t 	Height;
	sBufferType* pCameraBuffer;
	vCameraFrameProcess pCallback;
	eCAMERA_STATE State;							/* 0 未打开, 1打开摄像头, 2开始捕捉, 3错误 */
	pthread_t	CaptureThreadId;
} sCameraPrivateData;


// static int xioctl(int Fd, int IOCTL_X, void *pArg)
// {
// 	int Ret = 0;
// 	int Tries = CONFIG_IOCTL_RETRY;
// 	do {
// 		Ret = IOCTL_VIDEO(Fd, IOCTL_X, pArg);
// 	} while(Ret && Tries-- && ((errno == EINTR) || (errno == EAGAIN) || (errno == ETIMEDOUT)));

// 	if(Ret && (Tries <= 0)) fprintf(stderr, "ioctl (%i) retried %i times - giving up: %s)\n", IOCTL_X, CONFIG_IOCTL_RETRY, strerror(errno));

// 	return (Ret);
// }


/**
 * 存储设备名称
*/
static int DevNameSet(const char* pDevName)
{
	int CopyLen = 0;
	CopyLen = strlen(pDevName);
	if(CopyLen > CONFIG_DEVNAME_LEN - 1) {
		printf("Error: device name length over limit: %d", CopyLen);
		return -1;
	} 
	memset(sCameraPrivateData.DevName, 0, CONFIG_DEVNAME_LEN);
	memcpy(sCameraPrivateData.DevName, pDevName, CopyLen);
	return 0;
}

/**
 * 重置摄像头信息
*/
static void DevInfoReset(void)
{
	memset(sCameraPrivateData.DevName, 0, CONFIG_DEVNAME_LEN);
	sCameraPrivateData.State = CAMERA_STATE_CLOSE;
	sCameraPrivateData.DevFd = -1;
	sCameraPrivateData.pCallback = NULL;
	sCameraPrivateData.Width = 0;
	sCameraPrivateData.Height = 0;
}


static inline int Yuv2Rgb(int Y, int U, int V)
{
	uint32_t Pixel32 = 0;
	int R, G, B;
	static long int Ruv, Guv, Buv;

	if(Sign3) {
		Sign3 = 0;
		Ruv = 1159 * (V - 128);
		Guv = -380 * (U - 128) + 813 * (V - 128);
		Buv = 2018 * (U - 128);
	}

	int Delta = 1164 * (Y - 16);

	R = (Delta + Ruv) / 1000;
	G = (Delta - Guv) / 1000;
	B = (Delta + Buv) / 1000;

	R = R > 255 ? 255 : R < 0 ? 0 : R;
	G = G > 255 ? 255 : G < 0 ? 0 : G;
	B = B > 255 ? 255 : B < 0 ? 0 : B;

	Pixel32 = (B & 0xFF) | (G & 0xFF) << 8 | (R & 0xFF) << 16;

	return Pixel32;
}

int Yuyv2Rgb32(uint8_t* pYuv, uint8_t* pRgb, uint32_t Width, uint32_t Height)
{
	int y0, U, y1, V;
	uint32_t Pixel32;
	uint8_t* pPixel = (uint8_t *)&Pixel32;
	// 分辨率描述像素点个数,而yuv2个像素点占有4个字符,所以这里计算总的字符个数,需要乘2
	uint32_t Size = Width * Height * 2; 

	for(uint32_t In = 0, Out = 0; In < Size; In += 4, Out += 8) {
		y0 = pYuv[In + 0];
		U  = pYuv[In + 1];
		y1 = pYuv[In + 2];
		V  = pYuv[In + 3];

		Sign3 = 1;
		Pixel32 = Yuv2Rgb(y0, U, V);
		pRgb[Out + 0] = pPixel[0];   
		pRgb[Out + 1] = pPixel[1];
		pRgb[Out + 2] = pPixel[2];
		pRgb[Out + 3] = 0;  //32位rgb多了一个保留位

		Pixel32 = Yuv2Rgb(y1, U, V);
		pRgb[Out + 4] = pPixel[0];
		pRgb[Out + 5] = pPixel[1];
		pRgb[Out + 6] = pPixel[2];
		pRgb[Out + 7] = 0;

	}
	return 0;
}

int Yuyv2Rgb24(uint8_t* pYuv, uint8_t* pRgb, uint32_t Width, uint32_t Height)
{
	int y0, U, y1, V;
	uint32_t Pixel32;
	uint8_t* pPixel = (uint8_t *)&Pixel32;
	// 分辨率描述像素点个数,而yuv2个像素点占有4个字符,所以这里计算总的字符个数,需要乘2
	uint32_t Size = Width * Height * 2; 

	for(uint32_t In = 0, Out = 0; In < Size; In += 4, Out += 6) {
		y0 = pYuv[In + 0];
		U  = pYuv[In + 1];
		y1 = pYuv[In + 2];
		V  = pYuv[In + 3];

		Sign3 = 1;
		Pixel32 = Yuv2Rgb(y0, U, V);
		pRgb[Out + 0] = pPixel[0];   
		pRgb[Out + 1] = pPixel[1];
		pRgb[Out + 2] = pPixel[2];

		Pixel32 = Yuv2Rgb(y1, U, V);
		pRgb[Out + 3] = pPixel[0];
		pRgb[Out + 4] = pPixel[1];
		pRgb[Out + 5] = pPixel[2];

	}
	return 0;
}


int CameraOpen(const char* pDevName)
{
	int Ret = -1;

	/* 检查摄像头是否已经打开或者在错误状态 */
	printf("Open camera %s...\n", pDevName);
	if(sCameraPrivateData.State != CAMERA_STATE_CLOSE) {
		printf("Error: camera was open or meet error, now state is: %d", sCameraPrivateData.State);
		goto ErrorHandle;
	}

	/* 打开摄像头设备 */
	if((sCameraPrivateData.DevFd = open(pDevName, O_RDWR, 0)) == -1) {
	// if((sCameraPrivateData.DevFd = open(pDevName, O_RDWR | O_NONBLOCK, 0)) == -1) {
		printf("Open camera %s faild.\n", pDevName);
		goto ErrorHandle;
	}

	printf("Open device successful.\n");

	/* 设置采集来源 */
	struct v4l2_input Input;
	Input.index = 0;

	if (ioctl(sCameraPrivateData.DevFd, VIDIOC_S_INPUT, &Input)) {
		perror("VIDIOC_S_INPUT");
	}

	/* 获取摄像头信息 */
	struct v4l2_capability Cap;
	
	Ret = ioctl(sCameraPrivateData.DevFd, VIDIOC_QUERYCAP, &Cap);

	if(Ret){
		perror("Get device info error");
		goto ErrorHandle;
	}

	printf("Get device info successful.\n");

	printf("Driver name: %s\n", Cap.driver);
	printf("Card name: %s\n", Cap.card);
	printf("Bus info: %s\n", Cap.bus_info);

	DevNameSet(pDevName);

	struct v4l2_format TvFmt;
	struct v4l2_fmtdesc FmtDesc;
	struct v4l2_streamparm StreamParam;

	memset(&FmtDesc, 0, sizeof(FmtDesc));
	FmtDesc.index = 0;  
	FmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	/* 枚举支持的桢格式 */
	printf("Support format:\n");
	while(ioctl(sCameraPrivateData.DevFd, VIDIOC_ENUM_FMT, &FmtDesc) == 0){  
		FmtDesc.index++;
		printf("Pixel format = %c%c%c%c\n", 
		(FmtDesc.pixelformat & 0xFF),
		((FmtDesc.pixelformat >> 8) & 0xFF),
		((FmtDesc.pixelformat >> 16) & 0xFF),
		(FmtDesc.pixelformat >> 24));
		printf("Description = %s \n", FmtDesc.description); 
	}

	/* 判断设备是否是可以捕获视频的设备 */
	if(!(Cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE)) {  
		printf("The Current device is not a video capture device\n");  
		goto ErrorHandle;
	}

	/* 判断是否支持视频流 */
	if(!(Cap.capabilities & V4L2_CAP_STREAMING)) {  
		printf("The Current device does not support streaming i/o\n");  
		goto ErrorHandle;
	}

	/* 获取视频流信息 */
	memset(&StreamParam, 0, sizeof(struct v4l2_streamparm));
	StreamParam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	Ret = ioctl(sCameraPrivateData.DevFd, VIDIOC_G_PARM, &StreamParam);
	if(Ret) {
		perror("Get stream info failed");
		goto ErrorHandle;
	}

	printf("Before setting stream params:\n");
	printf("Capability: %u\n", StreamParam.parm.capture.capability);
	printf("Capturemode: %u\n", StreamParam.parm.capture.capturemode);
	printf("Extendedmode: %u\n", StreamParam.parm.capture.extendedmode);
	printf("Timeperframe denominator: %u\n", StreamParam.parm.capture.timeperframe.denominator);
	printf("Timeperframe numerator: %u\n", StreamParam.parm.capture.timeperframe.numerator);

	/* 帧率分母 分子设置 */
	StreamParam.parm.capture.timeperframe.denominator = CONFIG_CAPTURE_FPS;
	StreamParam.parm.capture.timeperframe.numerator = 1;

	if(ioctl(sCameraPrivateData.DevFd, VIDIOC_S_PARM, &StreamParam) == -1) {
		perror("Unable to set fps");
		goto ErrorHandle;
	}

	/* 获取视频流信息 */
	Ret = ioctl(sCameraPrivateData.DevFd, VIDIOC_G_PARM, &StreamParam);
	if(Ret) {
		perror("Get stream info failed");
		goto ErrorHandle;
	}

	printf("After setting stream params:\n");
	printf("Capability: %u\n", StreamParam.parm.capture.capability);
	printf("Capturemode: %u\n", StreamParam.parm.capture.capturemode);
	printf("Extendedmode: %u\n", StreamParam.parm.capture.extendedmode);
	printf("Timeperframe denominator: %u\n", StreamParam.parm.capture.timeperframe.denominator);
	printf("Timeperframe numerator: %u\n", StreamParam.parm.capture.timeperframe.numerator);

	// struct v4l2_control Ctrl;
	// Ctrl.id = V4L2_CID_EXPOSURE_AUTO;
	// Ret = ioctl(sCameraPrivateData.DevFd, VIDIOC_G_CTRL, &Ctrl);

	// if(Ret) {
	// 	perror("Get exposure info failed");
	// 	goto ErrorHandle;
	// }

	// printf("Before setting control params:\n");
	// printf("Controll value: %#X", Ctrl.value);

	/* 设置自动曝光 */
	struct v4l2_control Ctrl;
	Ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
	Ret = ioctl(sCameraPrivateData.DevFd, VIDIOC_G_CTRL, &Ctrl);

	if(Ret) {
		perror("Set exposure failed");
		// goto ErrorHandle;
	}

	/* 设置捕获格式 */
	TvFmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	TvFmt.fmt.pix.width = CONFIG_CAPTURE_WIDTH;
	TvFmt.fmt.pix.height = CONFIG_CAPTURE_HEIGHT;
	TvFmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
	TvFmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

	if (ioctl(sCameraPrivateData.DevFd, VIDIOC_S_FMT, &TvFmt) < 0) {  
		perror("Unable set VIDIOC_S_FMT");
		goto ErrorHandle;
	}

	if (ioctl(sCameraPrivateData.DevFd, VIDIOC_G_FMT, &TvFmt) < 0) {  
		perror("Get VIDIOC_S_FMT error");
		goto ErrorHandle;
	}

	printf("After setting frame params:\n");
	printf("Width: %d, height: %d\n", TvFmt.fmt.pix.width, TvFmt.fmt.pix.height);
		printf("Pixel format = %c%c%c%c\n", 
		(TvFmt.fmt.pix.pixelformat & 0xFF),
		((TvFmt.fmt.pix.pixelformat >> 8) & 0xFF),
		((TvFmt.fmt.pix.pixelformat >> 16) & 0xFF),
		(TvFmt.fmt.pix.pixelformat >> 24));

	sCameraPrivateData.Width = TvFmt.fmt.pix.width;
	sCameraPrivateData.Height = TvFmt.fmt.pix.height;

	/* 请求V4L2驱动申请内存 */
	struct v4l2_requestbuffers ReqBuffer;
	memset(&ReqBuffer, 0, sizeof(ReqBuffer));
	ReqBuffer.count = CONFIG_CAPTURE_BUF_CNT;
	ReqBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ReqBuffer.memory = V4L2_MEMORY_MMAP;  

	if(-1 == ioctl(sCameraPrivateData.DevFd, VIDIOC_REQBUFS, &ReqBuffer)){  
		perror("Fail to ioctl 'VIDIOC_REQBUFS'");
		goto ErrorHandle;
	}

	sCameraPrivateData.BufferCnt = ReqBuffer.count;  
	printf("Buffer count: %d\n", sCameraPrivateData.BufferCnt);

	sCameraPrivateData.pCameraBuffer = (sBufferType *)calloc(sCameraPrivateData.BufferCnt, sizeof(sBufferType));
	if(sCameraPrivateData.pCameraBuffer == NULL) {
		printf("Malloc buffer failed\n");
		goto ErrorHandle;
	}

	/* 映射内核缓存到用户空间 */
	struct v4l2_buffer Buf;  
	for(int i = 0; i < sCameraPrivateData.BufferCnt; i++) {
		memset(&Buf, 0, sizeof(Buf));  
		Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		Buf.memory = V4L2_MEMORY_MMAP;
		Buf.index = i;

		if(-1 == ioctl(sCameraPrivateData.DevFd, VIDIOC_QUERYBUF, &Buf)) {
			perror("Fail to ioctl : VIDIOC_QUERYBUF");
			goto ErrorHandle;
		}

		sCameraPrivateData.pCameraBuffer[i].Length = Buf.length;
		sCameraPrivateData.pCameraBuffer[i].pStart = 
			mmap(NULL, Buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, sCameraPrivateData.DevFd, Buf.m.offset);

		if(MAP_FAILED == sCameraPrivateData.pCameraBuffer[i].pStart) {
			perror("Fail to mmap");
			goto ErrorHandle;
		}

		if(ioctl(sCameraPrivateData.DevFd, VIDIOC_QBUF, &Buf)) {
			perror("Unable to queue buffer");
			goto ErrorHandle;;
		}
	}

	/* 开始捕获 */
	enum v4l2_buf_type BufType;
	BufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if(ioctl(sCameraPrivateData.DevFd, VIDIOC_STREAMON, &BufType) < 0) {
		perror("VIDIOC_STREAMON error");
		goto ErrorHandle;
	}
	sCameraPrivateData.State = CAMERA_SATTE_OPEN;

	return 0;

ErrorHandle:
	printf("Camera open meet error, now handle it\n");
	close(sCameraPrivateData.DevFd);
	sCameraPrivateData.State = CAMERA_STATE_CLOSE;
	free(sCameraPrivateData.pCameraBuffer);
	sCameraPrivateData.pCameraBuffer = NULL;
	return -1;
}

int CameraClose(void)
{
	if(sCameraPrivateData.State == CAMERA_SATTE_OPEN || sCameraPrivateData.State == CAMERA_SATTE_CAP) {
		printf("Close camera %s\n", sCameraPrivateData.DevName);
	} else {
		printf("Skip close camera progress\n");
		return 0;
	}
	
	for(int i = 0; i < CONFIG_CAPTURE_BUF_CNT; i++) {
		
		munmap(sCameraPrivateData.pCameraBuffer[i].pStart, 
				sCameraPrivateData.pCameraBuffer[i].Length);
		sCameraPrivateData.pCameraBuffer[i].Length = 0;
	}

	free(sCameraPrivateData.pCameraBuffer);
	sCameraPrivateData.pCameraBuffer = NULL;

	int Off = 1;

	if(ioctl(sCameraPrivateData.DevFd , VIDIOC_STREAMOFF, &Off))  {
		perror("Stop camera fail");
		return -1 ;
	}

	close(sCameraPrivateData.DevFd);

	DevInfoReset();

	printf("Camera closed\n");

	return 0;
}

int CameraCaptureCallbackSet(vCameraFrameProcess pCallback)
{
	if(sCameraPrivateData.State == CAMERA_SATTE_CAP) {
		printf("Set capture callback failed");
		return -1;
	}
	sCameraPrivateData.pCallback = pCallback;
	return 0;
}

static void* CameraCaptureThread(void* pParam)
{
	int Ret = -1;
	
	struct v4l2_buffer EnQueueBuf;
	struct v4l2_buffer DeQueueBuf;

	printf("Start capture\n");

	while(sCameraPrivateData.State == CAMERA_SATTE_CAP) {

		memset(&DeQueueBuf, 0, sizeof(struct v4l2_buffer));
		DeQueueBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		DeQueueBuf.memory = V4L2_MEMORY_MMAP;

		memset(&EnQueueBuf, 0, sizeof(struct v4l2_buffer));
		EnQueueBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		EnQueueBuf.memory = V4L2_MEMORY_MMAP;

		// 取出队列中的数据
		Ret = ioctl(sCameraPrivateData.DevFd, VIDIOC_DQBUF, &DeQueueBuf);
		if(Ret) {
			perror("Dequeue fail");
			break;
		}
		sCameraPrivateData.pCallback(sCameraPrivateData.pCameraBuffer[DeQueueBuf.index].pStart, 
										sCameraPrivateData.Width,
										sCameraPrivateData.Height,
										sCameraPrivateData.pCameraBuffer[DeQueueBuf.index].Length);

		// 重新入队列
		EnQueueBuf.index = DeQueueBuf.index;

		if(ioctl(sCameraPrivateData.DevFd , VIDIOC_QBUF , &EnQueueBuf)) {
			perror("Enqueue fail");
			break;
		}
	}

	printf("Stop capture\n");

	return NULL;
}

int CameraCaptureStart(void)
{
	printf("Start capture thread\n");
	if(!sCameraPrivateData.pCallback) {
		printf("Capture callback not set, start faild\n");
		return -1;
	}
	if(sCameraPrivateData.State != CAMERA_SATTE_OPEN) {
		printf("Camera not open, start faild\n");
		return -1;
	}
	sCameraPrivateData.State = CAMERA_SATTE_CAP;

	pthread_create(&sCameraPrivateData.CaptureThreadId, NULL, CameraCaptureThread, (void *)NULL);

	return 0;
}

int CameraCaptureStop(void)
{
	printf("Stop capture thread\n");
	sCameraPrivateData.State = CAMERA_SATTE_OPEN;
	pthread_join(sCameraPrivateData.CaptureThreadId, NULL);
	printf("Capture thread stop\n");
	return 0;
}
posted @ 2019-05-12 21:24  rootming  阅读(1263)  评论(0编辑  收藏  举报