Buildroot+Qt实现摄像头视频h264编码和拍照功能
https://gitee.com/zy853728579/video_test_app.git
实现h264硬编码+拍照功能,以下为关健代码
videodevice.h
#ifndef VIDEODEVICE_H
#define VIDEODEVICE_H
#include <QWidget>
#include <QTimer>
#include <QDebug>
#include <QReadWriteLock>
#include <QCamera>
#include <QCameraInfo>
#include <QVideoProbe>
#include <QVideoFrame>
#include <QCameraViewfinder>
#include <QCameraViewfinderSettings>
#define VIDEO_FPS (30)
#define CAMERA_PIXEL_WIDE (1920)
#define CAMERA_PIXEL_HIGH (1080)
class VideoDevice : public QWidget
{
Q_OBJECT
public:
explicit VideoDevice(QSize showSize, QWidget *parent = nullptr);
~VideoDevice(void);
void openCamera(bool reboot = false);
void closeCamera(void);
void showViewfinder(int x, int y);
void hideViewfinder(void) { if (m_viewFinder != nullptr) m_viewFinder->hide(); }
//获取摄像头配置参数
int getFrameRate(void) { return VIDEO_FPS; }
QSize getPixelSize(void) { return QSize(CAMERA_PIXEL_WIDE, CAMERA_PIXEL_HIGH);}
//获取每帧数据
void getFrameData(void * pData);
void getFrameData(QByteArray & pArray);
public slots:
void setProbeStatus(bool status) { m_probeStatus = status; }
private:
void setCamera(const QCameraInfo &cameraInfo);
private slots:
void updateCameraState(QCamera::State);
void showCameraError(void);
void onProbeFrameSlot(const QVideoFrame &frame);
void cameraTimeoutSlot(void);
signals:
void videoFrameSingal(void);
private:
QCamera * m_camera;
QVideoProbe * m_videoProbe;
QCameraViewfinder * m_viewFinder = nullptr;
QTimer * m_cameraTimer = nullptr;
QByteArray * m_frameData;
QReadWriteLock m_frameRWLock;
int m_cameraCnt = 0;
quint8 m_cameraCntPlus = 1;
bool isCameraOpen = false;
bool m_probeStatus = false;
};
#endif // VIDEODEVICE_H
videodevice.cpp(使用QT类打开摄像头进行获取图像和显示)
#include "videodevice.h"
Q_DECLARE_METATYPE(QCameraInfo)
VideoDevice::VideoDevice(QSize showSize, QWidget *parent) :
QWidget(parent)
{
m_frameData = new QByteArray();
m_cameraTimer = new QTimer(this);
QObject::connect(m_cameraTimer, &QTimer::timeout, this, &VideoDevice::cameraTimeoutSlot);
m_viewFinder = new QCameraViewfinder(parent);
m_viewFinder->setWindowFlag(Qt::FramelessWindowHint);
m_viewFinder->setFixedSize(showSize.width(), showSize.height());
setCamera(QCameraInfo::defaultCamera());
}
VideoDevice::~VideoDevice()
{
closeCamera();
m_frameData->clear();
delete m_frameData;
delete m_videoProbe;
delete m_camera;
}
void VideoDevice::setCamera(const QCameraInfo &cameraInfo)
{
//初始化摄像头
m_camera = new QCamera(cameraInfo, this);
connect(m_camera, &QCamera::stateChanged, this, &VideoDevice::updateCameraState);
connect(m_camera, QOverload<QCamera::Error>::of(&QCamera::error), this, &VideoDevice::showCameraError);
updateCameraState(m_camera->state());
/*
* QVideoFrame::Format_YUV420P
* YYYYYY
* YYYYYY
* YYYYYY
* UUUUUU
* VVVVVV
*/
QCameraViewfinderSettings settings;
settings.setPixelFormat(QVideoFrame::Format_YUV420P);
settings.setMinimumFrameRate(VIDEO_FPS);
settings.setMaximumFrameRate(VIDEO_FPS);
settings.setResolution(QSize(CAMERA_PIXEL_WIDE, CAMERA_PIXEL_HIGH));
m_camera->setViewfinderSettings(settings);
m_videoProbe = new QVideoProbe(this);
m_videoProbe->setSource(m_camera);
connect(m_videoProbe, &QVideoProbe::videoFrameProbed, this, &VideoDevice::onProbeFrameSlot);
}
void VideoDevice::openCamera(bool reboot)
{
if (isCameraOpen && !reboot)
return ;
m_camera->stop();
m_camera->unload();
m_camera->setCaptureMode(QCamera::CaptureVideo);
m_camera->start();
isCameraOpen = true;
if (!m_cameraTimer->isActive())
m_cameraTimer->start(10);
}
void VideoDevice::closeCamera()
{
isCameraOpen = false;
m_camera->stop();
m_camera->unload();
m_cameraTimer->stop();
}
void VideoDevice::showViewfinder(int x, int y)
{
if (m_viewFinder == nullptr)
return ;
m_camera->setViewfinder(m_viewFinder);
m_viewFinder->move(x, y);
m_viewFinder->show();
}
void VideoDevice::updateCameraState(QCamera::State state)
{
switch (state)
{
case QCamera::ActiveState: break;
case QCamera::UnloadedState:
case QCamera::LoadedState: break;
}
}
void VideoDevice::showCameraError(void)
{
qDebug()<<"Camera error:"<<m_camera->errorString();
}
void VideoDevice::getFrameData(void * pData)
{
QReadLocker readLock(&m_frameRWLock);
memcpy(pData, m_frameData->data(), m_frameData->length());
}
void VideoDevice::getFrameData(QByteArray &pArray)
{
QReadLocker readLock(&m_frameRWLock);
pArray.append(*m_frameData);
}
void VideoDevice::onProbeFrameSlot(const QVideoFrame &frame)
{
m_cameraCnt = 0;
m_cameraCntPlus = 1;
if (!m_probeStatus)
return ;
QVideoFrame cloneFrame(frame);
if (!cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) {
return ;
}
{
QWriteLocker writeLock(&m_frameRWLock);
m_frameData->clear();
m_frameData->append((const char *)cloneFrame.bits(), cloneFrame.mappedBytes());
}
cloneFrame.unmap();
emit videoFrameSingal();
}
void VideoDevice::cameraTimeoutSlot()
{
m_cameraCnt++;
//800ms
if (m_cameraCnt > (m_cameraCntPlus * 80))
{
qDebug()<<"camera is timeout!";
m_cameraCntPlus++;
m_cameraCnt = 0;
openCamera(true);
}
}
videothread.h
#ifndef VIDEOTHREAD_H
#define VIDEOTHREAD_H
#include <QObject>
#include <QSemaphore>
#include <QThread>
#include <QMutex>
#include <QReadWriteLock>
#include <QFileInfo>
#include <QTime>
#include "videodevice.h"
#include "mppencoder.h"
/*************************************************************************
*
* 录像
*
*/
class VideoThread : public QThread
{
Q_OBJECT
public:
enum RecordStatus{
RECORD_NULL,
RECORD_STOP,
RECORD_START,
RECORD_PAUSE,
RECORD_SIZEOVER,
RECORDING
};
explicit VideoThread(VideoDevice* vd, QObject *parent = nullptr);
~VideoThread();
//停止该线程运行
void stop(void);
//设置录像视频保存路经
void setRecordPath(const QString & path);
/*设置录像状态
* 当前状态 可改变状态
* RECORD_NULL/RECORD_STOP ---> RECORD_START
* RECORD_START ---> RECORD_STOP
* RECORDING/RECORD_SIZEOVER ---> RECORD_PAUSE/RECORD_STOP
* RECORD_PAUSE ---> RECORD_STOP/RECORDING
*/
void setRecordStatus(RecordStatus status);
public slots:
void videoFrameSlot(void);
signals:
//录像完成信号
void videoFinishSignal();
/* 编码完成后数据
* head == true : 表示数据包头
*/
void encodeDataSignal(bool head, const QByteArray & pData);
//录像时间
//格式:HH:mm:ss
void videoTimeSignal(const QString & time);
protected:
void run();
private slots:
void startRecord(void);
void endRecord(void);
void videoSave(const QByteArray & pArray);
//录像计时
void recordTiming();
void checkRecordSizeSlot(void);
void setStatus(RecordStatus s)
{
QWriteLocker write(&m_statusRWLock);
m_recordStatus = s;
}
RecordStatus getStatus(void)
{
QReadLocker read(&m_statusRWLock);
return m_recordStatus;
}
private:
bool isStop = false;
QSemaphore m_semVideo;
VideoDevice * m_videoDevice = nullptr;
quint64 m_frameCnt = 0;
quint64 m_frameTimeSave = 0;
QString m_recordPath = "";
QString m_recordFile = "";
QReadWriteLock m_statusRWLock;
RecordStatus m_recordStatus = RECORD_NULL;
//编码
MppEncoder * m_mppEncode = nullptr;
QTimer * m_checkSizeTimer = nullptr;
QSize m_videoSize;//图像大小
int m_videoFps = 0;//帧率
int m_frameTime = 0;//ms
};
/*************************************************************************
*
* 拍照
*
*/
#include "videoffmpeg.h"
class PictureThread : public QThread
{
Q_OBJECT
public:
enum PictureStatus{
PICTURE_NULL,
PICTURE_START
};
explicit PictureThread(VideoDevice* vd, QObject *parent = nullptr);
void stop(void);
void setPicturePath(const QString & path);
void setPictureStatus(PictureStatus status);
public slots:
void pictureFrameSlot(void);
signals:
void pictureFinishSignal(void);
protected:
void run();
private:
QString openPictureFile(void);
private:
bool isStop = false;
QSemaphore m_semPicture;
VideoDevice * m_videoDevice = nullptr;
QString m_picturePath = "";
PictureStatus m_pictureStatus = PICTURE_NULL;
};
#endif // VIDEOTHREAD_H
videothread.cpp(录像和拍照线程处理)
#include "videothread.h"
#include <QDebug>
#include <unistd.h>
/*************************************************************************
*
* 录像
*
*/
#define FILE_SZ_1M (1024LL*1024LL)
#define FILE_SZ_1G (1024LL*FILE_SZ_1M)
#define FILE_SZ_3G (3LL*FILE_SZ_1G)
VideoThread::VideoThread(VideoDevice* vd, QObject *parent) :
QThread(parent),
m_videoDevice(vd)
{
m_videoSize = m_videoDevice->getPixelSize();
m_videoFps = m_videoDevice->getFrameRate();
m_frameTime = 1000000 / m_videoFps;
m_mppEncode = new MppEncoder(m_videoSize.width(), m_videoSize.height(), m_videoFps);
m_checkSizeTimer = new QTimer(this);
QObject::connect(m_checkSizeTimer, &QTimer::timeout, this, &VideoThread::checkRecordSizeSlot);
}
VideoThread::~VideoThread()
{
setRecordStatus(RECORD_STOP);
endRecord();
delete m_mppEncode;
}
void VideoThread::stop()
{
isStop = true;
quit();
wait();
}
void VideoThread::setRecordPath(const QString &path)
{
if (path.isEmpty())
{
if (m_recordStatus != RECORD_NULL)
{
m_recordStatus = RECORD_NULL;
int num = m_semVideo.available();
if (num != 0)
m_semVideo.acquire(num);
}
return ;
}
m_recordPath = path;
}
void VideoThread::setRecordStatus(RecordStatus status)
{
if (m_recordPath.isEmpty())
return ;
RecordStatus record = getStatus();
switch (record) {
case RECORD_NULL:
case RECORD_STOP:
{
if (status == RECORD_START)
{
m_frameCnt = 0;
record = status;
}
}break;
case RECORD_START:
{
if (status == RECORD_STOP)
record = status;
}break;
case RECORDING:
case RECORD_SIZEOVER:
{
if (status == RECORD_PAUSE ||
status == RECORD_STOP)
record = status;
}break;
case RECORD_PAUSE:
{
if (status == RECORD_STOP) {
record = status;
if (m_semVideo.available() == 0)
m_semVideo.release();
}
else if (status == RECORDING)
{
record = status;
}
}break;
}
//定时器处理
if (record == RECORD_PAUSE ||
record == RECORD_STOP)
{
m_checkSizeTimer->stop();
}
if (record == RECORD_START ||
record == RECORDING)
{
if (!m_checkSizeTimer->isActive())
m_checkSizeTimer->start(10000);
}
setStatus(record);
}
void VideoThread::videoFrameSlot(void)
{
RecordStatus record = getStatus();
if (record == RECORD_NULL)
{
int num = m_semVideo.available();
if (num != 0)
m_semVideo.acquire(num);
return ;
}
m_semVideo.release();
}
void VideoThread::startRecord()
{
int videoCnt = 0;
QString lo = m_recordPath + "/VIDEO" + QString::number(videoCnt) + ".mp4";
QFileInfo fi = QFileInfo(lo);
while(fi.isFile()){
videoCnt++;
lo = m_recordPath + "/VIDEO" + QString::number(videoCnt) + ".mp4";
fi = QFileInfo(lo);
}
QByteArray mp4file(lo.toLocal8Bit());
bool ret = MP4_Write_Init(mp4file.data(), m_videoFps);
if(ret == false) {
qDebug()<<"MP4_Write_Init failed..";
return ;
}
QByteArray array = m_mppEncode->getFrameHeader();
if (array.isEmpty())
return ;
emit encodeDataSignal(true, array);
MP4_Write_SPS_PPS((uint8_t*)array.data(), array.length());
m_recordFile = lo;
}
void VideoThread::endRecord()
{
MP4_Write_Exit();
sync();
}
void VideoThread::videoSave(const QByteArray &pArray)
{
int lenght = pArray.length();
MP4_Write_Main((uint8_t*)pArray.data(), lenght);
}
void VideoThread::recordTiming()
{
m_frameCnt++;
quint64 time = (m_frameCnt * m_frameTime) / 1000000; //s
if (m_frameTimeSave != time)
{
m_frameTimeSave = time;
QString timeStr = QTime(0, 0, 0).addSecs(time).toString(QString::fromLatin1("HH:mm:ss"));
//qDebug()<<"timeStr="<<timeStr;
emit videoTimeSignal(timeStr);
}
}
void VideoThread::checkRecordSizeSlot()
{
if (m_recordFile.isEmpty())
return ;
RecordStatus record = getStatus();
if (record != RECORD_START &&
record != RECORDING)
return ;
QFileInfo fi = QFileInfo(m_recordFile);
qint64 size = fi.size();
if (size > FILE_SZ_3G){
qDebug()<<"size is 3G!";
setStatus(RECORD_SIZEOVER);
}
}
void VideoThread::run()
{
msleep(250);
for ( ; 1 ;)
{
if(isStop == true)
return;
if(m_videoDevice == nullptr)
continue;
m_semVideo.acquire();
//数据保存
RecordStatus record = getStatus();
if (record == RECORD_SIZEOVER)
{
setStatus(RECORD_START);
MP4_Write_Exit();
}
if (record == RECORD_START)
{
setStatus(RECORDING);
startRecord();
}
if (record == RECORDING)
{
//编码
void * desBuf = m_mppEncode->getFrameBuffer();
m_videoDevice->getFrameData(desBuf);
QByteArray encodeArray;
m_mppEncode->encode(encodeArray);
emit encodeDataSignal(false, encodeArray);
recordTiming();
videoSave(encodeArray);
}
if (record == RECORD_STOP)
{
setStatus(RECORD_NULL);
endRecord();
emit videoFinishSignal();
}
}
}
/*************************************************************************
*
* 拍照
*
*/
PictureThread::PictureThread(VideoDevice* vd, QObject *parent) :
QThread(parent),
m_videoDevice(vd)
{
}
void PictureThread::stop()
{
isStop = true;
quit();
wait();
}
void PictureThread::setPicturePath(const QString &path)
{
if (path.isEmpty())
{
if (m_pictureStatus != PICTURE_NULL)
{
m_pictureStatus = PICTURE_NULL;
int num = m_semPicture.available();
if (num != 0)
m_semPicture.acquire(num);
}
return ;
}
m_picturePath = path;
}
void PictureThread::setPictureStatus(PictureStatus status)
{
if (status == PICTURE_NULL)
return ;
if (m_picturePath.isEmpty())
return ;
if (m_pictureStatus == PICTURE_NULL)
m_pictureStatus = status;
}
QString PictureThread::openPictureFile()
{
QString lo = "";
if (m_picturePath.isEmpty())
return lo;
int imageCnt = 0;
lo = m_picturePath + "/PIC" + QString::number(imageCnt) + ".jpg";
QFileInfo fi = QFileInfo(lo);
while(fi.isFile()){
imageCnt++;
lo = m_picturePath + "/PIC" + QString::number(imageCnt) + ".jpg";
fi = QFileInfo(lo);
}
return lo;
}
void PictureThread::pictureFrameSlot()
{
if (m_pictureStatus == PICTURE_NULL)
{
int num = m_semPicture.available();
if (num != 0)
m_semPicture.acquire(num);
return ;
}
m_semPicture.release();
}
void PictureThread::run()
{
msleep(250);
VideoFfmpeg ffmpeg(CAMERA_PIXEL_WIDE, CAMERA_PIXEL_HIGH);
int rgbLenght = ffmpeg.getNumBytes();
for ( ; 1 ;)
{
if(isStop == true)
return;
if(m_videoDevice == nullptr)
continue;
m_semPicture.acquire();
if (m_pictureStatus != PICTURE_START)
continue;
m_pictureStatus = PICTURE_NULL;
QByteArray array;
array.clear();
m_videoDevice->getFrameData(array);
ffmpeg.setFrameYUV((unsigned char *)array.data());
char * dstBuff = (char *)malloc(rgbLenght * sizeof(uint8_t));
memset(dstBuff, 0, rgbLenght * sizeof(uint8_t));
ffmpeg.setRgbBuffer((unsigned char *)dstBuff);
bool isOk = ffmpeg.play();
if (isOk == false)
{
qDebug()<<"ffmpeg conversion failed!";
delete dstBuff;
dstBuff = nullptr;
continue;
}
//获取文件名称
QString fileName = openPictureFile();
//保存
if (!fileName.isEmpty()){
QSize s = m_videoDevice->getPixelSize();
QImage tmpImg((unsigned char*)dstBuff, s.width(), s.height(), QImage::Format_RGB888);
tmpImg.save(fileName);
sync();
//拍照保存完成
emit pictureFinishSignal();
}
//释放空间
delete dstBuff;
dstBuff = nullptr;
}
}