mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-29 22:55:52 +08:00
实现实时生成和获取截图的http api
This commit is contained in:
parent
d8e5dbb5b8
commit
76bece0217
@ -4,16 +4,21 @@ apiDebug=1
|
|||||||
#一些比较敏感的http api在访问时需要提供secret,否则无权限调用
|
#一些比较敏感的http api在访问时需要提供secret,否则无权限调用
|
||||||
#如果是通过127.0.0.1访问,那么可以不提供secret
|
#如果是通过127.0.0.1访问,那么可以不提供secret
|
||||||
secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||||
|
#截图保存路径根目录,截图通过http api(/index/api/makeSnap)生成和获取
|
||||||
|
snapRoot=./www/snap/
|
||||||
|
|
||||||
[ffmpeg]
|
[ffmpeg]
|
||||||
#FFmpeg可执行程序绝对路径
|
#FFmpeg可执行程序绝对路径
|
||||||
bin=/usr/local/bin/ffmpeg
|
bin=/usr/local/bin/ffmpeg
|
||||||
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
||||||
cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
|
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日志
|
#FFmpeg日志的路径,如果置空则不生成FFmpeg日志
|
||||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||||
log=./ffmpeg/ffmpeg.log
|
log=./ffmpeg/ffmpeg.log
|
||||||
|
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
#是否启用虚拟主机
|
#是否启用虚拟主机
|
||||||
enableVhost=0
|
enableVhost=0
|
||||||
|
@ -13,21 +13,25 @@
|
|||||||
#include "Common/MediaSource.h"
|
#include "Common/MediaSource.h"
|
||||||
#include "Util/File.h"
|
#include "Util/File.h"
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
|
#include "Thread/WorkThreadPool.h"
|
||||||
|
|
||||||
namespace FFmpeg {
|
namespace FFmpeg {
|
||||||
#define FFmpeg_FIELD "ffmpeg."
|
#define FFmpeg_FIELD "ffmpeg."
|
||||||
const string kBin = FFmpeg_FIELD"bin";
|
const string kBin = FFmpeg_FIELD"bin";
|
||||||
const string kCmd = FFmpeg_FIELD"cmd";
|
const string kCmd = FFmpeg_FIELD"cmd";
|
||||||
const string kLog = FFmpeg_FIELD"log";
|
const string kLog = FFmpeg_FIELD"log";
|
||||||
|
const string kSnap = FFmpeg_FIELD"snap";
|
||||||
|
|
||||||
onceToken token([]() {
|
onceToken token([]() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
string ffmpeg_bin = System::execute("where ffmpeg");
|
string ffmpeg_bin = System::execute("where ffmpeg");
|
||||||
//windows下先关闭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
|
#else
|
||||||
string ffmpeg_bin = System::execute("which ffmpeg");
|
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
|
#endif
|
||||||
//默认ffmpeg命令路径为环境变量中路径
|
//默认ffmpeg命令路径为环境变量中路径
|
||||||
mINI::Instance()[kBin] = ffmpeg_bin.empty() ? "ffmpeg" : ffmpeg_bin;
|
mINI::Instance()[kBin] = ffmpeg_bin.empty() ? "ffmpeg" : ffmpeg_bin;
|
||||||
@ -232,3 +236,31 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
|||||||
_listener = src->getListener();
|
_listener = src->getListener();
|
||||||
src->setListener(shared_from_this());
|
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 toolkit;
|
||||||
using namespace mediakit;
|
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{
|
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource> , public MediaSourceEvent{
|
||||||
public:
|
public:
|
||||||
typedef shared_ptr<FFmpegSource> Ptr;
|
typedef shared_ptr<FFmpegSource> Ptr;
|
||||||
|
@ -50,10 +50,13 @@ typedef enum {
|
|||||||
#define API_FIELD "api."
|
#define API_FIELD "api."
|
||||||
const string kApiDebug = API_FIELD"apiDebug";
|
const string kApiDebug = API_FIELD"apiDebug";
|
||||||
const string kSecret = API_FIELD"secret";
|
const string kSecret = API_FIELD"secret";
|
||||||
|
const string kSnapRoot = API_FIELD"snapRoot";
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
mINI::Instance()[kApiDebug] = "1";
|
mINI::Instance()[kApiDebug] = "1";
|
||||||
mINI::Instance()[kSecret] = "035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
mINI::Instance()[kSecret] = "035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
||||||
|
mINI::Instance()[kSnapRoot] = "./www/snap/";
|
||||||
|
|
||||||
});
|
});
|
||||||
}//namespace API
|
}//namespace API
|
||||||
|
|
||||||
@ -174,7 +177,7 @@ static inline void addHttpListener(){
|
|||||||
size = body->remainSize();
|
size = body->remainSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(size < 4 * 1024){
|
if(size && size < 4 * 1024){
|
||||||
string contentOut = body->readData(size)->toString();
|
string contentOut = body->readData(size)->toString();
|
||||||
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
||||||
<< "# content:\r\n" << parser.Content() << "\r\n"
|
<< "# content:\r\n" << parser.Content() << "\r\n"
|
||||||
@ -817,6 +820,63 @@ void installWebApi() {
|
|||||||
val["data"]["paths"] = paths;
|
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////////////
|
////////////以下是注册的Hook API////////////
|
||||||
api_regist1("/index/hook/on_publish",[](API_ARGS1){
|
api_regist1("/index/hook/on_publish",[](API_ARGS1){
|
||||||
//开始推流事件
|
//开始推流事件
|
||||||
|
Loading…
Reference in New Issue
Block a user