摄像头画面显示的程序比较简单,友善之臂的光盘里面已经提供了相关的代码,这里对其进行简单的封装,以便后续工程的使用。
首先从main函数看起,代码如下。
/*
* main.cpp
*
* Created on: 2015年12月4日
* Author: Westlor
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "camera.h"
#include "Fb.h"
#define CAM_DEV "/dev/video0"
#define FB_DEV "/dev/fb0"
Camera *camera;
Fb *fb;
void sign_func(int sign_num)
{
switch(sign_num)
{
case SIGINT:
printf("I have get SIGINT<Ctrl+c>, I'm going now..\n");
camera->CloseDevice();
fb->CloseDevice();
exit(0);
break;
}
}
int main(void) {
int width=640;
int height=480;
unsigned char* image;
camera=new Camera(CAM_DEV, width, height);
if(!camera->OpenDevice()){
printf("Cam Open error\n");
return -1;
}
fb = new Fb(FB_DEV, 80, 0, width, height);
if(!fb->OpenDevice()){
printf("Fb Open error\n");
return -1;
}
fb->Trans(&image);
printf("Waiting for signal SIGINT..\n");
signal(SIGINT, sign_func);
while(1){
if(!camera->GetBuffer(image)){
break;
}
fb->Draw();
}
return 0;
}
这里定义了两个类,Camera类的构造函数里定义了摄像头输入图像的宽和高,Fb类的构造函数里定义了液晶屏上显示图像的位置(x,y)以及大小。首先定义一个缓冲区,用来存储要显示的数据。在主循环中,不断读取摄像头采集度图像数据,并将其进行格式转换后放到缓冲区中,然后将缓冲区的数据拷贝到framebuffer中即可。
接下来看摄像头初始化部分,对摄像头配置代码如下。
bool Camera::init_device(void) {
v4l2_input input;
memset(&input, 0, sizeof(struct v4l2_input));
input.index = 0;
if (ioctl(fd, VIDIOC_ENUMINPUT, &input) != 0) {
fprintf(stderr, "No matching index found\n");
return false;
}
if (!input.name) {
fprintf(stderr, "No matching index found\n");
return false;
}
if (ioctl(fd, VIDIOC_S_INPUT, &input) < 0) {
fprintf(stderr, "VIDIOC_S_INPUT failed\n");
return false;
}
struct v4l2_format fmt;
CLEAR (fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
return false;
if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
return false;
//原始摄像头数据每帧的大小
cap_image_size = fmt.fmt.pix.sizeimage;
init_mmap();
return true;
}
这里配置摄像头图像格式为NV12,因为后面h264编码是需要摄像头图像格式为NV12。NV12为YUV420格式,每个像素占12位,平均是Y分量占8位,UV分量各占两位,存储时在线性模式下先存储Y分量,占前2/3,然后在剩余1/3空间里交叉存储UV分量。
然后看液晶屏初始化的代码,对屏幕显示配置的代码如下。
bool Fb::init_device(void) {
struct fb_fix_screeninfo Fix;
struct fb_var_screeninfo Var;
if (ioctl(fd, FBIOGET_FSCREENINFO, bitand Fix) < 0 or ioctl(fd, FBIOGET_VSCREENINFO, bitand Var) < 0) {
printf("cannot get frame buffer information\n");
}
BPP = Var.bits_per_pixel;
if (BPP not_eq 32) {
printf("support 32 BPP frame buffer only\n");
}
Width = Var.xres;
Height = Var.yres;
LineLen = Fix.line_length;
Size = LineLen * Height;
Addr = (unsigned char *)mmap(NULL, Size, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
if (Addr == (unsigned char *)MAP_FAILED) {
printf("map frame buffer failed\n");
return false;
}
show_buffer = (unsigned char*)malloc(show_width*show_height*BPP/8);
if(show_buffer == NULL){
printf("buffers malloc failed\n");
return false;
}
Clear(); //清空屏幕
return true;
}
屏幕所显示的图像格式为rgb,每个像素为32位的,rgb分量各占8位,还有8位是透明度的分量。从摄像头取来的图像转换到屏幕上显示时需要做格式转换,即NV12->rgb。下面是图像格式转换代码。申请内存的时候需要注意存储空间的大小,NV12的对应为 H*W*12/8,rgb的对应为 H*W*32/8。
void Camera::DecodeYUV420SP(unsigned int* rgbBuf, unsigned char* yuv420sp, int width, int height) {
int frameSize = width * height;
int i = 0, y = 0;
int uvp = 0, u = 0, v = 0;
int y1192 = 0, r = 0, g = 0, b = 0;
unsigned int xrgb8888;
int xrgb8888Index = 0;
for (int j = 0, yp = 0; j < height; j++) {
uvp = frameSize + (j >> 1) * width;
u = 0;
v = 0;
for (i = 0; i < width; i++, yp++) {
y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0) y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
y1192 = 1192 * y;
r = (y1192 + 1634 * u);
g = (y1192 - 833 * u - 400 * v);
b = (y1192 + 2066 * v);
if (r < 0) r = 0; else if (r > 262143) r = 262143;
if (g < 0) g = 0; else if (g > 262143) g = 262143;
if (b < 0) b = 0; else if (b > 262143) b = 262143;
r = (unsigned char)(r >> 10);
g = (unsigned char)(g >> 10);
b = (unsigned char)(b >> 10);
xrgb8888 = (unsigned int)((r << 16) | (g << 8) | b);
rgbBuf[xrgb8888Index++] = xrgb8888;
}
}
}
程序可以在
http://download.csdn.net/detail/westlor/9389478下载。