这一功能需要使用摄像头 则需要链接多媒体模块和多媒体工具模块 在pro文件中加上:
QT += multimedia multimediawidgets
使用Qt的3个类实现画面显示
QCamera:摄像头对象
QCameraViewfinder:用于实时显示摄像头传来的图像的控件
QCameraImageCapture:用于捕获摄像头图像实现拍照功能
测试代码如下,在窗口类中定义即可
pCamera =new QCamera(this); //创建摄像头对象
pViewfinder =new QCameraViewfinder(this); //创建实时显示摄像头图像的对象
pImageCapture =new QCameraImageCapture(pCamera); //创建截取摄像头图像的对象
//创建并设置按钮对象
QPushButton* button1 =new QPushButton("拍照");
QPushButton* button2 =new QPushButton("退出");
//创建并设置布局对象
QVBoxLayout* mainLayout =new QVBoxLayout(this);
mainLayout->addWidget(pViewfinder); //将取景器加入主界面中
mainLayout->addWidget(button1);
mainLayout->addWidget(button2);
pCamera->setViewfinder(pViewfinder); //给camera设置viewfinder
pCamera->start(); //开始摄像
分析:
1、在QWidget类上显示视频信息,一般使用paintEvent()显示图像,这样做CPU的占用率会很高,工程健壮性很低。
2、摄像头的捕捉数据需要存储成视频文件,使用这种方法不利于对图像帧的存储操作。
QOpenGLWidget
为解决CPU占用率过高的问题,显示视频的类,让它继承QOpenGLWidget,这样,在嵌入式设备上面,使用的是gpu渲染,而不是cpu,然后重新使用paintEvent()函数。
QOpenGLWidget类是用于渲染OpenGL图形。
除了可以选择使用QPainter和标准的OpenGL渲染图形,QOpenGLWidget类提供了在Qt应用程序中显示OpenGL图形的功能。它使用起来非常简单:新建类继承于QOpenGLWidget,使用方法就像继承于QWidget类子类一样。
QOpenGLWidget类提供了三个方便的虚函数,可以在新建的子类中重新实现以完成OpenGL的任务:
paintGL()—渲染OpenGL场景,需要更新Widget时就会调用。
resizeGL()—设置OpenGL视口,投影等。每当调整Widget的大小时(第一次显示窗口Widget时会调用它)。
initializeGL()—建立OpenGL的资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。
如果需要从paintGL()以外的地方触发重绘(一个典型的例子是使用定时器为场景设置动画),应该调用widget的update()函数来进行更新。
实现代码如下:
#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H
#include <QWidget>
#include <QOpenGLWidget>
#include <QImage>
class OpenGLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
OpenGLWidget(QWidget *parent = nullptr);
QImage m_CameraFrame;
protected:
void paintGL() override;
public slots:
void showCameraFrameSlot(QImage image);
};
#endif // OPENGLWIDGET_H
#include "openglwidget.h"
#include <QPainter>
OpenGLWidget::OpenGLWidget(QWidget *parent)
{
}
void OpenGLWidget::paintGL()
{
QPainter painter(this);
if (m_CameraFrame.size().width() <= 0)
{
return;
}
QImage _image = m_CameraFrame.scaled(this->size());
painter.drawImage(0, 0, _image);
}
void OpenGLWidget::showCameraFrameSlot(QImage image)
{
m_CameraFrame = image;
this->update();
}
Qt使用QAbstractVideoSurface捕获视频帧(信号槽方式)
Qt中捕获视频流方式:用QCamera::setViewfinder(QAbstractVideoSurface *surface)
QAbstractVideoSurface类是视频演示表面的基类。
QAbstractVideoSurface类定义视频制作者用于与视频演示表面交互操作的标准接口。您可以将此接口子类化,以接收来自解码媒体或相机等源的视频帧,以执行您自己的处理。
简单来说通过继承QAbstractVideoSurface类,重新实现他的一些虚函数对我们获取的视频帧数据进行处理,存储成我们需要的视频文件
实现代码如下:
#ifndef CAMERAVIDEOSURFACE_H
#define CAMERAVIDEOSURFACE_H
#include <QAbstractVideoSurface>
#include <QDebug>
#include "sqldata.h"
extern "C"
{
//avcodec:编解码(最重要的库)
#include <libavcodec/avcodec.h>
//avformat:封装格式处理
#include <libavformat/avformat.h>
//swscale:视频像素数据格式转换
#include <libswscale/swscale.h>
//avdevice:各种设备的输入输出
#include <libavdevice/avdevice.h>
//avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
//音频采样数据格式库
#include <libswresample/swresample.h>
}
class CameraVideoSurface : public QAbstractVideoSurface
{
Q_OBJECT
public:
CameraVideoSurface(QObject *parent = NULL);
~CameraVideoSurface();
void setCameraResolution(const QSize &size);
void escEvent();
void InitEncoder();
protected:
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const override;
bool present(const QVideoFrame &frame) override;
private slots:
void cameraStopSlot();
signals:
void showFrame(QImage image);
private:
void Encode(AVFrame *frame);
SqlData *pSqlData;
int FirstImg;
private:
AVFormatContext *pOutputFormatCtx;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVStream *pOutputStream;
AVPacket *packet;
AVFrame *yuvFrame;
QString FileName;
QString OutPutFileName;
struct SwsContext *image_convert_ctx;
};
#endif // CAMERAVIDEOSURFACE_H
#include "cameravideosurface.h"
#include "allwidget.h"
#include <QDateTime>
CameraVideoSurface::CameraVideoSurface(QObject *parent) : QAbstractVideoSurface(
parent),
pOutputFormatCtx(NULL),
pCodec(NULL),
pOutputStream(NULL),
packet(NULL),
yuvFrame(NULL),
image_convert_ctx(NULL)
{
// this->InitEncoder();
}
CameraVideoSurface::~CameraVideoSurface()
{
av_write_trailer(pOutputFormatCtx);
avio_close(pOutputFormatCtx->pb);
avformat_close_input(&pOutputFormatCtx);
av_frame_free(&yuvFrame);
av_packet_free(&packet);
avcodec_close(pCodecCtx);
}
void CameraVideoSurface::setCameraResolution(const QSize &size)
{
this->setNativeResolution(size);
}
QList<QVideoFrame::PixelFormat> CameraVideoSurface::supportedPixelFormats
(QAbstractVideoBuffer::HandleType handleType) const
{
QList<QVideoFrame::PixelFormat > pixelFormats;
pixelFormats.append(QVideoFrame::Format_RGB32);
pixelFormats.append(QVideoFrame::Format_YUV420P);
return pixelFormats;
}
bool CameraVideoSurface::present(const QVideoFrame &frame)
{
if(!AllWidget::pLoginWidget->CurUser.isEmpty() && !AllWidget::pSettingWidget->pSettingPath->ImagePath.isEmpty()){
//判断 是否有用户登录 是否处于设置路径界面
if (frame.isValid()){
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
QImage image(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
image = image.mirrored(true, true);
// rgb 转 yuv
uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
data[0] = (uint8_t *)image.constBits();
int linesize[AV_NUM_DATA_POINTERS] = {0};
linesize[0] = pCodecCtx->width * 4;
sws_scale(image_convert_ctx, data, linesize, 0, pCodecCtx->height,
yuvFrame->data, yuvFrame->linesize);
// 编码
this->Encode(yuvFrame);
emit showFrame(image);
if(FirstImg == 0){
image.save(QString("%1.jpg").arg(FileName)); //保存第一帧图片 用于视频展示列表的封面
FirstImg++;
}
cloneFrame.unmap();
return true;
}
return false;
}
return false;
}
void CameraVideoSurface::InitEncoder()
{
FirstImg = 0;
av_register_all();
avformat_network_init();
avcodec_register_all();
FileName = AllWidget::pSettingWidget->pSettingPath->VideoPath;
QString CurDate = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
FileName = QString("%1/%2").arg(FileName).arg(CurDate);
OutPutFileName = QString("%1.h264").arg(FileName);
qDebug() << OutPutFileName;
QString encoderName = "libx264";
//QString rtmpAddress = "rtmp://192.168.1.111/live/livestream";
pCodec = avcodec_find_encoder_by_name(encoderName.toStdString().c_str());
if(NULL == pCodec){
qDebug() <<"查找视频编码器失败!";
return;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if(NULL == pCodecCtx){
qDebug() <<"开辟编解码器上下文";
return;
}
// 输入样本参数
pCodecCtx->bit_rate = 400000;
pCodecCtx->width = 1280/2;
pCodecCtx->height = 720/2;
pCodecCtx->time_base = {1, 25};
pCodecCtx->framerate = {25, 1};
pCodecCtx->gop_size = 10;
pCodecCtx->max_b_frames = 1;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
if(AV_CODEC_ID_H264 == pCodecCtx->codec_id){
av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);
av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
}
// 打开编码器
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
qDebug() <<"打开编码器失败 !";
return;
}
pOutputFormatCtx = avformat_alloc_context();
if(NULL == pOutputFormatCtx){
qDebug() <<"视频封装器开辟失败!";
return;
}
AVOutputFormat *outputFormat = av_guess_format(NULL, OutPutFileName.toStdString().c_str(), NULL);
if(NULL == outputFormat){
qDebug() <<"猜测outputformat失败 !";
return;
}
pOutputFormatCtx->oformat = outputFormat;
// oprn url
if(avio_open(&pOutputFormatCtx->pb, OutPutFileName.toStdString().c_str(), AVIO_FLAG_READ_WRITE) < 0){
qDebug() <<"打开输出文件失败!";
return;
}
pOutputStream = avformat_new_stream(pOutputFormatCtx, NULL);
if(NULL == pOutputStream){
qDebug() <<"新建输出流失败 !";
return;
}
// 输出详细信息
av_dump_format(pOutputFormatCtx, 0, OutPutFileName.toStdString().c_str(), 1);
// 新建数据包
packet = av_packet_alloc();
if(NULL == packet){
qDebug() <<"新建数据包失败 !";
return;
}
// yuvFrame 初始化
yuvFrame = av_frame_alloc();
if(NULL == yuvFrame){
qDebug() <<"开辟AVFrame失败 !";
return;
}
yuvFrame->width = pCodecCtx->width;
yuvFrame->height = pCodecCtx->height;
yuvFrame->format = pCodecCtx->pix_fmt;
// 初始化 image 空间
av_image_alloc(yuvFrame->data, yuvFrame->linesize, yuvFrame->width, yuvFrame->height,
pCodecCtx->pix_fmt, 32);
// 转换上下文
image_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, pCodecCtx->width,
pCodecCtx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
if(NULL == image_convert_ctx){
qDebug() <<"转换上下文失败 !";
return;
}
// 写封装头
if(avformat_write_header(pOutputFormatCtx, NULL) < 0){
qDebug() <<"视频封装头写失败 !";
return;
}
}
// 编码为 h.264
void CameraVideoSurface::Encode(AVFrame *frame)
{
static int index = 0;
frame->pts = index++;
int ret = 0;
if((ret = avcodec_send_frame(pCodecCtx, frame)) < 0){
qDebug() <<"avcodec_send_frame 失败 !";
return;
}
while(ret >= 0)
{
ret = avcodec_receive_packet(pCodecCtx, packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
return;
}
else if (ret < 0){
qDebug() << "编码时出错";
return;
}
packet->stream_index = 0;
av_interleaved_write_frame(pOutputFormatCtx, packet); // write frame
av_packet_unref(packet);
}
}
//跳转至设置界面时调用 提前结束录像 并清空先前设置的路径 present()进程中的视频存储操作不会进行
void CameraVideoSurface::escEvent()
{
qDebug()<<"关闭摄像头";
av_write_trailer(pOutputFormatCtx);
avio_close(pOutputFormatCtx->pb);
av_frame_free(&yuvFrame);
avcodec_close(pCodecCtx);
QString CurDate = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
pSqlData->insert_sourcetable(
AllWidget::pLoginWidget->CurUser,
CurDate,
"video",
AllWidget::pSettingWidget->pSettingPath->VideoPath);
AllWidget::pSettingWidget->pSettingPath->ImagePath.clear();
AllWidget::pSettingWidget->pSettingPath->VideoPath.clear();
AllWidget::pSettingWidget->pSettingPath->TimeInterval.clear();
}
void CameraVideoSurface::cameraStopSlot()
{
qDebug()<<"关闭摄像头";
av_write_trailer(pOutputFormatCtx);
avio_close(pOutputFormatCtx->pb);
av_frame_free(&yuvFrame);
avcodec_close(pCodecCtx);
QString CurDate = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
if(!AllWidget::pLoginWidget->CurUser.isEmpty()){
pSqlData->insert_sourcetable(
AllWidget::pLoginWidget->CurUser,
CurDate,
"video",
AllWidget::pSettingWidget->pSettingPath->VideoPath);
}
InitEncoder();
}
使用方法:
QVBoxLayout *VBoxLayoutPlay;
QCamera *pCamera;
OpenGLWidget *pOpenGLWidget;
// QCameraViewfinder *pCameraViewfinder;
CameraVideoSurface *pCameraVideoSurface;
QCameraImageCapture *pCameraImageCapture; //获取摄像头当前帧
VBoxLayoutPlay = new QVBoxLayout(this);
pOpenGLWidget = new OpenGLWidget();
pCameraVideoSurface = new CameraVideoSurface();
pCamera = new QCamera(this);
pCameraImageCapture = new QCameraImageCapture(pCamera);
VBoxLayoutPlay->setGeometry(QRect(38, 20, 635, 341));
VBoxLayoutPlay->setContentsMargins(0, 0, 0, 0);
pCamera->setViewfinder(pCameraVideoSurface);
VBoxLayoutPlay->addWidget(pOpenGLWidget);
pCamera->start;
connect(pCameraVideoSurface, SIGNAL(showFrame(QImage)), pOpenGLWidget, SLOT(showCameraFrameSlot(QImage)));
connect(pCameraImageCapture, SIGNAL(imageCaptured(int,QImage)), this, SLOT(capture_slot(int,QImage)));
原文链接: https://blog.csdn.net/qq_34623621/article/details/106453823.
GitHub源码:https://github.com/Gernan-2135/Carcorder