Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
mahuifa
QMDemo
提交
5f93bedf
Q
QMDemo
项目概览
mahuifa
/
QMDemo
通知
1
Star
2
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
Q
QMDemo
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
5f93bedf
编写于
12月 28, 2022
作者:
mahuifa
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:完成FFmpeg录屏基本功能
上级
c5191464
变更
8
隐藏空白更改
内联
并排
Showing
8 changed file
with
65 addition
and
50 deletion
+65
-50
FFmpegDemo/Screencap/Screencap/readthread.cpp
FFmpegDemo/Screencap/Screencap/readthread.cpp
+12
-1
FFmpegDemo/Screencap/Screencap/readthread.h
FFmpegDemo/Screencap/Screencap/readthread.h
+2
-0
FFmpegDemo/Screencap/Screencap/videocodec.cpp
FFmpegDemo/Screencap/Screencap/videocodec.cpp
+7
-23
FFmpegDemo/Screencap/Screencap/videodecode.cpp
FFmpegDemo/Screencap/Screencap/videodecode.cpp
+3
-14
FFmpegDemo/Screencap/Screencap/videodecode.h
FFmpegDemo/Screencap/Screencap/videodecode.h
+0
-2
FFmpegDemo/Screencap/widget.cpp
FFmpegDemo/Screencap/widget.cpp
+21
-1
FFmpegDemo/Screencap/widget.h
FFmpegDemo/Screencap/widget.h
+2
-0
FFmpegDemo/Screencap/widget.ui
FFmpegDemo/Screencap/widget.ui
+18
-9
未找到文件。
FFmpegDemo/Screencap/Screencap/readthread.cpp
浏览文件 @
5f93bedf
...
...
@@ -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
()
<<
"打开输出文件失败!"
;
...
...
FFmpegDemo/Screencap/Screencap/readthread.h
浏览文件 @
5f93bedf
...
...
@@ -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
;
// 控制视频播放速度(更精确,但不支持视频后退)
};
...
...
FFmpegDemo/Screencap/Screencap/videocodec.cpp
浏览文件 @
5f93bedf
...
...
@@ -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
);
...
...
FFmpegDemo/Screencap/Screencap/videodecode.cpp
浏览文件 @
5f93bedf
...
...
@@ -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函数调用异常信息
...
...
FFmpegDemo/Screencap/Screencap/videodecode.h
浏览文件 @
5f93bedf
...
...
@@ -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
;
// 保存异常信息
...
...
FFmpegDemo/Screencap/widget.cpp
浏览文件 @
5f93bedf
#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
);
}
FFmpegDemo/Screencap/widget.h
浏览文件 @
5f93bedf
...
...
@@ -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
;
...
...
FFmpegDemo/Screencap/widget.ui
浏览文件 @
5f93bedf
...
...
@@ -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=
"Q
PushButton"
name=
"but_open
"
>
<widget
class=
"Q
Widget"
name=
"
"
>
<property
name=
"geometry"
>
<rect>
<x>
40
</x>
<y>
8
0
</y>
<width>
75
</width>
<height>
2
3
</height>
<y>
7
0
</y>
<width>
421
</width>
<height>
2
5
</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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录