Qt实现摄像头视频h264编码和拍照功能

1 篇文章 0 订阅

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;
    }
}

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kk电子粉丝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值