一、环境介绍
操作系统介绍:ubuntu 18.04 、windows、Android
QT版本: 5.12.6
摄像头: USB摄像头、虚拟机挂载本机自带摄像头
二、功能介绍
在子线程里通过QVideoProbe捕获摄像头一帧数据,处理之后(加时间水印),再通过信号/槽机制发送给主线程,在UI界面显示。
子线程方式采用moveToThread方式实现,因为需要用到QVideoProbe的槽函数,需要事件机制,使用子类化方式使用子线程不方便,直接用moveToThread方式实现。
三、核心代码
main.cpp
#include "widget.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Camera.cpp
#include "Camera.h"
/*
函数功能: 将YUV数据转为RGB格式
函数参数:
unsigned char *yuv_buffer: YUV源数据
unsigned char *rgb_buffer: 转换之后的RGB数据
int iWidth,int iHeight : 图像的宽度和高度
*/
void yuyv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight)
{
int x;
int z=0;
unsigned char *ptr = rgb_buffer;
unsigned char *yuyv= yuv_buffer;
for (x = 0; x < iWidth*iHeight; x++)
{
int r, g, b;
int y, u, v;
if (!z)
y = yuyv[0] << 8;
else
y = yuyv[2] <> 8;
g = (y - (88 * u) - (183 * v)) >> 8;
b = (y + (454 * u)) >> 8;
*(ptr++) = (r > 255) ? 255 : ((r 255) ? 255 : ((g 255) ? 255 : ((b < 0) ? 0 : b);
if(z++)
{
z = 0;
yuyv += 4;
}
}
}
void NV21_TO_RGB24(unsigned char *yuyv, unsigned char *rgb, int width, int height)
{
const int nv_start = width * height ;
int index = 0, rgb_index = 0;
uint8_t y, u, v;
int r, g, b, nv_index = 0,i, j;
for(i = 0; i < height; i++){
for(j = 0; j 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 setSource(camera); // Returns true, hopefully.
connect(m_pProbe, SIGNAL(videoFrameProbed(QVideoFrame)),this, SLOT(slotOnProbeFrame(QVideoFrame)), Qt::QueuedConnection);
}
/*配置摄像头捕 QCamera *camera;
QVideoProbe *m_pProbe;获模式为帧捕获模式*/
//camera->setCaptureMode(QCamera::CaptureStillImage); //如果在Linux系统下运行就这样设置
camera->setCaptureMode(QCamera::CaptureVideo);//如果在android系统下运行就这样设置
/*启动摄像头*/
camera->start();
/*设置摄像头的采集帧率和分辨率*/
QCameraViewfinderSettings settings;
settings.setPixelFormat(QVideoFrame::Format_YUYV); //设置像素格式 Android上只支持NV21格式
settings.setResolution(QSize(VIDEO_WIDTH,VIDEO_HEIGHT)); //设置摄像头的分辨率
camera->setViewfinderSettings(settings);
}
void VideoReadThread_0::slotOnProbeFrame(const QVideoFrame &frame)
{
//qDebug()<<"子线程ProbeFrame槽函数的ID:"<<QThread::currentThreadId();
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
unsigned char rgb_buffer[VIDEO_WIDTH*VIDEO_HEIGHT*3];
if(cloneFrame.pixelFormat()==QVideoFrame::Format_YUYV)
{
yuyv_to_rgb(cloneFrame.bits(),rgb_buffer,cloneFrame.width(),cloneFrame.height());
}
else if(cloneFrame.pixelFormat()==QVideoFrame::Format_NV21)
{
NV21_TO_RGB24(cloneFrame.bits(),rgb_buffer,cloneFrame.width(),cloneFrame.height());
}
else
{
qDebug("当前格式编码为%1,暂时不支持转换.\n");
return;
}
cloneFrame.unmap();
//加载图片数据
QImage image(rgb_buffer,
cloneFrame.width(),
cloneFrame.height(),
QImage::Format_RGB888);
//绘制图片水印
QDateTime dateTime(QDateTime::currentDateTime());
//时间效果: 2020-03-05 16:25::04 周一
QString qStr="";
qStr+=dateTime.toString("yyyy-MM-dd hh:mm:ss ddd");
QPainter pp(&image);
QPen pen = QPen(Qt::white);
pp.setPen(pen);
pp.drawText(QPointF(0,20),qStr);
//提取RGB数据
unsigned char *p=rgb_buffer;
for(int i=0;i<image.height();i++)
{
for(int j=0;j<image.width();j++)
{
QRgb rgb=image.pixel(j,i);
*p++=qRed(rgb);
*p++=qGreen(rgb);
*p++=qBlue(rgb);
}
}
emit VideoDataOutput(image); //发送信号
}
//停止视频采集
void VideoReadThread_0::stop()
{
qDebug()<stop();
delete camera;
camera=nullptr;
}
if(m_pProbe)
{
delete m_pProbe;
m_pProbe=nullptr;
}
}
//执行线程
void VideoReadThread_0::run()
{
stop();
Camear_Init();
qDebug()<<"摄像头开始采集数据";
qDebug()<<"子线程run槽函数的ID:"<<QThread::currentThreadId();
}
camera.h
#ifndef CAMERA_H
#define CAMERA_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //这五个是QT处理音频的库
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //这五个是QT处理音频的库
#include
#include
#include
#include
//视频输出尺寸
#define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480
class VideoReadThread_0:public QObject
{
Q_OBJECT
public:
QCamera *camera;
QVideoProbe *m_pProbe;
VideoReadThread_0(QObject* parent=nullptr):QObject(parent){camera=nullptr;m_pProbe=nullptr;}
~VideoReadThread_0(){}
void Camear_Init(void);
public slots:
void slotOnProbeFrame(const QVideoFrame &frame);
void run();
void stop();
signals:
void VideoDataOutput(QImage ); //输出信号
};
//视频音频编码类
class VideoAudioEncode
{
public:
/*继续采集标志*/
bool run_flag;
bool mode; //0 表示保存视频 1表示推流
/*视频相关*/
QMutex video_encode_mutex;
QWaitCondition video_WaitConditon;
QCameraInfo camera; //当前选择的摄像头
/*音频相关*/
QAudioDeviceInfo audio;
QMutex audio_encode_mutex;
QQueue audio_data_queue;
};
extern class VideoAudioEncode videoaudioencode_0;
#endif // CAMERA_H
mainwindow.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug()<<"主线程的ID:"<setWindowTitle("监控设备");
//启动摄像头的信号
connect(this,SIGNAL(StartWorkThread()),work_class,SLOT(run()));
//释放资源-释放摄像头
connect(this,SIGNAL(Stop_VideoAudioEncode_0()),work_class,SLOT(stop()));
//连接摄像头采集信号,在主线程实时显示视频画面
connect(work_class,SIGNAL(VideoDataOutput(QImage)),this,SLOT(VideoDataDisplay_0(QImage)));
//将类移动到子线程工作
work_class->moveToThread(work_thread);
}
Widget::~Widget()
{
delete ui;
}
//驾驶室:视频刷新显示
void Widget::VideoDataDisplay_0(QImage image)
{
QPixmap my_pixmap;
my_pixmap.convertFromImage(image.copy());
ui->label->setPixmap(my_pixmap);
}
void Widget::on_pushButton_clicked()
{
/*2. 获取摄像头列表*/
video_dev_list.clear();
ui->comboBox->clear();
video_dev_list=QCameraInfo::availableCameras();
for(int i=0;i
mainwindow.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include "Camera.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
QList video_dev_list;
VideoReadThread_0 *work_class;
QThread *work_thread;
private slots:
void on_pushButton_clicked();
void VideoDataDisplay_0(QImage image);
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
signals:
void StartWorkThread();
void Stop_VideoAudioEncode_0();//停止线程
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
四、运行环境说明
在windows、Android、ubuntu下都可正常使用。
主要注意的地方: 在windows下有些摄像头不支持YUYV格式输出设置,需要在代码里根据情况稍微修改。
下面公众号有全套基础QT\C++\C\单片机教程。
作者:DS小龙哥