You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
14KB

  1. /*
  2. * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
  3. *
  4. * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
  5. *
  6. * Use of this source code is governed by MIT license that can be found in the
  7. * LICENSE file in the root of the source tree. All contributing project authors
  8. * may be found in the AUTHORS file in the root of the source tree.
  9. */
  10. #include "FFmpegSource.h"
  11. #include "Common/config.h"
  12. #include "Common/MediaSource.h"
  13. #include "Util/File.h"
  14. #include "System.h"
  15. #include "Thread/WorkThreadPool.h"
  16. #include "Network/sockutil.h"
  17. using namespace std;
  18. using namespace toolkit;
  19. using namespace mediakit;
  20. namespace FFmpeg {
  21. #define FFmpeg_FIELD "ffmpeg."
  22. const string kBin = FFmpeg_FIELD"bin";
  23. const string kCmd = FFmpeg_FIELD"cmd";
  24. const string kLog = FFmpeg_FIELD"log";
  25. const string kSnap = FFmpeg_FIELD"snap";
  26. const string kRestartSec = FFmpeg_FIELD"restart_sec";
  27. onceToken token([]() {
  28. #ifdef _WIN32
  29. string ffmpeg_bin = trim(System::execute("where ffmpeg"));
  30. #else
  31. string ffmpeg_bin = trim(System::execute("which ffmpeg"));
  32. #endif
  33. //默认ffmpeg命令路径为环境变量中路径
  34. mINI::Instance()[kBin] = ffmpeg_bin.empty() ? "ffmpeg" : ffmpeg_bin;
  35. //ffmpeg日志保存路径
  36. mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
  37. mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s";
  38. mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -t 0.001 %s";
  39. mINI::Instance()[kRestartSec] = 0;
  40. });
  41. }
  42. FFmpegSource::FFmpegSource() {
  43. _poller = EventPollerPool::Instance().getPoller();
  44. }
  45. FFmpegSource::~FFmpegSource() {
  46. DebugL;
  47. }
  48. static bool is_local_ip(const string &ip){
  49. if (ip == "127.0.0.1" || ip == "localhost") {
  50. return true;
  51. }
  52. auto ips = SockUtil::getInterfaceList();
  53. for (auto &obj : ips) {
  54. if (ip == obj["ip"]) {
  55. return true;
  56. }
  57. }
  58. return false;
  59. }
  60. void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){
  61. _enable_hls = enable_hls;
  62. _enable_mp4 = enable_mp4;
  63. }
  64. void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) {
  65. GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
  66. GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd);
  67. GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
  68. _src_url = src_url;
  69. _dst_url = dst_url;
  70. _ffmpeg_cmd_key = ffmpeg_cmd_key;
  71. _media_info.parse(dst_url);
  72. auto ffmpeg_cmd = ffmpeg_cmd_default;
  73. if (!ffmpeg_cmd_key.empty()) {
  74. auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key);
  75. if (cmd_it != mINI::Instance().end()) {
  76. ffmpeg_cmd = cmd_it->second;
  77. } else{
  78. WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")";
  79. }
  80. }
  81. char cmd[2048] = {0};
  82. snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data());
  83. auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log);
  84. _process.run(cmd, log_file);
  85. InfoL << cmd;
  86. if (is_local_ip(_media_info._host)) {
  87. //推流给自己的,通过判断流是否注册上来判断是否正常
  88. if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){
  89. cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
  90. return;
  91. }
  92. weak_ptr<FFmpegSource> weakSelf = shared_from_this();
  93. findAsync(timeout_ms,[cb,weakSelf,timeout_ms](const MediaSource::Ptr &src){
  94. auto strongSelf = weakSelf.lock();
  95. if(!strongSelf){
  96. //自己已经销毁
  97. return;
  98. }
  99. if(src){
  100. //推流给自己成功
  101. cb(SockException());
  102. strongSelf->onGetMediaSource(src);
  103. strongSelf->startTimer(timeout_ms);
  104. return;
  105. }
  106. //推流失败
  107. if(!strongSelf->_process.wait(false)){
  108. //ffmpeg进程已经退出
  109. cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
  110. return;
  111. }
  112. //ffmpeg进程还在线,但是等待推流超时
  113. cb(SockException(Err_other,"等待超时"));
  114. });
  115. } else{
  116. //推流给其他服务器的,通过判断FFmpeg进程是否在线判断是否成功
  117. weak_ptr<FFmpegSource> weakSelf = shared_from_this();
  118. _timer = std::make_shared<Timer>(timeout_ms / 1000.0f,[weakSelf,cb,timeout_ms](){
  119. auto strongSelf = weakSelf.lock();
  120. if(!strongSelf){
  121. //自身已经销毁
  122. return false;
  123. }
  124. //FFmpeg还在线,那么我们认为推流成功
  125. if(strongSelf->_process.wait(false)){
  126. cb(SockException());
  127. strongSelf->startTimer(timeout_ms);
  128. return false;
  129. }
  130. //ffmpeg进程已经退出
  131. cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
  132. return false;
  133. },_poller);
  134. }
  135. }
  136. void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSource::Ptr &src)> &cb) {
  137. auto src = MediaSource::find(_media_info._schema,
  138. _media_info._vhost,
  139. _media_info._app,
  140. _media_info._streamid);
  141. if(src || !maxWaitMS){
  142. cb(src);
  143. return;
  144. }
  145. void *listener_tag = this;
  146. //若干秒后执行等待媒体注册超时回调
  147. auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){
  148. //取消监听该事件
  149. NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
  150. cb(nullptr);
  151. return 0;
  152. });
  153. weak_ptr<FFmpegSource> weakSelf = shared_from_this();
  154. auto onRegist = [listener_tag,weakSelf,cb,onRegistTimeout](BroadcastMediaChangedArgs) {
  155. auto strongSelf = weakSelf.lock();
  156. if(!strongSelf) {
  157. //本身已经销毁,取消延时任务
  158. onRegistTimeout->cancel();
  159. NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
  160. return;
  161. }
  162. if (!bRegist ||
  163. sender.getSchema() != strongSelf->_media_info._schema ||
  164. sender.getVhost() != strongSelf->_media_info._vhost ||
  165. sender.getApp() != strongSelf->_media_info._app ||
  166. sender.getId() != strongSelf->_media_info._streamid) {
  167. //不是自己感兴趣的事件,忽略之
  168. return;
  169. }
  170. //查找的流终于注册上了;取消延时任务,防止多次回调
  171. onRegistTimeout->cancel();
  172. //取消事件监听
  173. NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
  174. //切换到自己的线程再回复
  175. strongSelf->_poller->async([weakSelf,cb](){
  176. auto strongSelf = weakSelf.lock();
  177. if(!strongSelf) {
  178. return;
  179. }
  180. //再找一遍媒体源,一般能找到
  181. strongSelf->findAsync(0,cb);
  182. }, false);
  183. };
  184. //监听媒体注册事件
  185. NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
  186. }
  187. /**
  188. * 定时检查媒体是否在线
  189. */
  190. void FFmpegSource::startTimer(int timeout_ms) {
  191. weak_ptr<FFmpegSource> weakSelf = shared_from_this();
  192. GET_CONFIG(uint64_t,ffmpeg_restart_sec,FFmpeg::kRestartSec);
  193. _timer = std::make_shared<Timer>(1.0f, [weakSelf, timeout_ms]() {
  194. auto strongSelf = weakSelf.lock();
  195. if (!strongSelf) {
  196. //自身已经销毁
  197. return false;
  198. }
  199. bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000;
  200. if (is_local_ip(strongSelf->_media_info._host)) {
  201. //推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
  202. strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
  203. //同步查找流
  204. if (!src || needRestart) {
  205. if(needRestart){
  206. strongSelf->_replay_ticker.resetTime();
  207. if(strongSelf->_process.wait(false)){
  208. //FFmpeg进程还在运行,超时就关闭它
  209. strongSelf->_process.kill(2000);
  210. }
  211. InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
  212. }
  213. //流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了
  214. if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){
  215. //上次重试时间超过10秒,那么再重试FFmpeg拉流
  216. strongSelf->_replay_ticker.resetTime();
  217. strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
  218. }
  219. }
  220. });
  221. } else {
  222. //推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出
  223. if (!strongSelf->_process.wait(false) || needRestart) {
  224. if(needRestart){
  225. strongSelf->_replay_ticker.resetTime();
  226. if(strongSelf->_process.wait(false)){
  227. //FFmpeg进程还在运行,超时就关闭它
  228. strongSelf->_process.kill(2000);
  229. }
  230. InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
  231. }
  232. //ffmpeg不在线,重新拉流
  233. strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) {
  234. if(!ex){
  235. //没有错误
  236. return;
  237. }
  238. auto strongSelf = weakSelf.lock();
  239. if (!strongSelf) {
  240. //自身已经销毁
  241. return;
  242. }
  243. //上次重试时间超过10秒,那么再重试FFmpeg拉流
  244. strongSelf->startTimer(10 * 1000);
  245. });
  246. }
  247. }
  248. return true;
  249. }, _poller);
  250. }
  251. void FFmpegSource::setOnClose(const function<void()> &cb){
  252. _onClose = cb;
  253. }
  254. bool FFmpegSource::close(MediaSource &sender) {
  255. auto listener = getDelegate();
  256. if (listener && !listener->close(sender)) {
  257. //关闭失败
  258. return false;
  259. }
  260. //该流无人观看,我们停止吧
  261. if (_onClose) {
  262. _onClose();
  263. }
  264. return true;
  265. }
  266. MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{
  267. return MediaOriginType::ffmpeg_pull;
  268. }
  269. string FFmpegSource::getOriginUrl(MediaSource &sender) const{
  270. return _src_url;
  271. }
  272. std::shared_ptr<SockInfo> FFmpegSource::getOriginSock(MediaSource &sender) const {
  273. return nullptr;
  274. }
  275. void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
  276. auto listener = src->getListener(true);
  277. if (listener.lock().get() != this) {
  278. //防止多次进入onGetMediaSource函数导致无限递归调用的bug
  279. setDelegate(listener);
  280. src->setListener(shared_from_this());
  281. if (_enable_hls) {
  282. src->setupRecord(Recorder::type_hls, true, "", 0);
  283. }
  284. if (_enable_mp4) {
  285. src->setupRecord(Recorder::type_mp4, true, "", 0);
  286. }
  287. }
  288. }
  289. void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) {
  290. GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
  291. GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap);
  292. GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
  293. Ticker ticker;
  294. WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){
  295. auto elapsed_ms = ticker.elapsedTime();
  296. if (elapsed_ms > timeout_sec * 1000) {
  297. //超时,后台线程负载太高,当代太久才启动该任务
  298. cb(false, "wait work poller schedule snap task timeout");
  299. return;
  300. }
  301. char cmd[2048] = { 0 };
  302. snprintf(cmd, sizeof(cmd), ffmpeg_snap.data(), File::absolutePath("", ffmpeg_bin).data(), play_url.data(), save_path.data());
  303. std::shared_ptr<Process> process = std::make_shared<Process>();
  304. auto log_file = ffmpeg_log.empty() ? ffmpeg_log : File::absolutePath("", ffmpeg_log);
  305. process->run(cmd, log_file);
  306. //定时器延时应该减去后台任务启动的延时
  307. auto delayTask = EventPollerPool::Instance().getPoller()->doDelayTask(
  308. (uint64_t)(timeout_sec * 1000 - elapsed_ms), [process, cb, log_file, save_path]() {
  309. if (process->wait(false)) {
  310. // FFmpeg进程还在运行,超时就关闭它
  311. process->kill(2000);
  312. }
  313. return 0;
  314. });
  315. //等待FFmpeg进程退出
  316. process->wait(true);
  317. // FFmpeg进程退出了可以取消定时器了
  318. delayTask->cancel();
  319. //执行回调函数
  320. bool success = process->exit_code() == 0 && File::fileSize(save_path.data());
  321. cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : "");
  322. });
  323. }