mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
实现实时生成和获取截图的http api
This commit is contained in:
parent
d8e5dbb5b8
commit
76bece0217
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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){
|
||||
//开始推流事件
|
||||
|
Loading…
Reference in New Issue
Block a user