提交 5f93bedf 编写于 作者: mahuifa's avatar mahuifa

feat:完成FFmpeg录屏基本功能

上级 c5191464
......@@ -23,6 +23,17 @@ ReadThread::~ReadThread()
}
}
/**
* @brief 设置视频保存地址
* @param path
*/
void ReadThread::setPath(const QString &path)
{
if(path.isEmpty()) return;
m_path = path;
}
/**
* @brief 传入播放的视频地址并开启线程
* @param url
......@@ -74,7 +85,7 @@ void ReadThread::run()
m_etime1.start();
emit playState(play);
bool ret = m_videoCodec->open(m_videoDecode->getCodecContext(), m_videoDecode->avgFrameRate(), "./1.mp4");
bool ret = m_videoCodec->open(m_videoDecode->getCodecContext(), m_videoDecode->avgFrameRate(), m_path);
if(!ret)
{
qDebug() << "打开输出文件失败!";
......
......@@ -30,6 +30,7 @@ public:
explicit ReadThread(QObject *parent = nullptr);
~ReadThread() override;
void setPath(const QString& path);
void open(const QString& url = QString()); // 打开视频
void close(); // 关闭视频
const QString& url(); // 获取打开的视频地址
......@@ -45,6 +46,7 @@ private:
VideoDecode* m_videoDecode = nullptr; // 视频解码类
VideoCodec* m_videoCodec = nullptr;
QString m_url; // 打开的视频地址
QString m_path; // 视频保存路径
bool m_play = false; // 播放控制
QElapsedTimer m_etime1; // 控制视频播放速度(更精确,但不支持视频后退)
};
......
......@@ -28,18 +28,9 @@ bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString
{
if(!codecContext || fileName.isEmpty()) return false;
// 通过输出文件名为输出格式分配AVFormatContext。
#if USE_H264
int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, "h264", fileName.toStdString().data());
#else
/**
* 摄像头打开使用的是mjpeg编码器;
* MJPEG压缩技术可以获取清晰度很高的视频图像,可以【动态调整帧率】适合保存摄像头视频、分辨率。但由于没有考虑到帧间变化,造成大量冗余信息被重复存储,因此单帧视频的占用空间较大;
* 如果采用其它编码器,由于摄像头曝光时间长度不一定,所以录像时帧率一直在变,编码器指定固定帧率会导致视频一会快一会慢,效果很不好,适用于录制固定帧率的视频(当然其它编码器应该是有处理办法,不过我还不清楚);
*/
QString strName = avcodec_find_encoder(codecContext->codec_id)->name; // 获取编码器名称
int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, strName.toStdString().data(), fileName.toStdString().data()); // 这里使用和解码一样的编码器,防止保存的图像颜色出问题
#endif
// 通过输出文件名为输出格式分配AVFormatContext。参数3编码器设置为空,由参数4文件名后缀推测合适的编码器
int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, nullptr, fileName.toStdString().data());
if(ret < 0)
{
close();
......@@ -63,6 +54,7 @@ bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString
showError(AVERROR(ENOMEM));
return false;
}
qDebug() << codec->id <<" " << codec->name;
// 分配AVCodecContext并将其字段设置为默认值。
m_codecContext = avcodec_alloc_context3(codec);
......@@ -83,8 +75,8 @@ bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString
#endif
m_codecContext->time_base = {point.y(), point.x()}; //设置时间基,20为分母,1为分子,表示以1/20秒时间间隔播放一帧图像
m_codecContext->framerate = {point.x(), point.y()};
m_codecContext->bit_rate = codecContext->bit_rate; // 目标的码率,即采样的码率;显然,采样码率越大,视频大小越大,画质越高
m_codecContext->gop_size = codecContext->gop_size; // I帧间隔
m_codecContext->bit_rate = 4000000; // 目标的码率,即采样的码率;显然,采样码率越大,视频大小越大,画质越高
m_codecContext->gop_size = 12; // I帧间隔
m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// m_codecContext->max_b_frames = 1; // 非B帧之间的最大B帧数(有些格式不支持)
// m_codecContext->qmin = 1;
......@@ -95,22 +87,16 @@ bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString
// m_codecContext->bits_per_coded_sample = 24;
// m_codecContext->bits_per_raw_sample = 8;
// av_opt_set(m_codecContext->priv_data, "preset", "placebo", 0);
// qDebug() << m_codecContext->pix_fmt;
qDebug() << 5 <<" " << m_codecContext->time_base.den<<" " << m_codecContext->time_base.num;
// 打开编码器
ret = avcodec_open2(m_codecContext, nullptr, nullptr);
#if USE_H264
// ret = avcodec_open2(m_codecContext, codec, nullptr); // 使用h264时第一次打不开,第二次可以打卡,不知道什么原因
#endif
if(ret < 0)
{
// close();
close();
showError(ret);
return false;
}
qDebug() << 6;
// 向媒体文件添加新流
m_videoStream = avformat_new_stream(m_formatContext, nullptr);
if(!m_videoStream)
......@@ -119,7 +105,6 @@ bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString
showError(AVERROR(ENOMEM));
return false;
}
qDebug() << 7;
//拷贝一些参数,给codecpar赋值
ret = avcodec_parameters_from_context(m_videoStream->codecpar,m_codecContext);
......@@ -129,7 +114,6 @@ bool VideoCodec::open(AVCodecContext *codecContext, QPoint point, const QString
showError(ret);
return false;
}
qDebug() << 8;
// 写入文件头
ret = avformat_write_header(m_formatContext, nullptr);
......
......@@ -83,9 +83,9 @@ bool VideoDecode::open(const QString &url)
AVDictionary* dict = nullptr;
av_dict_set(&dict, "framerate", "20", 0); // 设置帧率,默认的是30000/1001,但是实际可能达不到30的帧率,所以最好手动设置
av_dict_set(&dict, "draw_mouse", "1", 0); // 指定是否绘制鼠标指针。0:不包含鼠标,1:包含鼠标
av_dict_set(&dict, "video_size", "500x400", 0); // 录制视频的大小(宽高),默认为全屏
av_dict_set(&dict, "offset_x", "100", 0); // 录制视频的起点X坐标
av_dict_set(&dict, "offset_y", "500", 0); // 录制视频的起点Y坐标
// av_dict_set(&dict, "video_size", "500x400", 0); // 录制视频的大小(宽高),默认为全屏
// av_dict_set(&dict, "offset_x", "100", 0); // 录制视频的起点X坐标
// av_dict_set(&dict, "offset_y", "500", 0); // 录制视频的起点Y坐标
// 打开输入流并返回解封装上下文
int ret = avformat_open_input(&m_formatContext, // 返回解封装上下文
......@@ -264,7 +264,6 @@ AVFrame* VideoDecode::read()
return nullptr;
}
m_pts = m_frame->pts;
if(m_frame->format == AV_PIX_FMT_YUV420P) // 如果图像格式为YUV420P则直接返回,不进行格式转换
{
return m_frame;
......@@ -319,7 +318,6 @@ void VideoDecode::close()
m_videoIndex = 0;
m_totalFrames = 0;
m_obtainFrames = 0;
m_pts = 0;
m_frameRate = 0;
m_size = QSize(0, 0);
}
......@@ -333,15 +331,6 @@ bool VideoDecode::isEnd()
return m_end;
}
/**
* @brief 返回当前帧图像播放时间
* @return
*/
const qint64 &VideoDecode::pts()
{
return m_pts;
}
/**
* @brief 显示ffmpeg函数调用异常信息
......
......@@ -36,7 +36,6 @@ public:
AVFrame* read(); // 读取视频图像
void close(); // 关闭
bool isEnd(); // 是否读取完成
const qint64& pts(); // 获取当前帧显示时间
AVCodecContext* getCodecContext(){return m_codecContext;}
QPoint avgFrameRate(){return m_avgFrameRate;}
......@@ -59,7 +58,6 @@ private:
qint64 m_totalTime = 0; // 视频总时长
qint64 m_totalFrames = 0; // 视频总帧数
qint64 m_obtainFrames = 0; // 视频当前获取到的帧数
qint64 m_pts = 0; // 图像帧的显示时间
qreal m_frameRate = 0; // 视频帧率
QSize m_size; // 视频分辨率大小
char* m_error = nullptr; // 保存异常信息
......
#include "widget.h"
#include "ui_widget.h"
#include <qfiledialog.h>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle(QString("Qt+ffmpeg录屏Demo V%1").arg(APP_VERSION));
m_readThread = new ReadThread();
connect(m_readThread, &ReadThread::playState, this, &Widget::on_playState);
}
......@@ -27,6 +31,7 @@ void Widget::on_but_open_clicked()
{
if(ui->but_open->text() == "开始录屏")
{
setSavePath();
m_readThread->open("desktop");
// m_readThread->open("C:/Users/mhf/Videos/1.mp4");
}
......@@ -44,7 +49,7 @@ void Widget::on_playState(ReadThread::PlayState state)
{
if(state == ReadThread::play)
{
this->setWindowTitle(QString("正在播放:%1").arg(m_readThread->url()));
this->setWindowTitle(QString("正在录制:%1").arg(ui->line_path->text()));
ui->but_open->setText("停止录制");
}
else
......@@ -53,3 +58,18 @@ void Widget::on_playState(ReadThread::PlayState state)
this->setWindowTitle(QString("Qt+ffmpeg录屏Demo V%1").arg(APP_VERSION));
}
}
/**
* @brief 设置文件保存路径
*/
void Widget::setSavePath()
{
QString strDefault = QString("%1/Videos/%2").arg(QDir::homePath()).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH-mm-ss"));
QString strPath = QFileDialog::getSaveFileName(this, "视频保存到~", strDefault,
"常用视频文件 (*.mp4 *.avi *.mov *.wmv *.flv *.h264 *.h265);;"
"其它文件格式 (*)");
if(strPath.isEmpty()) return;
ui->line_path->setText(strPath);
m_readThread->setPath(strPath);
}
......@@ -20,6 +20,8 @@ private slots:
void on_but_open_clicked();
void on_playState(ReadThread::PlayState state);
void setSavePath();
private:
Ui::Widget *ui;
ReadThread* m_readThread = nullptr;
......
......@@ -6,25 +6,34 @@
<rect>
<x>0</x>
<y>0</y>
<width>635</width>
<height>519</height>
<width>512</width>
<height>427</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QPushButton" name="but_open">
<widget class="QWidget" name="">
<property name="geometry">
<rect>
<x>40</x>
<y>80</y>
<width>75</width>
<height>23</height>
<y>70</y>
<width>421</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>开始录屏</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="line_path"/>
</item>
<item>
<widget class="QPushButton" name="but_open">
<property name="text">
<string>开始录屏</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册