案例分享:音视频录像综合应用(支持录制麦克风音频、录制摄像头视频、同步录制音视频,支持opencv对图形进行处理,录制mp4文件)

若该文为原创文章,转载请注明出处

本文章博客地址: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录屏应用(支持帧率、清晰度设置)

项目实战: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

OpenCV开发笔记(七):OpenCV基础图形绘制

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

相关推荐
ZC跨境爬虫18 分钟前
跟着 MDN 学 HTML day_52:(深入 XPathExpression 接口)
开发语言·前端·javascript·ui·html·音视频
观北海1 小时前
听觉智能新纪元:AST音频技术全景解读
音视频
XD7429716361 小时前
科技早报晚报|2026年5月15日:无摄像头空间感知、Android 设备实验室与视频检索代理,今天更值得跟进的 3 个技术机会
android·科技·音视频·开源项目·边缘ai·开发者工具
aqi002 小时前
FFmpeg开发笔记(一百零一)跨平台的开源音视频移动框架MobileFFmpeg
android·ffmpeg·音视频·直播·流媒体
小歆8842 小时前
音频分析仪推荐
音视频
菊风 Juphoon3 小时前
如何让车载通话从“能用”变“好用”?请看菊风智能车载音视频解决方案
音视频
互联网科技看点3 小时前
以标准立标杆,以技术赢口碑——园世赋能中国运动音频高质量发展
音视频
沉浸式学习ing3 小时前
播客和视频怎么变成知识库里的笔记?音视频转结构化笔记完整方案
人工智能·笔记·gpt·学习·ai·音视频·notion
沃普天科技4 小时前
USB显示器多屏异显多屏拼接IF8032 IT690 VL171 8801 RTD2556
arm开发·驱动开发·算法·计算机外设·音视频·硬件工程·pcb工艺
byte轻骑兵4 小时前
【LE Audio】CAP精讲[6]: 控制中枢操盘指南,Commander协同全流程拆解
人工智能·音视频·le audio·低功耗音频