若该文为原创文章,转载请注明出处
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/154340644
长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中...
合作案例专栏:案例分享(体验Demo可下载,只定制)
需求
1.windows系统录制音视频(限定开发机的麦克风);
2.使用软解码,软编码;
3.ffmpeg录音,使用ffmpeg打开麦克风,重采样,然后编码录音mp4(开发机的一种通道和采样率);
4.ffmpeg录像,使用ffmpeg打开摄像头,转码,然后编码录像mp4;
5.opencv图像帧提供接口,ffmpeg打开摄像头的图像进入opencv算法mat处理后传出,保持原有的帧时间;
6.ffmpeg录音频、录像并且录像可以处理opencv算法处理的图像录制成mp4;
7.运行稳定,可停止录音、录像、可打开不录音、不录像等。
8.预留模块化设计,可以并行多个摄像头多个麦克风进行录制多个mp4文件(没有实现,模块化类似于开多个exe就可以了;
9.预留流程化设计,opencv图像与音频都是通过信号槽传递,并且可以保持帧序号时间同步;
10.版本:Qt5.12 vs2017x64、ffmpeg5、opencv4.10
相关博客
《案例分享:Qt+FFmpeg录屏应用(支持帧率、清晰度设置)》
《FFmpeg开发笔记(三):ffmpeg介绍、windows编译以及开发环境搭建》
《FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)》
《FFmpeg开发笔记(七):ffmpeg解码音频保存为PCM并使用软件播放》
《FFmpeg开发笔记(十二):ffmpeg音频处理、采集麦克风音频录音为WAV》
《FFmpeg开发笔记(十三):ffmpeg采集麦克风音频pcm重采样为aac录音为AAC文件》
《OpenCV开发笔记(六):OpenCV基础数据结构、颜色转换函数和颜色空间》
《OpenCV开发笔记(七十):红胖子带你傻瓜式编译VS2017x64版本的openCV4》
Demo演示







CSDN免(0)积分下载地址:https://download.csdn.net/download/qq21497936/92240313
QQ群(看博主名)下载:搜素"ffmpegCaptureAudioCameraRecord"
特点:颠覆传统录制音视频设计思路
与传统的录音录像有较大的不同,既可以实现录音录像又兼容了Qt信号槽的低耦合高内聚思想,让对音频和视频的处理可以通过中间信号槽环节,只要输出信号的数据类型和处理后接收的信号类型数据一致,至于中间怎么处理,可以模块内自行任何处理,但是延迟不应该太长,比如几十ms几百ms即可。(这里有个预期风险点 :算法时间,当前也就几十ms,时间未作较长延迟测试,因为ffmpeg音视频录像会有函数校准流数据中的帧位置进行调整,可能存在偏差较大无法校准的状态,单视频、单音频无此问题,多流录制文件是录制音视频同步的核心关键技术点 ,可以假设音频录制到第10s,图像再传入第4s的,从感觉上来说不太合适,从音频10s的地方修改数据到音频第4s数据处然后插入视频4s的包,调整长度达到6s,暂未论证)。
下图圆圈既是线程又是模块,也可以合并线程(moveToThread),线程间的数据流就是模块数据流:

添加opencv处理之后的数据流(本身设计的时候就是低耦合高内聚模块化,实现了数据流清晰简单,节点就是功能模块,只有信号槽的交互):



模块化


Demo部分代码
RecoredWidget.h
            
            
              cpp
              
              
            
          
          #ifndef RECORDWIDGET_H
#define RECORDWIDGET_H
#include <QWidget>
#include <QThread>
#include <QFileDialog>
#include <QButtonGroup>
#include <QDateTime>
#include <QFile>
#include "FFmpegCaptureCameraManager.h"
#include "FFmpegRecordManager.h"
#include "FFmpegCaptureAudioManager.h"
#include "OpenCVDrawManager.h"
#include "Windows.h"
namespace Ui {
class RecordWidget;
}
// 用于测试传递的文件是否存在问题(检测是有问题,实际没问题,原因未知,怀疑是播放有些没注意,因为编码的时候又按照原有格式弄回去了)
#define TEST_WRITE_AUDIO_FILE (0)
// 用于处理图像数据流,看抛送对延迟的影响,应该是基本没有,一帧为33ms左右,比如差距1ms,只有32刷了,33没刷这一帧才能看出,其余32ms都是一样的
#define TEST_IMMEDIATE_SIGNAL_TO_IMAGE (0)
class RecordWidget : public QWidget
{
    Q_OBJECT
public:
    explicit RecordWidget(QWidget *parent = 0);
    ~RecordWidget();
protected:
    void initControl();
protected slots:        // 音频采集相关
    void slot_openedAudioInfo(QString audioName, int sampleRate, int channels);     // 打开的麦克风信息
    void slot_getAudioOneFrame(QByteArray byteArray);                               // 获取一帧的数据
protected slots:        // 摄像头采集相关
    void slot_openedCameraInfo(QString cameraDevName, int width, int height, int fps);  // 打开的摄像头信息
    void slot_getVideoOneFrame(QImage image);                                           // 获取一帧的数据(图像)
protected slots:        // opencv绘制相关
    void slot_getOpenCVFrame(QImage image);
protected slots:
    void slot_videoStateChanged(FFmpegCaptureCameraManager::CAMERA_STATE state);    // 视频状态更新
    void slot_audioStateChanged(FFmpegCaptureAudioManager::AUDIO_STATE state);      // 录音状态更新
    void slot_recordStateChanged(FFmpegRecordManager::RECORD_STATE state);          // 录像状态更新
protected slots:
    void slot_buttonClicked(QAbstractButton *pAbstractButton);
    void slot_buttonOpenCVDrawTypeClicked(int type);
private slots:
    void on_pushButton_browser_clicked();
    void on_pushButton_openCamera_clicked();            // 打开摄像头
    void on_pushButton_closeCamera_clicked();           // 关闭摄像头
    void on_pushButton_openAudio_clicked();         // 录音操作
    void on_pushButton_closeAudio_clicked();        // 录音操作
    void on_pushButton_startRecord_clicked();           // 开始录像
    void on_pushButton_stopRecord_clicked();            // 停止录像
    void on_pushButton_detectDevice_clicked();
    void on_pushButton_detectAudio_clicked();
    void on_pushButton_detectCamera_clicked();
private:
    Ui::RecordWidget *ui;
    FFmpegCaptureCameraManager *_pFFmpegCaptureCameraManager;   // 摄像头采集管理类
    QThread *_pFFmpegCaptureCameraManagerThread;                // 摄像头
    FFmpegRecordManager *_pFFmpegRecordManager;                 // 录制管理类
    QThread *_pFFmpegRecordManagerThread;                       // 录制线程
    FFmpegCaptureAudioManager *_pFFmpegCaptureAudioManager;     // 音频采集管理类
    QThread *_pFFmpegCaptureAudioManagerThread;                 // 音频采集线程
    bool _recording;                                            // 正在录像
    OpenCVDrawManager *_pOpenCVDrawManager;                     // opencv绘制管理类
    QThread *_pOpenCVDrawManagerThread;                         // opencv子线程
#if TEST_WRITE_AUDIO_FILE
    QFile _audioDataFile;
#endif
};
#endif // RECORDWIDGET_H
        RecordWidget.cpp
            
            
              cpp
              
              
            
          
          #include "RecordWidget.h"
#include "ui_RecordWidget.h"
#include <QDateTime>
#include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")
// QtCreator在msvc下设置编码也或有一些乱码,直接一刀切,避免繁琐的设置
#define MSVC
#ifdef MSVC
#define QSTRING(s)  QString::fromLocal8Bit(s)
#else
#define QSTRING(s)  QString(s)
#endif
RecordWidget::RecordWidget(QWidget *parent)
    : QWidget(parent),
      ui(new Ui::RecordWidget),
      _pFFmpegCaptureCameraManager(0),
      _pFFmpegCaptureCameraManagerThread(0),
      _pFFmpegRecordManager(0),
      _pFFmpegRecordManagerThread(0),
      _pFFmpegCaptureAudioManager(0),
      _pFFmpegCaptureAudioManagerThread(0),
    _recording(false)
{
    ui->setupUi(this);
    QString version = "v1.0.2";
    setWindowTitle(QSTRING("音视频录像综合应用Demo %1(长沙红胖子Qt(长沙创微智科) QQ:21497936 www.chuangweizhike.com)").arg(version));
    initControl();
}
RecordWidget::~RecordWidget()
{
    delete ui;
}
void RecordWidget::initControl()
{
#if 1
    // 初始化摄像头线程
    {
        _pFFmpegCaptureCameraManagerThread = new QThread();
        _pFFmpegCaptureCameraManager = new FFmpegCaptureCameraManager();
        _pFFmpegCaptureCameraManager->moveToThread(_pFFmpegCaptureCameraManagerThread);
        connect(_pFFmpegCaptureCameraManagerThread, SIGNAL(started()),
                _pFFmpegCaptureCameraManager, SLOT(slot_start()));
        connect(_pFFmpegCaptureCameraManager, SIGNAL(signal_openedCameraInfo(QString,int,int,int)),
                this, SLOT(slot_openedCameraInfo(QString,int,int,int)));
        connect(_pFFmpegCaptureCameraManager, SIGNAL(signal_captureOneFrame(QImage)),
                this, SLOT(slot_getVideoOneFrame(QImage)));
        connect(_pFFmpegCaptureCameraManager, SIGNAL(signal_stateChanged(FFmpegCaptureCameraManager::CAMERA_STATE)),
                this, SLOT(slot_videoStateChanged(FFmpegCaptureCameraManager::CAMERA_STATE)));
        _pFFmpegCaptureCameraManagerThread->start();
    }
#endif
#if 1
    // 初始化音频采集线程
    {
        _pFFmpegCaptureAudioManagerThread = new QThread();
        _pFFmpegCaptureAudioManager = new FFmpegCaptureAudioManager();
        _pFFmpegCaptureAudioManager->moveToThread(_pFFmpegCaptureAudioManagerThread);
        connect(_pFFmpegCaptureAudioManagerThread, SIGNAL(started()),
                _pFFmpegCaptureAudioManager, SLOT(slot_start()));
        connect(_pFFmpegCaptureAudioManager, SIGNAL(signal_captureOneFrame(QByteArray)),
                this, SLOT(slot_getAudioOneFrame(QByteArray)));
        connect(_pFFmpegCaptureAudioManager, SIGNAL(signal_stateChanged(FFmpegCaptureAudioManager::AUDIO_STATE)),
                this, SLOT(slot_audioStateChanged(FFmpegCaptureAudioManager::AUDIO_STATE)));
        _pFFmpegCaptureAudioManagerThread->start();
    }
#endif
#if 1
    // 初始化录制线程
    {
        _pFFmpegRecordManagerThread = new QThread();
        _pFFmpegRecordManager = new FFmpegRecordManager();
        _pFFmpegRecordManager->moveToThread(_pFFmpegRecordManagerThread);
        connect(_pFFmpegRecordManagerThread, SIGNAL(started()),
                _pFFmpegRecordManager, SLOT(slot_start()));
        connect(_pFFmpegRecordManager, SIGNAL(signal_stateChanged(FFmpegRecordManager::RECORD_STATE)),
                this, SLOT(slot_recordStateChanged(FFmpegRecordManager::RECORD_STATE)));
        _pFFmpegRecordManagerThread->start();
    }
#endif
#if 1
    // 初始化opencv
    {
        _pOpenCVDrawManagerThread = new QThread();
        _pOpenCVDrawManager = new OpenCVDrawManager();
        _pOpenCVDrawManager->moveToThread(_pOpenCVDrawManagerThread);
        connect(_pOpenCVDrawManagerThread, SIGNAL(started()),
                _pOpenCVDrawManager, SLOT(slot_start()));
        connect(_pOpenCVDrawManager, SIGNAL(signal_getOneFrame(QImage)),
                this, SLOT(slot_getOpenCVFrame(QImage)));
#if TEST_IMMEDIATE_SIGNAL_TO_IMAGE
        // 注意:这里可以让界面发,可能延迟低一点,也可以直接从摄像头采集出关联过来,前者先后顺序,后者同步发送
        connect(_pFFmpegCaptureCameraManager, SIGNAL(signal_captureOneFrame(QImage)),
                _pOpenCVDrawManager, SLOT(slot_getOneFrame(QImage)));
#endif
        _pOpenCVDrawManagerThread->start();
    }
#endif
    // 控件路径
    {
        ui->lineEdit_dirPath->setText(QCoreApplication::applicationDirPath());
    }
    // 按钮组
    {
        QButtonGroup *pButtonGroup = new QButtonGroup(this);
        pButtonGroup->addButton(ui->radioButton_audio);
        pButtonGroup->addButton(ui->radioButton_video);
        pButtonGroup->addButton(ui->radioButton_audioAndVideo);
        connect(pButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)),
                this, SLOT(slot_buttonClicked(QAbstractButton*)));
        // 初始化
        ui->radioButton_audio->setChecked(true);
        slot_buttonClicked(ui->radioButton_audio);
    }
    // 绘制按钮租
    {
        QButtonGroup *pButtonGroup = new QButtonGroup(this);
        pButtonGroup->addButton(ui->radioButton_opencvDraw0, 0);
        pButtonGroup->addButton(ui->radioButton_opencvDraw1, 1);
        pButtonGroup->addButton(ui->radioButton_opencvDraw2, 2);
        pButtonGroup->addButton(ui->radioButton_opencvDraw3, 3);
        pButtonGroup->addButton(ui->radioButton_opencvDraw4, 4);
        pButtonGroup->addButton(ui->radioButton_opencvDraw5, 5);
        connect(pButtonGroup, SIGNAL(buttonClicked(int)),
                this, SLOT(slot_buttonOpenCVDrawTypeClicked(int)));
        // 初始化
        ui->radioButton_audio->setChecked(true);
        slot_buttonClicked(ui->radioButton_audio);
    }
}
void RecordWidget::slot_openedAudioInfo(QString audioName, int sampleRate, int channels)
{
    LOG << __FUNCTION__ << audioName << sampleRate << channels;
}
void RecordWidget::slot_getAudioOneFrame(QByteArray byteArray)
{
    LOG << __FUNCTION__ << byteArray.size();
#if TEST_WRITE_AUDIO_FILE
    _audioDataFile.write(byteArray);
    _audioDataFile.flush();
#endif
    if(_recording)
    {
        QMetaObject::invokeMethod(_pFFmpegRecordManager, "slot_recordAudioFrame", Q_ARG(QByteArray, byteArray));
    }
}
void RecordWidget::slot_openedCameraInfo(QString cameraDevName, int width, int height, int fps)
{
    LOG << __FUNCTION__ << cameraDevName << width << height << fps;
    _pFFmpegRecordManager->setWidthIn(width);
    _pFFmpegRecordManager->setHeightIn(height);
    _pFFmpegRecordManager->setFps(fps);
    _pFFmpegRecordManager->setWidthOut(width);
    _pFFmpegRecordManager->setHeightOut(height);
    _pFFmpegRecordManager->setFps(fps);
}
void RecordWidget::slot_getVideoOneFrame(QImage image)
{
    ui->widget_image->setImage(image);
#if !TEST_IMMEDIATE_SIGNAL_TO_IMAGE
    // 注意:这里可以让界面发,延迟第一点,也可以直接从摄像头采集出关联过来,前者先后顺序,后者同步发送
    // 这里是前者
    QMetaObject::invokeMethod(_pOpenCVDrawManager, "slot_getOneFrame", Q_ARG(QImage, image));
#endif
}
void RecordWidget::slot_getOpenCVFrame(QImage image)
{
    ui->widget_imageResult->setImage(image);
    if(_recording)
    {
        QMetaObject::invokeMethod(_pFFmpegRecordManager, "slot_recordVideoFrame", Q_ARG(QImage, image));
    }
}
void RecordWidget::slot_videoStateChanged(FFmpegCaptureCameraManager::CAMERA_STATE state)
{
    LOG << __FUNCTION__ << state;
    // 使用细分状态进行全局控制
    switch(state)
    {
    case FFmpegCaptureCameraManager::CAMERA_STATE_NONE:                  // 无状态,未有任何操作
        ui->pushButton_openCamera->setEnabled(true);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->label_videoState->setText(QSTRING("未打开"));
        break;
    case FFmpegCaptureCameraManager::CAMERA_STATE_OPENING:               // 正在打开摄像头
        ui->pushButton_openCamera->setEnabled(false);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->label_videoState->setText(QSTRING("正在打开..."));
        break;
    case FFmpegCaptureCameraManager::CAMERA_STATE_OPENED:                // 打开了
        ui->pushButton_openCamera->setEnabled(false);
        ui->pushButton_closeCamera->setEnabled(true);
        ui->label_videoState->setText(QSTRING("打开成功"));
        break;
    case FFmpegCaptureCameraManager::CAMERA_STATE_OPEN_FAILED:           // 打开失败
        ui->pushButton_openCamera->setEnabled(true);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->label_videoState->setText(QSTRING("打开失败"));
        break;
    case FFmpegCaptureCameraManager::CAMERA_STATE_CLOSEING:              // 正在关闭摄像头
        ui->pushButton_openCamera->setEnabled(false);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->label_videoState->setText(QSTRING("正在关闭"));
        break;
    case FFmpegCaptureCameraManager::CAMERA_STATE_CLOSED:                // 已关闭
        ui->pushButton_openCamera->setEnabled(true);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->label_videoState->setText(QSTRING("已关闭"));
        break;
    default:
        break;
    }
}
void RecordWidget::slot_audioStateChanged(FFmpegCaptureAudioManager::AUDIO_STATE state)
{
    LOG << __FUNCTION__ << state;
    // 使用细分状态进行全局控制
    switch(state)
    {
    case FFmpegCaptureAudioManager::AUDIO_STATE_NONE:                   // 无状态
        ui->pushButton_openAudio->setEnabled(true);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->label_audioState->setText(QSTRING("未打开"));
        break;
    case FFmpegCaptureAudioManager::AUDIO_STATE_OPENING:                // 正在打开麦克风
        ui->pushButton_openAudio->setEnabled(false);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->label_audioState->setText(QSTRING("正在打开"));
        break;
    case FFmpegCaptureAudioManager::AUDIO_STATE_OPENED:                 // 打开了
        ui->pushButton_openAudio->setEnabled(false);
        ui->pushButton_closeAudio->setEnabled(true);
        ui->label_audioState->setText(QSTRING("打开成功"));
        break;
    case FFmpegCaptureAudioManager::AUDIO_STATE_OPEN_FAILED:            // 打开失败
        ui->pushButton_openAudio->setEnabled(true);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->label_audioState->setText(QSTRING("打开失败"));
        break;
    case FFmpegCaptureAudioManager::AUDIO_STATE_CLOSING:                // 正在关闭麦克风
        ui->pushButton_openAudio->setEnabled(false);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->label_audioState->setText(QSTRING("正在关闭"));
        break;
    case FFmpegCaptureAudioManager::AUDIO_STATE_CLOSED:                 // 已关闭
        ui->pushButton_openAudio->setEnabled(true);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->label_audioState->setText(QSTRING("已关闭"));
        break;
    default:
        break;
    };
}
void RecordWidget::slot_recordStateChanged(FFmpegRecordManager::RECORD_STATE state)
{
    LOG << __FUNCTION__ << state;
    // 使用细分状态进行全局控制
    switch (state)
    {
    case FFmpegRecordManager::RECORD_STATE_NONE:             // 没有录像
        ui->pushButton_startRecord->setEnabled(true);
        ui->pushButton_stopRecord->setEnabled(false);
        ui->label_recordState->setText(QSTRING("未录像"));
        ui->radioButton_audio->setEnabled(true);
        ui->radioButton_video->setEnabled(true);
        ui->radioButton_audioAndVideo->setEnabled(true);
        break;
    case FFmpegRecordManager:: RECORD_STATE_STARTING:         // 正在打开录像
        ui->pushButton_startRecord->setEnabled(false);
        ui->pushButton_stopRecord->setEnabled(false);
        ui->label_recordState->setText(QSTRING("正在准备录像"));
        ui->radioButton_audio->setEnabled(false);
        ui->radioButton_video->setEnabled(false);
        ui->radioButton_audioAndVideo->setEnabled(false);
        break;
    case FFmpegRecordManager:: RECORD_STATE_START_FAILED:     // 打开录像失败
        ui->pushButton_startRecord->setEnabled(true);
        ui->pushButton_stopRecord->setEnabled(false);
        ui->label_recordState->setText(QSTRING("录像失败"));
        ui->radioButton_audio->setEnabled(true);
        ui->radioButton_video->setEnabled(true);
        ui->radioButton_audioAndVideo->setEnabled(true);
        break;
    case FFmpegRecordManager:: RECORD_STATE_RECORDING:        // 正在录像中
        ui->pushButton_startRecord->setEnabled(false);
        ui->pushButton_stopRecord->setEnabled(true);
        ui->label_recordState->setText(QSTRING("正在录像中..."));
        ui->radioButton_audio->setEnabled(false);
        ui->radioButton_video->setEnabled(false);
        ui->radioButton_audioAndVideo->setEnabled(false);
        break;
    case FFmpegRecordManager:: RECORD_STATE_FINISHING:        // 正在关闭录像
        ui->pushButton_startRecord->setEnabled(false);
        ui->pushButton_stopRecord->setEnabled(false);
        ui->label_recordState->setText(QSTRING("正在停止录像"));
        ui->radioButton_audio->setEnabled(false);
        ui->radioButton_video->setEnabled(false);
        ui->radioButton_audioAndVideo->setEnabled(false);
        break;
    case FFmpegRecordManager:: RECORD_STATE_FINISHED:         // 已结束录像
        ui->pushButton_startRecord->setEnabled(true);
        ui->pushButton_stopRecord->setEnabled(false);
        ui->label_recordState->setText(QSTRING("录像成功"));
        ui->radioButton_audio->setEnabled(true);
        ui->radioButton_video->setEnabled(true);
        ui->radioButton_audioAndVideo->setEnabled(true);
        break;
    default:
        return;
        break;
    }
}
void RecordWidget::slot_buttonClicked(QAbstractButton *pAbstractButton)
{
    if(pAbstractButton == ui->radioButton_audio)
    {
        // 设置文件是只录制音频
        // 区域使能
        ui->groupBox_recordAudio->setEnabled(true);
        ui->groupBox_recordVideo->setEnabled(false);
        // 按钮使能
        ui->pushButton_openAudio->setEnabled(true);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->pushButton_startRecord->setEnabled(true);
        ui->pushButton_stopRecord->setEnabled(false);
    }else if(pAbstractButton == ui->radioButton_video)
    {
        // 区域使能
        ui->groupBox_recordAudio->setEnabled(false);
        ui->groupBox_recordVideo->setEnabled(true);
        // 按钮使能
        ui->pushButton_openCamera->setEnabled(true);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->pushButton_startRecord->setEnabled(true);
        ui->pushButton_stopRecord->setEnabled(false);
    }else if(pAbstractButton == ui->radioButton_audioAndVideo)
    {
        // 区域使能
        ui->groupBox_recordAudio->setEnabled(true);
        ui->groupBox_recordVideo->setEnabled(true);
        // 按钮是能
        ui->pushButton_openAudio->setEnabled(true);
        ui->pushButton_closeAudio->setEnabled(false);
        ui->pushButton_openCamera->setEnabled(true);
        ui->pushButton_closeCamera->setEnabled(false);
        ui->pushButton_startRecord->setEnabled(true);
        ui->pushButton_stopRecord->setEnabled(false);
    }
}
void RecordWidget::slot_buttonOpenCVDrawTypeClicked(int type)
{
    QMetaObject::invokeMethod(_pOpenCVDrawManager, "slot_setType", Q_ARG(int, type));
}
void RecordWidget::on_pushButton_browser_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(0, "保存到文件夹", ".");
    if(dir.isEmpty())
    {
        return;
    }
    ui->lineEdit_dirPath->setText(dir);
}
void RecordWidget::on_pushButton_openCamera_clicked()
{
    _pFFmpegCaptureCameraManager->setCameraDevName(ui->comboBox_cameraDevName->currentText());
    _pFFmpegCaptureCameraManager->setWidth(ui->lineEdit_videoWidth->text().toInt());
    _pFFmpegCaptureCameraManager->setHeight(ui->lineEdit_videoHeight->text().toInt());
    _pFFmpegCaptureCameraManager->setFps(ui->lineEdit_fps->text().toInt());
    QTimer::singleShot(1, _pFFmpegCaptureCameraManager, SLOT(slot_openCamera()));
}
void RecordWidget::on_pushButton_closeCamera_clicked()
{
    QTimer::singleShot(1, _pFFmpegCaptureCameraManager, SLOT(slot_closeCamera()));
}
void RecordWidget::on_pushButton_openAudio_clicked()
{
    _pFFmpegCaptureAudioManager->setAudioDevName(ui->comboBox_audioDevName->currentText());
    _pFFmpegCaptureAudioManager->setSampleRate(ui->lineEdit_audioSampleRate->text().toInt());
    _pFFmpegCaptureAudioManager->setChannels(ui->lineEdit_aduioChannels->text().toInt());
    QTimer::singleShot(1, _pFFmpegCaptureAudioManager, SLOT(slot_openAudio()));
#if TEST_WRITE_AUDIO_FILE
    _audioDataFile.setFileName(QString("%1/%2.data")
                         .arg(ui->lineEdit_dirPath->text())
                         .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm_ss")));
    _audioDataFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
#endif
}
void RecordWidget::on_pushButton_closeAudio_clicked()
{
    QTimer::singleShot(1, _pFFmpegCaptureAudioManager, SLOT(slot_closeAudio()));
#if TEST_WRITE_AUDIO_FILE
    _audioDataFile.close();
#endif
}
void RecordWidget::on_pushButton_startRecord_clicked()
{
    _pFFmpegRecordManager->setFileName(QString("%1/%2.mp4")
                                       .arg(ui->lineEdit_dirPath->text())
                                       .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm_ss")));
    QTimer::singleShot(1, _pFFmpegRecordManager, SLOT(slot_startRecord()));
    // 录像状态
    _recording = true;
}
void RecordWidget::on_pushButton_stopRecord_clicked()
{
    // 录像状态则停止录
    if(_recording)
    {
        _recording = false;
    }
    QTimer::singleShot(1, _pFFmpegRecordManager, SLOT(slot_stopRecord()));
}
void RecordWidget::on_pushButton_detectDevice_clicked()
{
    QString cmd = QString("ffmpeg.exe -list_devices true -f dshow -i dummy");
    // Qt5.14.2 可以弹出
    {
        // 这里对pProcess最好做管理,不能一直new,这里代码没有做了,随进程一起销毁
        QProcess * pProcess = new QProcess(this);
        // setCreateProcessArgumentsModifier定义传入就是一个函数指针
        // qt5.9.3可以弹出,此版本Qt5.14版本,还有个版本不会弹出,需要设这样设置
        // 添加windows.h头文件
        pProcess->setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args)
        {
            args->flags |= CREATE_NEW_CONSOLE;
            args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
            args->startupInfo->dwFlags |= STARTF_USEFILLATTRIBUTE;
            args->startupInfo->dwFillAttribute = BACKGROUND_BLUE | FOREGROUND_RED
                                               | FOREGROUND_INTENSITY;
        });
#if 0
        // 使用startDetached无法弹出,那么QProcess process得方式去startDetached就没有意义了
//        pProcess->startDetached("cmd.exe", QStringList() << "/k" << cmd);
#endif
        LOG << cmd;
        pProcess->start("cmd.exe", QStringList() << "/k" << cmd);
    }
}
void RecordWidget::on_pushButton_detectAudio_clicked()
{
    QStringList args;
    args << "ffmpeg.exe" << "-f" << "dshow" << "-list_options" << "true" << "-i" << QString("audio=%1").arg(ui->comboBox_audioDevName->currentText());
    // Qt5.14.2 可以弹出
    {
        // 这里对pProcess最好做管理,不能一直new,这里代码没有做了,随进程一起销毁
        QProcess * pProcess = new QProcess(this);
        // setCreateProcessArgumentsModifier定义传入就是一个函数指针
        // qt5.9.3可以弹出,此版本Qt5.14版本,还有个版本不会弹出,需要设这样设置
        // 添加windows.h头文件
        pProcess->setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args)
        {
            args->flags |= CREATE_NEW_CONSOLE;
            args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
            args->startupInfo->dwFlags |= STARTF_USEFILLATTRIBUTE;
            args->startupInfo->dwFillAttribute = BACKGROUND_BLUE | FOREGROUND_RED
                                               | FOREGROUND_INTENSITY;
        });
#if 0
        // 使用startDetached无法弹出,那么QProcess process得方式去startDetached就没有意义了
        pProcess->start("cmd.exe", QStringList() << "/k" << cmd);
#else
        pProcess->start("cmd.exe", QStringList() << "/k" << args);
#endif
    }
}
void RecordWidget::on_pushButton_detectCamera_clicked()
{
    QStringList args;
    args << "ffmpeg.exe" << "-f" << "dshow" << "-list_options" << "true" << "-i" << QString("video=%1").arg(ui->comboBox_cameraDevName->currentText());
    // Qt5.14.2 可以弹出
    {
        // 这里对pProcess最好做管理,不能一直new,这里代码没有做了,随进程一起销毁
        QProcess * pProcess = new QProcess(this);
        // setCreateProcessArgumentsModifier定义传入就是一个函数指针
        // qt5.9.3可以弹出,此版本Qt5.14版本,还有个版本不会弹出,需要设这样设置
        // 添加windows.h头文件
        pProcess->setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args)
        {
            args->flags |= CREATE_NEW_CONSOLE;
            args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
            args->startupInfo->dwFlags |= STARTF_USEFILLATTRIBUTE;
            args->startupInfo->dwFillAttribute = BACKGROUND_BLUE | FOREGROUND_RED
                                               | FOREGROUND_INTENSITY;
        });
#if 0
        // 使用startDetached无法弹出,那么QProcess process得方式去startDetached就没有意义了
        pProcess->start("cmd.exe", QStringList() << "/k" << cmd);
#else
        pProcess->start("cmd.exe", QStringList() << "/k" << args);
#endif
    }
}
        FFmepgCaptureAudio.h:采集音频对齐aac1024抛出QByteArray
            
            
              cpp
              
              
            
          
          #ifndef FFMPEGCAPTUREAUDIOMANAGER_H
#define FFMPEGCAPTUREAUDIOMANAGER_H
#include <QObject>
#include <QString>
#include <QDebug>
#include <QTimer>
#include <QThread>
#include <QImage>
#include <QProcess>
#include <QMessageBox>
#include "ffmpeg5_1_2env.h"
#define LOG qDebug()<<__FILE__<<__LINE__
class FFmpegCaptureAudioManager : public QObject
{
    Q_OBJECT
public:
    // 音频状态
    enum AUDIO_STATE {
        AUDIO_STATE_NONE = 0x00,            // 无状态
        AUDIO_STATE_OPENING,                // 正在打开麦克风
        AUDIO_STATE_OPENED,                 // 打开了
        AUDIO_STATE_OPEN_FAILED,            // 打开失败
        AUDIO_STATE_CLOSING,                // 正在关闭麦克风
        AUDIO_STATE_CLOSED,                 // 已关闭
    };
public:
    explicit FFmpegCaptureAudioManager(QObject *parent = 0);
public:
    QString getAudioDevName();
    int getSampleRate();
    int getChannels();
public:
    void setAudioDevName(QString audioDevName);
    void setSampleRate(int sampleRate);
    void setChannels(int channels);
signals:
    void signal_stateChanged(FFmpegCaptureAudioManager::AUDIO_STATE state);
    void signal_openedAudioInfo(QString audioName, int sampleRate, int channels);
    void signal_captureOneFrame(QByteArray byteArray);      // 重采样、且做了fifo,满足1024个点才抛出
public slots:
    void slot_openAudio();                           // 打开麦克风(采集,但是不录音)
    void slot_closeAudio();                          // 关闭麦克风(关闭麦克风,必须先停止录音)
public slots:
    void slot_start();                          // 线程运行
    void slot_stop();                           // 线程停止
protected:
    void initControl();
protected:
    void startRecord();                         // 创建录音资源
    void stopRecord();                          // 释放录音资源
protected slots:
    void slot_captureOneFrame();                // 内部循环
protected:
    int convertPcmToAac(AVFrame *pSrcFrame, AVFrame *pDstFrame);
    void cleanup();
private:
    bool _running;                              // 线程是否在运行
    AUDIO_STATE _state;                         // 音频状态
private:
    QString _audioDevName;                      // 麦克风名称
    int _sampleRate;                            // 采样率
    int _channels;                              // 通道数
private:                                        // ffmpeg相关类
    AVFormatContext *_pFormatContext;      // 全局上下文(输入)
    AVInputFormat *_pInputFormat;               // 输入格式
    AVDictionary* _pOptions;               // 打开编码器的配置
    int _audioStreamIndex;                      // 存储查找到的音频流序号
    AVCodecContext * _pDecCodecContext;         // 音频解码器上下文(采集音频数据pcm压缩包AVPacket 解码为 原始pcm数据AVFrame数据帧)
    AVCodec * _pDecCodec;                       // 音频解码器(采集音频数据pcm压缩包AVPacket 解码为 原始pcm数据AVFrame数据帧)
    AVCodec * _pEncCodec;                       // 音频编码器(原始pcm的AVFrame 编码为 原始aac的AVFrame数据帧)
    SwrContext *_pSwrContext;                   // 音频重采样转换上下文
    AVPacket *_pDecPacket;                    // 解码前的pcm数据包
    AVFrame * _pDecFrame;                     // 采集解码后的pcm原始数据帧
    AVFrame * _pAacFrame;                     // 重采样编码输出aac原始数据帧
    AVAudioFifo * _pAudioFifo;                  // ffmpeg的音频采样点fifo队列
    AVStream *_pEncAVStream;                    // 编码流
    AVAudioFifo *_pAVAudioFifo;                 // 使用fifo队列进行传递
    int _aacBufferSizePerChannel;               // 一帧数据大小
    uint8_t *_pAacBuffer[2];                    // 指针,缓存一帧1024点aac的原始数据,用于生成byteArray
};
#endif // FFmpegCaptureAudioManager_H
        FFmpegCaptureCameraManager.h:采集摄像头抛出QImage
            
            
              cpp
              
              
            
          
          #ifndef FFMPEGCAPTURECAMERAMANAGER_H
#define FFMPEGCAPTURECAMERAMANAGER_H
#include <QObject>
#include <QString>
#include <QDebug>
#include <QTimer>
#include <QThread>
#include <QImage>
#include <QProcess>
#include <QMessageBox>
#include "ffmpeg5_1_2env.h"
class FFmpegCaptureCameraManager : public QObject
{
    Q_OBJECT
public:
    enum CAMERA_STATE {
        CAMERA_STATE_NONE = 0x00,           // 无状态,未有任何操作
        CAMERA_STATE_OPENING,               // 正在打开摄像头
        CAMERA_STATE_OPENED,                // 打开了
        CAMERA_STATE_OPEN_FAILED,           // 打开失败
        CAMERA_STATE_CLOSEING,              // 正在关闭摄像头
        CAMERA_STATE_CLOSED,                // 已关闭
    };
public:
    explicit FFmpegCaptureCameraManager(QObject *parent = 0);
signals:
    void signal_stateChanged(FFmpegCaptureCameraManager::CAMERA_STATE state);
    void signal_openedCameraInfo(QString cameraDevName, int width, int height, int fps);
    void signal_captureOneFrame(QImage image);
public:
    static QString getAvcodecConfiguration();
public:
    QString getCameraDevName();
    int width();
    int height();
    int fps();
public:
    void setCameraDevName(QString cameraDevName);
    void setWidth(int width);
    void setHeight(int height);
    void setFps(int fps);
public slots:
    void slot_start();
    void slot_stop();
    void slot_openCamera();
    void slot_closeCamera();
protected:
    void initControl();
protected slots:
    void slot_captureOneFrame();
protected:
    int convertYuvToRgb888(AVFrame * pSrcFrame, AVFrame * pDstFrame);
    void cleanup();                             // 释放资源
signals:
public slots:
private:                                        // 内部状态标记
    bool _running;                              // 线程运行状态
    CAMERA_STATE _state;                        // 摄像头状态
private:                                        // 可设置获取的输入参数
    QString _cameraDevName;                     // 摄像头名称
    int _width;                                 // 宽度
    int _height;                                // 高度
    int _fps;                                   // 帧率
private:                                        // ffmpeg相关类
    AVFormatContext * _pFormatContext;          // 全局格式上下文
    AVInputFormat * _pInputFormat;              // 输入文件格式
    AVDictionary * _pOptions;                   // 打开编码器的配置
    AVCodecContext * _pDecCodecContext;         // 视频解码器上下文(不带音频)
    AVCodec * _pDecCodec;                       // 视频解码器(不带音频)
    int _videoStreamIndex;                      // 视频流序号
    int _rgbBufferSize;                         // 数据内存有效大小
    uint8_t *_pRgbBuffer;                       // 数据内存指针
    AVPacket *_pDecPacket;                      // 解码压缩包
    SwsContext *_pSwsContext;                   // 转码上下文
    AVFrame * _pDecFrame;                       // 解码帧
    AVFrame * _pRgbFrame;                       // rgb数据帧
};
#endif // FFmpegCaptureCameraManager_H
        FFmpegRecordManager.h:录制音视频(接收音频视频帧,音频/视频编码,音视频同步录制mp4)
            
            
              cpp
              
              
            
          
          #ifndef FFMPEGRECORDMANAGER_H
#define FFMPEGRECORDMANAGER_H
#include <QObject>
#include <QTimer>
#include <QFile>
#include <QImage>
#include "ffmpeg5_1_2env.h"
class FFmpegRecordManager : public QObject
{
    Q_OBJECT
public:
    enum RECORD_STATE {
        RECORD_STATE_NONE = 0x00,       // 没有录像
        RECORD_STATE_STARTING,          // 正在打开录像
        RECORD_STATE_START_FAILED,      // 打开录像失败
        RECORD_STATE_RECORDING,         // 正在录像中
        RECORD_STATE_FINISHING,         // 正在关闭录像
        RECORD_STATE_FINISHED,          // 已结束录像
    };
public:
    explicit FFmpegRecordManager(QObject *parent = 0);
public:
    int getFps() const;                 // 获取编码的帧率
    int getWidthIn() const;             // 获取编码前输入的图像宽度
    int getHeightIn() const;            // 获取编码前输入的图像高度
    int getWidthOut() const;            // 获取编码后输出的图像宽度
    int getHeightOut() const;           // 获取编码后输出的图像高度
    int getValue() const;               // 获取编码的星期度(1~100)
    QString getFileName();
public:
    void setFps(int fps);               // 设置编码的帧率
    void setWidthIn(int widthIn);       // 设置编码前输入的图像宽度
    void setHeightIn(int heightIn);     // 设置编码前输入的图像高度
    void setWidthOut(int widthOut);     // 设置编码后输出的图像宽度
    void setHeightOut(int heightOut);   // 设置编码后输出的图像高度
    void setValue(int value);           // 设置编码的清晰度(1~100)
    void setFileName(QString fileName);
signals:
    void signal_stateChanged(FFmpegRecordManager::RECORD_STATE state);
public slots:
    void slot_start();                  // 开始运行(单独开子线程)
    void slot_stop();                   // 停止运行(单独开子线程)
public slots:
    void slot_startRecord();            // 开始编码,输入编码的文件名称
    void slot_stopRecord();             // 停止编码,保存文件
public slots:
    void slot_encodeOneFrame();
    void slot_recordVideoFrame(QImage image);           // 使用消息,压入队列
    void slot_recordAudioFrame(QByteArray byteArray);   // 使用消息,压入队列
protected:
    int encodeWrite(AVCodecContext *pCodeContext, AVFormatContext *pFormatContext, AVStream *pStream, AVFrame *pFrame, AVPacket *pPacket);
    int convertRgb888ToYuv(AVFrame * pSrcFrame, AVFrame * pDstFrame);
    void cleanup();
    void finishing();
private:
    bool _running;                          // 是否正在运行
    RECORD_STATE _state;
private:
    QString _fileName;                          // 保存的文件名
private:
    QList<QImage> _listImage;
    QList<QByteArray> _listByteArray;
private:
    int _fps;                                   // 帧率
    int _widthIn;                               // 输入编码宽度
    int _heightIn;                              // 输入编码高度
    int _widthOut;                              // 输出编码宽度
    int _heightOut;                             // 输出编码高度
private:
private:                                        // ffmpeg
    AVFormatContext *_pFileFormatContext;       // 文件上下文
private:
    AVStream *_pEncVideoStream;                 // 编码流
    AVCodec *_pEncVideoCodec;                   // 编码器
    AVCodecContext *_pEncVideoCodecContext;     // 编码器上下文
    AVFrame *_pRgbVideoFrame;                   // rgb原始数据帧
    AVFrame *_pEncVideoFrame;                   // yuv原始数据包
    AVPacket *_pEncVideoPacket;                 // 编码包
    int _videoFrameIndex;                       // 帧序号
    int _rgbBufferSize;                         // 输入编码格式的帧字节大小
    uint8_t *_pRgbBuffer;                       // 数据缓存
    struct SwsContext *_pSwsContext;            // ffmpeg编码数据格式转换
private:
    AVStream *_pEncAudioStream;                 // 编码流
    AVCodec *_pEncAudioCodec;                   // 编码器
    AVCodecContext *_pEncAudioCodecContext;     // 编码器上下文
    AVFrame *_pEncAudioFrame;                   // pcm原始数据帧
    AVPacket *_pEncAudioPacket;                 // 编码包
    int _audioFrameIndex;                       // 帧序号
};
#endif // FFMPEGMANAGER_H
        本文章博客地址:https://hpzwl.blog.csdn.net/article/details/154340644