Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
ZLMediaKit
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
张翔宇
ZLMediaKit
Commits
76bece02
Commit
76bece02
authored
May 09, 2020
by
xiongziliang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
实现实时生成和获取截图的http api
parent
d8e5dbb5
隐藏空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
117 行增加
和
3 行删除
+117
-3
conf/config.ini
+5
-0
server/FFmpegSource.cpp
+34
-2
server/FFmpegSource.h
+17
-0
server/WebApi.cpp
+61
-1
没有找到文件。
conf/config.ini
查看文件 @
76bece02
...
...
@@ -4,16 +4,21 @@ apiDebug=1
#一些比较敏感的http api在访问时需要提供secret,否则无权限调用
#如果是通过127.0.0.1访问,那么可以不提供secret
secret
=
035c73f7-bb6b-4889-a715-d9eb2d1925cc
#截图保存路径根目录,截图通过http api(/index/api/makeSnap)生成和获取
snapRoot
=
./www/snap/
[ffmpeg]
#FFmpeg可执行程序绝对路径
bin
=
/usr/local/bin/ffmpeg
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
cmd
=
%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
#FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量
snap
=
%s -i %s -y -f mjpeg -t 0.001 %s
#FFmpeg日志的路径,如果置空则不生成FFmpeg日志
#可以为相对(相对于本可执行程序目录)或绝对路径
log
=
./ffmpeg/ffmpeg.log
[general]
#是否启用虚拟主机
enableVhost
=
0
...
...
server/FFmpegSource.cpp
查看文件 @
76bece02
...
...
@@ -13,21 +13,25 @@
#include "Common/MediaSource.h"
#include "Util/File.h"
#include "System.h"
#include "Thread/WorkThreadPool.h"
namespace
FFmpeg
{
#define FFmpeg_FIELD "ffmpeg."
const
string
kBin
=
FFmpeg_FIELD
"bin"
;
const
string
kCmd
=
FFmpeg_FIELD
"cmd"
;
const
string
kLog
=
FFmpeg_FIELD
"log"
;
const
string
kSnap
=
FFmpeg_FIELD
"snap"
;
onceToken
token
([]()
{
#ifdef _WIN32
string
ffmpeg_bin
=
System
::
execute
(
"where ffmpeg"
);
//windows下先关闭FFmpeg日志(目前不支持日志重定向)
mINI
::
Instance
()[
kCmd
]
=
"%s -re -i
\"
%s
\"
-loglevel quiet -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s "
;
mINI
::
Instance
()[
kCmd
]
=
"%s -re -i %s -loglevel quiet -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"
;
mINI
::
Instance
()[
kSnap
]
=
"%s -i %s -loglevel quiet -y -f mjpeg -t 0.001 %s"
;
#else
string
ffmpeg_bin
=
System
::
execute
(
"which ffmpeg"
);
mINI
::
Instance
()[
kCmd
]
=
"%s -re -i
\"
%s
\"
-c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s "
;
mINI
::
Instance
()[
kCmd
]
=
"%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"
;
mINI
::
Instance
()[
kSnap
]
=
"%s -i %s -y -f mjpeg -t 0.001 %s"
;
#endif
//默认ffmpeg命令路径为环境变量中路径
mINI
::
Instance
()[
kBin
]
=
ffmpeg_bin
.
empty
()
?
"ffmpeg"
:
ffmpeg_bin
;
...
...
@@ -232,3 +236,31 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
_listener
=
src
->
getListener
();
src
->
setListener
(
shared_from_this
());
}
void
FFmpegSnap
::
makeSnap
(
const
string
&
play_url
,
const
string
&
save_path
,
float
timeout_sec
,
const
function
<
void
(
bool
)
>
&
cb
)
{
GET_CONFIG
(
string
,
ffmpeg_bin
,
FFmpeg
::
kBin
);
GET_CONFIG
(
string
,
ffmpeg_snap
,
FFmpeg
::
kSnap
);
GET_CONFIG
(
string
,
ffmpeg_log
,
FFmpeg
::
kLog
);
std
::
shared_ptr
<
Process
>
process
=
std
::
make_shared
<
Process
>
();
auto
delayTask
=
EventPollerPool
::
Instance
().
getPoller
()
->
doDelayTask
(
timeout_sec
*
1000
,[
process
,
cb
](){
if
(
process
->
wait
(
false
)){
//FFmpeg进程还在运行,超时就关闭它
process
->
kill
(
2000
);
}
return
0
;
});
WorkThreadPool
::
Instance
().
getPoller
()
->
async
([
process
,
play_url
,
save_path
,
delayTask
,
cb
](){
char
cmd
[
1024
]
=
{
0
};
snprintf
(
cmd
,
sizeof
(
cmd
),
ffmpeg_snap
.
data
(),
ffmpeg_bin
.
data
(),
play_url
.
data
(),
save_path
.
data
());
process
->
run
(
cmd
,
ffmpeg_log
.
empty
()
?
""
:
File
::
absolutePath
(
""
,
ffmpeg_log
));
//等待FFmpeg进程退出
process
->
wait
(
true
);
//FFmpeg进程退出了可以取消定时器了
delayTask
->
cancel
();
//执行回调函数
cb
(
process
->
exit_code
()
==
0
);
});
}
server/FFmpegSource.h
查看文件 @
76bece02
...
...
@@ -23,6 +23,23 @@ using namespace std;
using
namespace
toolkit
;
using
namespace
mediakit
;
namespace
FFmpeg
{
extern
const
string
kSnap
;
}
class
FFmpegSnap
{
public
:
/// 创建截图
/// \param play_url 播放url地址,只要FFmpeg支持即可
/// \param save_path 截图jpeg文件保存路径
/// \param timeout_sec 生成截图超时时间(防止阻塞太久)
/// \param cb 生成截图成功与否回调
static
void
makeSnap
(
const
string
&
play_url
,
const
string
&
save_path
,
float
timeout_sec
,
const
function
<
void
(
bool
)
>
&
cb
);
private
:
FFmpegSnap
()
=
delete
;
~
FFmpegSnap
()
=
delete
;
};
class
FFmpegSource
:
public
std
::
enable_shared_from_this
<
FFmpegSource
>
,
public
MediaSourceEvent
{
public
:
typedef
shared_ptr
<
FFmpegSource
>
Ptr
;
...
...
server/WebApi.cpp
查看文件 @
76bece02
...
...
@@ -50,10 +50,13 @@ typedef enum {
#define API_FIELD "api."
const
string
kApiDebug
=
API_FIELD
"apiDebug"
;
const
string
kSecret
=
API_FIELD
"secret"
;
const
string
kSnapRoot
=
API_FIELD
"snapRoot"
;
static
onceToken
token
([]()
{
mINI
::
Instance
()[
kApiDebug
]
=
"1"
;
mINI
::
Instance
()[
kSecret
]
=
"035c73f7-bb6b-4889-a715-d9eb2d1925cc"
;
mINI
::
Instance
()[
kSnapRoot
]
=
"./www/snap/"
;
});
}
//namespace API
...
...
@@ -174,7 +177,7 @@ static inline void addHttpListener(){
size
=
body
->
remainSize
();
}
if
(
size
<
4
*
1024
){
if
(
size
&&
size
<
4
*
1024
){
string
contentOut
=
body
->
readData
(
size
)
->
toString
();
DebugL
<<
"
\r\n
# request:
\r\n
"
<<
parser
.
Method
()
<<
" "
<<
parser
.
FullUrl
()
<<
"
\r\n
"
<<
"# content:
\r\n
"
<<
parser
.
Content
()
<<
"
\r\n
"
...
...
@@ -817,6 +820,63 @@ void installWebApi() {
val
[
"data"
][
"paths"
]
=
paths
;
});
GET_CONFIG
(
string
,
snap_root
,
API
::
kSnapRoot
);
//获取截图缓存或者实时截图
//http://127.0.0.1/index/api/getSnap?url=rtmp://127.0.0.1/record/robot.mp4&timeout_sec=10&expire_sec=3
api_regist2
(
"/index/api/getSnap"
,
[](
API_ARGS2
){
CHECK_SECRET
();
CHECK_ARGS
(
"url"
,
"timeout_sec"
,
"expire_sec"
);
auto
file_prefix
=
MD5
(
allArgs
[
"url"
]).
hexdigest
()
+
"_"
;
string
file_path
;
int
expire_sec
=
allArgs
[
"expire_sec"
];
File
::
scanDir
(
File
::
absolutePath
(
snap_root
,
""
),[
&
](
const
string
&
path
,
bool
isDir
){
if
(
!
isDir
){
auto
pos
=
path
.
find
(
file_prefix
);
if
(
pos
!=
string
::
npos
){
//找到截图
auto
tm
=
FindField
(
path
.
data
()
+
pos
+
file_prefix
.
size
(),
nullptr
,
".jpeg"
);
if
(
atoll
(
tm
.
data
())
+
expire_sec
<
time
(
NULL
)){
//截图已经过期,删除之,后面重新生成
File
::
delete_file
(
path
.
data
());
}
else
{
//截图未过期
file_path
=
path
;
}
return
false
;
}
}
return
true
;
});
if
(
!
file_path
.
empty
()){
//返回上次生成的截图
StrCaseMap
headerOut
;
headerOut
[
"Content-Type"
]
=
HttpFileManager
::
getContentType
(
".jpeg"
);
invoker
.
responseFile
(
headerIn
,
headerOut
,
file_path
);
return
;
}
//无截图或者截图已经过期
file_path
=
File
::
absolutePath
(
StrPrinter
<<
file_prefix
<<
time
(
NULL
)
<<
".jpeg"
,
snap_root
);
#if !defined(_WIN32)
//创建文件夹
File
::
create_path
(
file_path
.
c_str
(),
S_IRWXO
|
S_IRWXG
|
S_IRWXU
);
#else
File
::
create_path
(
file_path
.
c_str
(),
0
);
#endif
FFmpegSnap
::
makeSnap
(
allArgs
[
"url"
],
file_path
,
allArgs
[
"timeout_sec"
],[
invoker
,
headerIn
,
file_path
](
bool
success
){
if
(
!
success
){
//生成截图失败,可能残留空文件
File
::
delete_file
(
file_path
.
data
());
}
StrCaseMap
headerOut
;
headerOut
[
"Content-Type"
]
=
HttpFileManager
::
getContentType
(
".jpeg"
);
invoker
.
responseFile
(
headerIn
,
headerOut
,
file_path
);
});
});
////////////以下是注册的Hook API////////////
api_regist1
(
"/index/hook/on_publish"
,[](
API_ARGS1
){
//开始推流事件
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论