1.摄像头编程
用linux的V4L2架构实现摄像头显示(复杂)
QT中的摄像头操作(简单)
2.jpeg库移植和使用(显示jpeg图片,保存jpeg图片)
3.ALSA库的移植,使用ALSA库实现录音和播放录音
linux中V4L2摄像头编程
1.什么是V4L2
触摸屏:输入子系统struct input_event
video for linux two //linux中视频架构
linux提供的开发视频类设备(摄像头,网络摄像机,视频会议系统)的驱动以及应用程序的一种架构,它里面定义大量的结构体,枚举,宏定义去表示视频类设备的参数,属性(统一江湖,所有的厂家生产的视频类设备,如果需要在linux系统上使用,都必须使用这个架构)
系统有个头文件/usr/include/linux/videodev2.h有V4L2详细的参数定义
常见摄像头采集格式有两种:jpg和yuv格式
发给大家的摄像头默认拍摄的是yuv格式
A厂家 --》做了一款摄像头
A厂家的驱动工程师
struct cammsg
{
int w; 宽
int h; 高
enum type; //摄像头画面格式 jpg yuv
。。。。。。。
}
B厂家 --》做了一款摄像头
B厂家的驱动工程师
struct caminfo
{
int width; 宽
int height; 高
enum pictype; //摄像头画面格式 jpg yuv
。。。。。。。
}
2.使用V4L2提供的参数,变量类型去实现摄像头显示
#include <stropts.h>
int ioctl(int fildes, int request, ... /* arg */); //硬件驱动跟上层应用程序进行数据交互的一个接口
作用一:驱动可以用ioctl传递数据给应用程序
作用二:应用程序也可以用ioctl给底层驱动发送数据
返回值:成功 0 失败 -1
参数: fildes --》摄像头的文件描述符
request --》你要发送给摄像头驱动的控制指令
V4L2架构提供了所有的摄像头控制指令
缓冲队列:指的就是你申请的那4个缓冲区,由于先拍摄的画面必须先出队显示出来,特点跟队列这种数据结构的特点相似,所以老外就取名缓冲队列
画面入队:发送指令让摄像头把拍摄的画面保存到刚才你申请的多个缓冲区里面
画面出队:把缓冲区里面的画面数据取出来
(1)打开摄像头驱动
camerafd=open("/dev/video7") //摄像头插到开发板的usb,自己去/dev下面查看
(2)设置摄像头的采集格式
用到的指令:VIDIOC_S_FMT
用到的变量类型:struct v4l2_format
{
type; //开关,type设置成不同值,选择不同的结构体变量 V4L2_BUF_TYPE_VIDEO_CAPTURE
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
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 */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
} fmt;
}
struct v4l2_pix_format //最终保存摄像头采集格式
{
width; //画面宽
height; //画面高
pixelformat; //画面采集格式
V4L2_PIX_FMT_YUYV --》yuv格式
V4L2_PIX_FMT_JPEG --》jpg格式
}
ioctl(camerafd,VIDIOC_S_FMT,你具体要设置的格式)
(3)申请缓冲区--》方便等一会摄像头启动以后画面可以往申请的缓冲区里面存放
用到的指令:VIDIOC_REQBUFS
用到的变量类型:struct v4l2_requestbuffers
{
count; //你要申请的缓冲块的个数,每个缓冲块只能存放一帧画面数据,一般申请4--8个缓冲块即可
type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
memory; //V4L2_MEMORY_MMAP
}
(4)分配缓冲区同时获取每个缓冲区的首地址和大小
为什么需要知道缓冲区的首地址?
可以通过首地址得到完整的画面数据(利用指针操作)
用到的指令:VIDIOC_QUERYBUF
用到的变量类型:struct v4l2_buffer
{
index; //刚才你申请的缓冲区的索引号,从0开始
type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
memory; //V4L2_MEMORY_MMAP
union {
__u32 offset; //保存缓冲块之间的地址偏移量
} m;
length; //保存分配给你的每个缓冲块的大小
}
映射得到每个缓冲块的首地址
mmap()
(5)让摄像头拍摄的画面入队
用到的指令:VIDIOC_QBUF
用到的变量类型:struct v4l2_buffer
(6)启动摄像头采集
用到的指令:VIDIOC_STREAMON
用到的变量类型:enum v4l2_buf_type //V4L2_BUF_TYPE_VIDEO_CAPTURE
(7)循环让摄像头拍摄的画面出队,入队,在开发板的lcd上把画面数据流显示出来(核心步骤)
while(1)
{
for(i=0; i<4; i++)
{
ioctl( 出队指令)
//把刚才出队的画面数据在lcd上显示出来
//出队的画面数据在哪里??
//画面数据是什么格式的,能不能直接在lcd上显示出来??
ioctl( 入队指令)
}
}
(8)关闭摄像头
用到的指令:VIDIOC_STREAMOFF
用到的变量类型:enum v4l2_buf_type //V4L2_BUF_TYPE_VIDEO_CAPTURE
3.YUV转换成RGB的数学公式
公式有很多不同的版本,区别在于转换的精度(最终得到的RGB数据的失真程序不同)不同而已
//版本1公式
R = Y + 1.4075( V - 128);
G = Y - 0.3455( U - 128) - 0.7169( V - 128);
B = Y + 1.779( U - 128);//版本2公式
r = 1164*(y-16)/1000 + 1596*(v-128)/1000;
g = 1164*(y-16)/1000 + 813*(v-128)/1000 - 391*(u-128)/1000;
b = 1164*(y-16)/1000 + 2018*(u-128)/1000;
YUV格式的画面,像素点的排列规则(编码规则)
#include <stdio.h>
#include <linux/videodev2.h> //跟V4L2有关的头文件
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define W 640
#define H 480
//自定义一个结构体用来存放每个缓冲块的首地址和大小
struct bufmsg
{
void *start; //保存每个缓冲块首地址
int somelen; //保存每个缓冲块的大小
};
//封装一个函数把一组YUV数据转换成一组ARGB数据
int yuvtoargb(int y,int u,int v)
{
int r,g,b;
int pix;
r = y + 1.4075*( v - 128);
g = y - 0.3455*( u - 128) - 0.7169*( v - 128);
b = y + 1.779*( u - 128);
//修正计算结果
if(r>255)
r=255;
if(g>255)
g=255;
if(b>255)
b=255;
if(r<0)
r=0;
if(g<0)
g=0;
if(b<0)
b=0;
//把rgb拼接成argb
pix=0x00<<24|r<<16|g<<8|b;
return pix;
}
//封装一个函数把一帧画面完整的yuv全部都转换argb
int allyuvtoargb(char *yuvdata,int *argbdata)
{
/*
yuvdata[0]---[3]一组YUYV数据 --》转换成两组ARGB
*/
int i,j;
for(i=0,j=0; i<W*H; i+=2,j+=4)
{
argbdata[i]=yuvtoargb(yuvdata[j],yuvdata[j+1],yuvdata[j+3]);
argbdata[i+1]=yuvtoargb(yuvdata[j+2],yuvdata[j+1],yuvdata[j+3]);
}
return 0;
}
int main()
{
int camerafd;
int lcdfd;
int *lcdmem;
int ret;
int i,j;
//打开lcd
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开lcd失败!\n");
return -1;
}
//映射得到lcd的首地址
lcdmem=mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射lcd失败!\n");
return -1;
}
//打开摄像头的驱动
camerafd=open("/dev/video7",O_RDWR);
if(camerafd==-1)
{
perror("打开摄像头驱动失败了!\n");
return -1;
}
//设置摄像头的采集格式
struct v4l2_format myfmt;
bzero(&myfmt,sizeof(myfmt));
myfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
myfmt.fmt.pix.width=W;
myfmt.fmt.pix.height=H;
myfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //YUV格式
ret=ioctl(camerafd,VIDIOC_S_FMT,&myfmt);
if(ret==-1)
{
perror("设置采集格式失败!\n");
return -1;
}
//申请缓冲区
struct v4l2_requestbuffers mybuf;
bzero(&mybuf,sizeof(mybuf));
mybuf.count=4; //申请4个缓冲块
mybuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
mybuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_REQBUFS,&mybuf);
if(ret==-1)
{
perror("申请缓冲区失败!\n");
return -1;
}
//定义结构体数组存放4个缓冲块的信息
struct bufmsg array[4];
//分配刚才你申请的缓冲区,顺便映射得到每个缓冲块的首地址
struct v4l2_buffer otherbuf;
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_QUERYBUF,&otherbuf);
if(ret==-1)
{
perror("分配缓冲区失败!\n");
return -1;
}
//映射得到每个缓冲块的首地址
array[i].somelen=otherbuf.length; //保存大小
array[i].start=mmap(NULL,otherbuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,otherbuf.m.offset);
if(array[i].start==NULL)
{
perror("映射首地址失败!\n");
return -1;
}
//顺便入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return -1;
}
}
//启动摄像头采集
enum v4l2_buf_type mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);
if(ret==-1)
{
perror("启动采集失败!\n");
return -1;
}
//定义一个数组存放一帧画面完整的argb数据
int argbbuf[W*H];
//循环出队入队显示视频流
while(1)
{
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
//出队
ret=ioctl(camerafd,VIDIOC_DQBUF,&otherbuf);
if(ret==-1)
{
perror("出队失败!\n");
return -1;
}
//把出队的画面数据在开发板的液晶屏上显示出来
//由于我们的摄像头拍摄的画面是YUV格式,不是RGB,液晶屏要求是ARGB格式
//需要把YUV--》ARGB格式才能在开发板的液晶屏上显示
allyuvtoargb(array[i].start,argbbuf);
//把转换得到的ARGB数据填充到开发板的液晶屏
/*
argbbuf[0]--argbbuf[W-1] 第一行像素点 --》lcdmem
argbbuf[W]--argbbuf[2*W-1] 第二行像素点 --》lcdmem+800
*/
for(j=0; j<H; j++)
memcpy(lcdmem+j*800,&argbbuf[j*W],W*4);
//入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return -1;
}
}
}
}
QT中摄像头编程
1.涉及到的类
QT += multimedia //添加这个库
QT += multimediawidgets
QCameraInfo 获取当前系统中所有的摄像头参数信息
QCamera 表示摄像头对象
QVideoWidget 摄像头显示画面的窗口2.思路步骤
第一步:QCameraInfo获取当前系统中所有可以使用的摄像头信息
[static] QList<QCameraInfo> QCameraInfo::availableCameras()
返回值:容器中存放了所有的摄像头信息
QString QCameraInfo::description() const
返回值:返回摄像头的描述信息
QString QCameraInfo::deviceName() const
返回值:返回摄像头的设备名称
第二步:创建一个摄像头对象
QCamera::QCamera(const QByteArray &deviceName, QObject *parent = nullptr)
参数:deviceName --》摄像头的设备名字
parent --》this指针
第三步:准备摄像头显示画面的窗口(在ui界面中拖一个QWidget过来)
QVideoWidget::QVideoWidget(QWidget *parent = nullptr)
参数:parent--》把ui->QWidget对象指针当参数传过来
设置窗口大小(跟ui->QWidget大小保持一致)
void QWidget::resize(int w, int h)
关联摄像头和窗口(让摄像头拍摄的画面自动在窗口中显示出来)
void QCamera::setViewfinder(QVideoWidget *viewfinder)
参数:viewfinder --》你刚才创建的那个窗口对象
显示窗口
void QWidget::show()
第四步:启动摄像头拍摄
void QCamera::start()
关闭摄像头
void QCamera::stop()3.注意的问题
QT中的摄像头类不能交叉编译到开发板上使用,不支持这multimedia和multimediawidgets两个类库
但是后面做阶段项目,需要用到QT,所以教大家把V4L2C语言写的摄像头代码融入到QT工程中
QT在Windows系统显示摄像头拍摄的画面
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//获取window系统中所有的摄像头信息
QList<QCameraInfo> myinfo=QCameraInfo::availableCameras();
//遍历容器打印摄像头信息
for(auto &x:myinfo)
{
//qDebug()<<"摄像头的描述信息: "<<x.description();
//qDebug()<<"摄像头的设备名字: "<<x.deviceName();
//在下拉框中显示摄像头的设备名字
ui->comboBox->addItem(x.deviceName());
}
}
MainWindow::~MainWindow()
{
delete ui;
}
//启动摄像头
void MainWindow::on_startbt_clicked()
{
//获取下拉框中你点选的设备名字
QString devname=ui->comboBox->currentText();
//创建摄像头对象
cam=new QCamera(devname.toUtf8());
//准备窗口
QVideoWidget *camwin=new QVideoWidget(ui->widget);
//调整窗口的大小
camwin->resize(ui->widget->width(),ui->widget->height());
//把摄像头跟窗口绑定
cam->setViewfinder(camwin);
//显示窗口
camwin->show();
//启动摄像头
cam->start();
}
//关闭摄像头
void MainWindow::on_closebt_clicked()
{
cam->stop();
}
V4L2摄像头融入到QT工程中
#include "mycamera.h"
//封装一个函数把一组YUV数据转换成一组ARGB数据
int yuvtoargb(int y,int u,int v)
{
int r,g,b;
int pix;
r = y + 1.4075*( v - 128);
g = y - 0.3455*( u - 128) - 0.7169*( v - 128);
b = y + 1.779*( u - 128);
//修正计算结果
if(r>255)
r=255;
if(g>255)
g=255;
if(b>255)
b=255;
if(r<0)
r=0;
if(g<0)
g=0;
if(b<0)
b=0;
//把rgb拼接成argb
pix=0x00<<24|r<<16|g<<8|b;
return pix;
}
//封装一个函数把一帧画面完整的yuv全部都转换argb
int allyuvtoargb(char *yuvdata,int *argbdata)
{
/*
yuvdata[0]---[3]一组YUYV数据 --》转换成两组ARGB
*/
int i,j;
for(i=0,j=0; i<W*H; i+=2,j+=4)
{
argbdata[i]=yuvtoargb(yuvdata[j],yuvdata[j+1],yuvdata[j+3]);
argbdata[i+1]=yuvtoargb(yuvdata[j+2],yuvdata[j+1],yuvdata[j+3]);
}
return 0;
}
mycamera::mycamera(QWidget *parent) : QMainWindow(parent)
{
}
void mycamera::camera_init()
{
int ret;
int i;
//打开lcd
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开lcd失败!\n");
return;
}
//映射得到lcd的首地址
lcdmem=(int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射lcd失败!\n");
return;
}
//打开摄像头的驱动
camerafd=open("/dev/video7",O_RDWR);
if(camerafd==-1)
{
perror("打开摄像头驱动失败了!\n");
return;
}
//设置摄像头的采集格式
struct v4l2_format myfmt;
bzero(&myfmt,sizeof(myfmt));
myfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
myfmt.fmt.pix.width=W;
myfmt.fmt.pix.height=H;
myfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //YUV格式
ret=ioctl(camerafd,VIDIOC_S_FMT,&myfmt);
if(ret==-1)
{
perror("设置采集格式失败!\n");
return;
}
//申请缓冲区
struct v4l2_requestbuffers mybuf;
bzero(&mybuf,sizeof(mybuf));
mybuf.count=4; //申请4个缓冲块
mybuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
mybuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_REQBUFS,&mybuf);
if(ret==-1)
{
perror("申请缓冲区失败!\n");
return;
}
//分配刚才你申请的缓冲区,顺便映射得到每个缓冲块的首地址
struct v4l2_buffer otherbuf;
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_QUERYBUF,&otherbuf);
if(ret==-1)
{
perror("分配缓冲区失败!\n");
return;
}
//映射得到每个缓冲块的首地址
array[i].somelen=otherbuf.length; //保存大小
array[i].start=mmap(NULL,otherbuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,otherbuf.m.offset);
if(array[i].start==NULL)
{
perror("映射首地址失败!\n");
return;
}
//顺便入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return;
}
}
//启动摄像头采集
enum v4l2_buf_type mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);
if(ret==-1)
{
perror("启动采集失败!\n");
return;
}
}
void mycamera::camera_capture()
{
int i,j;
int ret;
//定义一个数组存放一帧画面完整的argb数据
int argbbuf[W*H];
struct v4l2_buffer otherbuf;
//循环出队入队显示视频流
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
//出队
ret=ioctl(camerafd,VIDIOC_DQBUF,&otherbuf);
if(ret==-1)
{
perror("出队失败!\n");
return;
}
//把出队的画面数据在开发板的液晶屏上显示出来
//由于我们的摄像头拍摄的画面是YUV格式,不是RGB,液晶屏要求是ARGB格式
//需要把YUV--》ARGB格式才能在开发板的液晶屏上显示
allyuvtoargb((char *)(array[i].start),argbbuf);
//把转换得到的ARGB数据填充到开发板的液晶屏
/*
argbbuf[0]--argbbuf[W-1] 第一行像素点 --》lcdmem
argbbuf[W]--argbbuf[2*W-1] 第二行像素点 --》lcdmem+800
*/
for(j=0; j<H; j++)
memcpy(lcdmem+80+j*800,&argbbuf[j*W],W*4);
//入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return;
}
}
}
void mycamera::camera_uninit()
{
int i;
int ret;
//关闭摄像头
enum v4l2_buf_type mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMOFF,&mytype);
if(ret==-1)
{
perror("关闭she'x摄像头采集失败!\n");
return;
}
//关闭文件描述符
::close(lcdfd);
::close(camerafd);
//解除映射
munmap(lcdmem,800*480*4);
for(i=0; i<4; i++)
munmap(array[i].start,array[i].somelen);
}
定时器实现V4L2摄像头显示
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置超时时间
mytimer.setInterval(50); //50毫秒
//关联timeout信号
connect(&mytimer,SIGNAL(timeout()),this,SLOT(fun()));
//初始化摄像头
cam.camera_init();
}
MainWindow::~MainWindow()
{
//关闭摄像头
cam.camera_uninit();
delete ui;
}
//启动摄像头--》启动定时器
void MainWindow::on_startbt_clicked()
{
mytimer.start();
}
//关闭摄像头--》关闭定时器
void MainWindow::on_closebt_clicked()
{
mytimer.stop();
}
void MainWindow::fun()
{
//捕捉摄像头画面
cam.camera_capture();
}
多线程实现V4L2摄像头显示
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//初始化摄像头
cam.camera_init();
}
MainWindow::~MainWindow()
{
//关闭摄像头
cam.camera_uninit();
delete ui;
}
//启动摄像头--》开启线程
void MainWindow::on_startbt_clicked()
{
cam.start(); //run()函数自动执行
}
//关闭摄像头--》关闭线程
void MainWindow::on_closebt_clicked()
{
qDebug()<<"close close";
cam.terminate();
}
#include "mycamera.h"
//封装一个函数把一组YUV数据转换成一组ARGB数据
int yuvtoargb(int y,int u,int v)
{
int r,g,b;
int pix;
r = y + 1.4075*( v - 128);
g = y - 0.3455*( u - 128) - 0.7169*( v - 128);
b = y + 1.779*( u - 128);
//修正计算结果
if(r>255)
r=255;
if(g>255)
g=255;
if(b>255)
b=255;
if(r<0)
r=0;
if(g<0)
g=0;
if(b<0)
b=0;
//把rgb拼接成argb
pix=0x00<<24|r<<16|g<<8|b;
return pix;
}
//封装一个函数把一帧画面完整的yuv全部都转换argb
int allyuvtoargb(char *yuvdata,int *argbdata)
{
/*
yuvdata[0]---[3]一组YUYV数据 --》转换成两组ARGB
*/
int i,j;
for(i=0,j=0; i<W*H; i+=2,j+=4)
{
argbdata[i]=yuvtoargb(yuvdata[j],yuvdata[j+1],yuvdata[j+3]);
argbdata[i+1]=yuvtoargb(yuvdata[j+2],yuvdata[j+1],yuvdata[j+3]);
}
return 0;
}
mycamera::mycamera()
{
}
void mycamera::camera_init()
{
int ret;
int i;
//打开lcd
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开lcd失败!\n");
return;
}
//映射得到lcd的首地址
lcdmem=(int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射lcd失败!\n");
return;
}
//打开摄像头的驱动
camerafd=open("/dev/video7",O_RDWR);
if(camerafd==-1)
{
perror("打开摄像头驱动失败了!\n");
return;
}
//设置摄像头的采集格式
struct v4l2_format myfmt;
bzero(&myfmt,sizeof(myfmt));
myfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
myfmt.fmt.pix.width=W;
myfmt.fmt.pix.height=H;
myfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //YUV格式
ret=ioctl(camerafd,VIDIOC_S_FMT,&myfmt);
if(ret==-1)
{
perror("设置采集格式失败!\n");
return;
}
//申请缓冲区
struct v4l2_requestbuffers mybuf;
bzero(&mybuf,sizeof(mybuf));
mybuf.count=4; //申请4个缓冲块
mybuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
mybuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_REQBUFS,&mybuf);
if(ret==-1)
{
perror("申请缓冲区失败!\n");
return;
}
//分配刚才你申请的缓冲区,顺便映射得到每个缓冲块的首地址
struct v4l2_buffer otherbuf;
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_QUERYBUF,&otherbuf);
if(ret==-1)
{
perror("分配缓冲区失败!\n");
return;
}
//映射得到每个缓冲块的首地址
array[i].somelen=otherbuf.length; //保存大小
array[i].start=mmap(NULL,otherbuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,otherbuf.m.offset);
if(array[i].start==NULL)
{
perror("映射首地址失败!\n");
return;
}
//顺便入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return;
}
}
//启动摄像头采集
enum v4l2_buf_type mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);
if(ret==-1)
{
perror("启动采集失败!\n");
return;
}
}
void mycamera::camera_capture()
{
int i,j;
int ret;
//定义一个数组存放一帧画面完整的argb数据
int argbbuf[W*H];
struct v4l2_buffer otherbuf;
//循环出队入队显示视频流
for(i=0; i<4; i++)
{
bzero(&otherbuf,sizeof(otherbuf));
otherbuf.index=i;
otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
otherbuf.memory=V4L2_MEMORY_MMAP;
//出队
ret=ioctl(camerafd,VIDIOC_DQBUF,&otherbuf);
if(ret==-1)
{
perror("出队失败!\n");
return;
}
//把出队的画面数据在开发板的液晶屏上显示出来
//由于我们的摄像头拍摄的画面是YUV格式,不是RGB,液晶屏要求是ARGB格式
//需要把YUV--》ARGB格式才能在开发板的液晶屏上显示
allyuvtoargb((char *)(array[i].start),argbbuf);
//把转换得到的ARGB数据填充到开发板的液晶屏
/*
argbbuf[0]--argbbuf[W-1] 第一行像素点 --》lcdmem
argbbuf[W]--argbbuf[2*W-1] 第二行像素点 --》lcdmem+800
*/
for(j=0; j<H; j++)
memcpy(lcdmem+80+j*800,&argbbuf[j*W],W*4);
//入队
ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
if(ret==-1)
{
perror("入队失败!\n");
return;
}
}
}
void mycamera::camera_uninit()
{
int i;
int ret;
//关闭摄像头
enum v4l2_buf_type mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMOFF,&mytype);
if(ret==-1)
{
perror("关闭she'x摄像头采集失败!\n");
return;
}
//关闭文件描述符
::close(lcdfd);
::close(camerafd);
//解除映射
munmap(lcdmem,800*480*4);
for(i=0; i<4; i++)
munmap(array[i].start,array[i].somelen);
}
void mycamera::run()
{
while(1)
{
//死循环捕捉画面显示
camera_capture();
}
}
jpeg库的移植
1.jpeg格式的图片
用算法压缩过,显示的时候需要解压缩数据还原得到原始的像素点颜色值
借助jpeg库的接口函数实现jpeg图片的显示/把RGB数据保存成jpeg图片2.移植步骤
第一步:把jpeg库的源码拷贝到家目录解压,然后执行configure脚本
经验:./configure --help 查看configure脚本的参数选项介绍
./configure --prefix=/xxx某个路径 CC=arm-linux-gcc --host=arm-linux --enable-shared --enable-static
--prefix 指定编译产生的头文件/库文件/可执行程序的安装路径
CC 指定编译工具
--enable-shared 编译生成动态库
--enable-static 编译生成静态库
第二步:执行make自动编译程序
第三步:执行make install把生成的头文件/库文件/可执行程序自动存放到第一步--prefix指定的路径下
使用jpeg库接口实现jpeg图片的显示
1.思路步骤
第一步:定义解压缩结构体变量和处理错误的结构体变量
解压缩结构体
struct jpeg_decompress_struct
{
image_width; //图片原始的宽
image_height //图片原始的高
num_components; //图片的色深(每个像素点占多少字节) --》3个字节RGB
unsigned int scale_num
unsigned int scale_denom scale_num=1 scale_denom=2 //图片的宽高缩小一半
scale_num=1 scale_denom=4 //图片的宽高缩小1/4
output_width; //如果你按比例缩小了图片,表示缩小以后宽
output_height; //如果你按比例缩小了图片,表示缩小以后高
}
初始化解压缩结构体
jpeg_create_decompress(cinfo)
参数:cinfo --》解压缩结构体指针
处理错误的结构体变量
struct jpeg_error_mgr
初始化错误结构体变量
jpeg_std_error(struct jpeg_error_mgr * err);
参数: err --》用来保存出错信息
第二步:指定解压缩数据源(指定你要解压缩哪张图片)
fopen(你要显示的jpg图片)
jpeg_stdio_src(j_decompress_ptr cinfo, FILE * infile);
参数:cinfo --》解压缩结构体指针
infile --》你要显示的图片
第三步:读取jpeg图片的头信息
jpeg_read_header(j_decompress_ptr cinfo,boolean require_image);
参数:cinfo --》解压缩结构体指针
require_image --》true
第四步:开始解压缩
jpeg_start_decompress(j_decompress_ptr cinfo);
参数:cinfo --》解压缩结构体指针
第五步:关键步骤,读取解压缩得到的原始RGB数据,然后把RGB数据填充到开发板的液晶屏
jpeg_read_scanlines(j_decompress_ptr cinfo,JSAMPARRAY scanlines,JDIMENSION max_lines);
参数:cinfo --》解压缩结构体指针
scanlines --》存放你读取到的解压缩以后的RGB数据
char *buf=malloc(按照jpg图片一行的大小分配)
max_lines --》你打算读取多少行RGB数据,一般一次读取一行
jpeg_read_scanlines(解压缩结构体指针,&buf,1); //读取一行,读取的是RGB数据-->转换ARGB填充到液晶屏就可以了
第六步:收尾
jpeg_finish_decompress(j_decompress_ptr cinfo);
jpeg_destroy_decompress(j_decompress_ptr cinfo);2.编译命令
arm-linux-gcc showjpg.c libjpeg.so.9.1.0 -o showjpg -I./include
反向操作把RGB数据--》保存成jpg图片
1.经典错误思路(jpg图片需要压缩)
open(新建空白JPG文件)
write(RGB写入到空白的jpg文件中)
close()2.正确思路
第一步:定义压缩结构体变量和处理错误的结构体变量
压缩结构体
struct jpeg_compress_struct
{
image_width;
image_height;
input_components; //色深 3
in_color_space //图片的编码方式 JCS_RGB
}
初始化压缩结构体
jpeg_create_compress(cinfo)
参数:cinfo --》压缩结构体指针
处理错误的结构体变量
struct jpeg_error_mgr
初始化错误结构体变量
jpeg_std_error(struct jpeg_error_mgr * err);
第二步:设置压缩参数
jpeg_set_defaults(j_compress_ptr cinfo);
第三步:设置压缩比
jpeg_set_quality(j_compress_ptr cinfo, int quality, boolean force_baseline);
参数:cinfo --》压缩结构体指针
quality --》压缩比 取值70--100之间 值越大,图片清晰度高一点
force_baseline --》true
第四步:指定压缩数据源
fopen(新建空白jpg)
jpeg_stdio_dest(j_compress_ptr cinfo, FILE * outfile);
第五步:开始压缩
jpeg_start_compress(j_compress_ptr cinfo,boolean write_all_tables);
第六步:把压缩的数据一行行写入到刚才的空白jpg图片中
jpeg_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines);
第七步:收尾
jpeg_finish_compress(j_decompress_ptr cinfo);
jpeg_destroy_compress(j_decompress_ptr cinfo);
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include "jpeglib.h" //jpg库的头文件
int main(int argc,char **argv)
{
int i,j;
int lcdfd;
int *lcdmem;
//定义解压缩结构体变量和处理错误的结构体变量
struct jpeg_decompress_struct mydem;
jpeg_create_decompress(&mydem);
struct jpeg_error_mgr myerror;
mydem.err=jpeg_std_error(&myerror);
//打开液晶屏
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开lcd失败了!\n");
return -1;
}
//映射得到lcd的首地址
lcdmem=mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射lcd失败!\n");
return -1;
}
//指定解压缩数据源
FILE *myfile=fopen(argv[1],"r+");
if(myfile==NULL)
{
perror("打开失败!\n");
return -1;
}
jpeg_stdio_src(&mydem,myfile);
//读取头信息
jpeg_read_header(&mydem,true);
//开始解压缩
jpeg_start_decompress(&mydem);
//打印图片信息
printf("图片的宽: %d\n",mydem.image_width);
printf("图片的高: %d\n",mydem.image_height);
printf("图片的色深: %d\n",mydem.num_components);
//循环读取解压缩后的RGB数据,填充到开发板的lcd上
//定义指针用来保存读取得到的一行RGB数据
char *buf=malloc(mydem.image_width*3);
for(i=0; i<mydem.image_height; i++)
{
//一次读取一行RGB数据
jpeg_read_scanlines(&mydem,(JSAMPARRAY)&buf,1);
//把一行RGB转成ARGB填充到lcd上
/*
buf[0]--[2] 三个字节一组RGB
*/
for(j=0; j<mydem.image_width; j++)
//思路一
*(lcdmem+i*800+j)=0x00<<24|buf[3*j]<<16|buf[3*j+1]<<8|buf[3*j+2];
//思路二
//临时数组=0x00<<24|buf[3*j]<<16|buf[3*j+1]<<8|buf[3*j+2];
//memcpy(lcdmem+偏移量,临时数组)
}
//收尾
jpeg_finish_decompress(&mydem);
jpeg_destroy_decompress(&mydem);
close(lcdfd);
fclose(myfile);
munmap(lcdmem,800*480*4);
free(buf);
return 0;
}
任意位置显示任意大小的jpg
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "jpeglib.h" //jpg库的头文件
//封装任意位置显示任意大小jpg
int show_anyjpg(int x,int y,const char *jpgpath)
{
int i,j;
int lcdfd;
int *lcdmem;
//定义解压缩结构体变量和处理错误的结构体变量
struct jpeg_decompress_struct mydem;
jpeg_create_decompress(&mydem);
struct jpeg_error_mgr myerror;
mydem.err=jpeg_std_error(&myerror);
//打开液晶屏
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开lcd失败了!\n");
return -1;
}
//映射得到lcd的首地址
lcdmem=mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射lcd失败!\n");
return -1;
}
//指定解压缩数据源
FILE *myfile=fopen(jpgpath,"r+");
if(myfile==NULL)
{
perror("打开失败!\n");
return -1;
}
jpeg_stdio_src(&mydem,myfile);
//读取头信息
jpeg_read_header(&mydem,true);
//开始解压缩
jpeg_start_decompress(&mydem);
//打印图片信息
printf("图片的宽: %d\n",mydem.image_width);
printf("图片的高: %d\n",mydem.image_height);
printf("图片的色深: %d\n",mydem.num_components);
//循环读取解压缩后的RGB数据,填充到开发板的lcd上
//定义指针用来保存读取得到的一行RGB数据
char *buf=malloc(mydem.image_width*3);
//定义数组临时存放一行ARGB数据
int tempbuf[mydem.image_width];
for(i=0; i<mydem.image_height; i++)
{
//一次读取一行RGB数据
jpeg_read_scanlines(&mydem,(JSAMPARRAY)&buf,1);
//把一行RGB转成ARGB填充到lcd的(x,y)开始
/*
buf[0]--[2] 三个字节一组RGB
第一行(x,y)位置: lcdmem+y*800+x
第二行(x,y+1)位置:lcdmem+(y+1)*800+x
*/
for(j=0; j<mydem.image_width; j++)
tempbuf[j]=0x00<<24|buf[3*j]<<16|buf[3*j+1]<<8|buf[3*j+2];
//把tempbuf中一行ARGB数据拷贝到液晶屏对应的位置
memcpy(lcdmem+(y+i)*800+x,tempbuf,4*mydem.image_width);
}
//收尾
jpeg_finish_decompress(&mydem);
jpeg_destroy_decompress(&mydem);
close(lcdfd);
fclose(myfile);
munmap(lcdmem,800*480*4);
free(buf);
return 0;
}
int main(int argc,char **argv)
{
show_anyjpg(100,78,"/1.jpg");
}
jpg图片按照比例缩小
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include "jpeglib.h" //jpg库的头文件
int main(int argc,char **argv)
{
int i,j;
int lcdfd;
int *lcdmem;
//定义解压缩结构体变量和处理错误的结构体变量
struct jpeg_decompress_struct mydem;
jpeg_create_decompress(&mydem);
struct jpeg_error_mgr myerror;
mydem.err=jpeg_std_error(&myerror);
//打开液晶屏
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开lcd失败了!\n");
return -1;
}
//映射得到lcd的首地址
lcdmem=mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射lcd失败!\n");
return -1;
}
//指定解压缩数据源
FILE *myfile=fopen(argv[1],"r+");
if(myfile==NULL)
{
perror("打开失败!\n");
return -1;
}
jpeg_stdio_src(&mydem,myfile);
//读取头信息
jpeg_read_header(&mydem,true);
//设置图片的缩小比例
mydem.scale_num=1;
mydem.scale_denom=3; //缩小1/2
//开始解压缩
jpeg_start_decompress(&mydem);
//打印图片信息
printf("图片原始的宽: %d\n",mydem.image_width);
printf("图片原始的高: %d\n",mydem.image_height);
printf("图片缩小以后的宽: %d\n",mydem.output_width);
printf("图片缩小以后的高: %d\n",mydem.output_height);
printf("图片的色深: %d\n",mydem.num_components);
//循环读取解压缩后的RGB数据,填充到开发板的lcd上
//定义指针用来保存读取得到的一行RGB数据
char *buf=malloc(mydem.output_width*3);
for(i=0; i<mydem.output_height; i++)
{
//一次读取一行RGB数据
jpeg_read_scanlines(&mydem,(JSAMPARRAY)&buf,1);
//把一行RGB转成ARGB填充到lcd上
/*
buf[0]--[2] 三个字节一组RGB
*/
for(j=0; j<mydem.output_width; j++)
//思路一
*(lcdmem+i*800+j)=0x00<<24|buf[3*j]<<16|buf[3*j+1]<<8|buf[3*j+2];
//思路二
//临时数组=0x00<<24|buf[3*j]<<16|buf[3*j+1]<<8|buf[3*j+2];
//memcpy(lcdmem+偏移量,临时数组)
}
//收尾
jpeg_finish_decompress(&mydem);
jpeg_destroy_decompress(&mydem);
close(lcdfd);
fclose(myfile);
munmap(lcdmem,800*480*4);
free(buf);
return 0;
}
使用ALSA库实现录音和播放录音
一.音频的输入
Linux系统有一个开源的音频库 --------- alsa库,实现了录音和播放的功能,alsa库包含如下内容:
alsa-lib-1.0.22.tar.bz2 -------- alsa的核心支持库
alsa-utils-1.0.22.tar.bz2 ------ alsa的工具库
1.移植alsa库
移植三部曲 ---------------- 配置 编译 安装
拷贝到Ubuntu非共享目录解压,先移植核心库,再移植工具库
(1)alsa核心库
1)配置
./configure --host=arm-linux --prefix=/home/gec/alsa --disable-python
2)编译
make
3)安装
make install
(2)alsa工具库
1)配置
./configure --host=arm-linux --prefix=/home/gec/alsa --with-alsa-prefix=/home/gec/alsa/lib --with-alsa-inc-prefix=/home/gec/alsa/include --disable-alsamixer --disable-xmlto
2)编译
make
3)安装
make install
(3)将编译生成的目录(alsa)打包,拷贝到开发板,解压后将需要的文件拷贝到对应目录
tar -cJvf alsa.tar.xz alsa
aplay xxx.WAV