若该文为原创文章,转载请注明出处
本文章博客地址: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