实现实时生成和获取截图的http api

This commit is contained in:
xiongziliang 2020-05-09 00:06:36 +08:00
parent d8e5dbb5b8
commit 76bece0217
4 changed files with 117 additions and 3 deletions

View File

@ -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

View File

@ -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);
});
}

View File

@ -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;

View File

@ -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){
//开始推流事件