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.

335 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 <map>
  11. #include <signal.h>
  12. #include <iostream>
  13. #include "Util/MD5.h"
  14. #include "Util/logger.h"
  15. #include "Util/SSLBox.h"
  16. #include "Util/onceToken.h"
  17. #include "Network/TcpServer.h"
  18. #include "Poller/EventPoller.h"
  19. #include "Common/config.h"
  20. #include "Rtsp/UDPServer.h"
  21. #include "Rtsp/RtspSession.h"
  22. #include "Rtmp/RtmpSession.h"
  23. #include "Shell/ShellSession.h"
  24. #include "Rtmp/FlvMuxer.h"
  25. #include "Player/PlayerProxy.h"
  26. #include "Http/WebSocketSession.h"
  27. using namespace std;
  28. using namespace toolkit;
  29. using namespace mediakit;
  30. namespace mediakit {
  31. ////////////HTTP配置///////////
  32. namespace Http {
  33. #define HTTP_FIELD "http."
  34. #define HTTP_PORT 80
  35. const string kPort = HTTP_FIELD"port";
  36. #define HTTPS_PORT 443
  37. const string kSSLPort = HTTP_FIELD"sslport";
  38. onceToken token1([](){
  39. mINI::Instance()[kPort] = HTTP_PORT;
  40. mINI::Instance()[kSSLPort] = HTTPS_PORT;
  41. },nullptr);
  42. }//namespace Http
  43. ////////////SHELL配置///////////
  44. namespace Shell {
  45. #define SHELL_FIELD "shell."
  46. #define SHELL_PORT 9000
  47. const string kPort = SHELL_FIELD"port";
  48. onceToken token1([](){
  49. mINI::Instance()[kPort] = SHELL_PORT;
  50. },nullptr);
  51. } //namespace Shell
  52. ////////////RTSP服务器配置///////////
  53. namespace Rtsp {
  54. #define RTSP_FIELD "rtsp."
  55. #define RTSP_PORT 554
  56. #define RTSPS_PORT 322
  57. const string kPort = RTSP_FIELD"port";
  58. const string kSSLPort = RTSP_FIELD"sslport";
  59. onceToken token1([](){
  60. mINI::Instance()[kPort] = RTSP_PORT;
  61. mINI::Instance()[kSSLPort] = RTSPS_PORT;
  62. },nullptr);
  63. } //namespace Rtsp
  64. ////////////RTMP服务器配置///////////
  65. namespace Rtmp {
  66. #define RTMP_FIELD "rtmp."
  67. #define RTMP_PORT 1935
  68. const string kPort = RTMP_FIELD"port";
  69. onceToken token1([](){
  70. mINI::Instance()[kPort] = RTMP_PORT;
  71. },nullptr);
  72. } //namespace RTMP
  73. } // namespace mediakit
  74. #define REALM "realm_zlmediakit"
  75. static map<string,FlvRecorder::Ptr> s_mapFlvRecorder;
  76. static mutex s_mtxFlvRecorder;
  77. void initEventListener() {
  78. static onceToken s_token([]() {
  79. //监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
  80. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
  81. DebugL << "RTSP是否需要鉴权事件:" << args.getUrl() << " " << args._param_strs;
  82. if (string("1") == args._streamid) {
  83. // live/1需要认证
  84. //该流需要认证,并且设置realm
  85. invoker(REALM);
  86. } else {
  87. //有时我们要查询redis或数据库来判断该流是否需要认证,通过invoker的方式可以做到完全异步
  88. //该流我们不需要认证
  89. invoker("");
  90. }
  91. });
  92. //监听kBroadcastOnRtspAuth事件返回正确的rtsp鉴权用户密码
  93. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnRtspAuth, [](BroadcastOnRtspAuthArgs) {
  94. DebugL << "RTSP播放鉴权:" << args.getUrl() << " " << args._param_strs;
  95. DebugL << "RTSP用户:" << user_name << (must_no_encrypt ? " Base64" : " MD5") << " 方式登录";
  96. string user = user_name;
  97. //假设我们异步读取数据库
  98. if (user == "test0") {
  99. //假设数据库保存的是明文
  100. invoker(false, "pwd0");
  101. return;
  102. }
  103. if (user == "test1") {
  104. //假设数据库保存的是密文
  105. auto encrypted_pwd = MD5(user + ":" + REALM + ":" + "pwd1").hexdigest();
  106. invoker(true, encrypted_pwd);
  107. return;
  108. }
  109. if (user == "test2" && must_no_encrypt) {
  110. //假设登录的是test2,并且以base64方式登录,此时我们提供加密密码,那么会导致认证失败
  111. //可以通过这个方式屏蔽base64这种不安全的加密方式
  112. invoker(true, "pwd2");
  113. return;
  114. }
  115. //其他用户密码跟用户名一致
  116. invoker(false, user);
  117. });
  118. //监听rtsp/rtmp推流事件,返回结果告知是否有推流权限
  119. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
  120. DebugL << "推流鉴权:" << args.getUrl() << " " << args._param_strs;
  121. invoker("", ProtocolOption());//鉴权成功
  122. //invoker("this is auth failed message");//鉴权失败
  123. });
  124. //监听rtsp/rtsps/rtmp/http-flv播放事件,返回结果告知是否有播放权限(rtsp通过kBroadcastOnRtspAuth或此事件都可以实现鉴权)
  125. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
  126. DebugL << "播放鉴权:" << args.getUrl() << " " << args._param_strs;
  127. invoker("");//鉴权成功
  128. //invoker("this is auth failed message");//鉴权失败
  129. });
  130. //shell登录事件,通过shell可以登录进服务器执行一些命令
  131. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) {
  132. DebugL << "shell login:" << user_name << " " << passwd;
  133. invoker("");//鉴权成功
  134. //invoker("this is auth failed message");//鉴权失败
  135. });
  136. //监听rtsp、rtmp源注册或注销事件;此处用于测试rtmp保存为flv录像,保存在http根目录下
  137. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
  138. if (sender.getSchema() == RTMP_SCHEMA && sender.getApp() == "live") {
  139. lock_guard<mutex> lck(s_mtxFlvRecorder);
  140. auto key = sender.shortUrl();
  141. if (bRegist) {
  142. DebugL << "开始录制RTMP:" << sender.getUrl();
  143. GET_CONFIG(string, http_root, Http::kRootPath);
  144. auto path = http_root + "/" + key + "_" + to_string(time(NULL)) + ".flv";
  145. FlvRecorder::Ptr recorder(new FlvRecorder);
  146. try {
  147. recorder->startRecord(EventPollerPool::Instance().getPoller(),
  148. dynamic_pointer_cast<RtmpMediaSource>(sender.shared_from_this()), path);
  149. s_mapFlvRecorder[key] = recorder;
  150. } catch (std::exception &ex) {
  151. WarnL << ex.what();
  152. }
  153. } else {
  154. s_mapFlvRecorder.erase(key);
  155. }
  156. }
  157. });
  158. //监听播放失败(未找到特定的流)事件
  159. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastNotFoundStream, [](BroadcastNotFoundStreamArgs) {
  160. /**
  161. * 你可以在这个事件触发时再去拉流,这样就可以实现按需拉流
  162. * 拉流成功后,ZLMediaKit会把其立即转发给播放器(最大等待时间约为5秒,如果5秒都未拉流成功,播放器会播放失败)
  163. */
  164. DebugL << "未找到流事件:" << args.getUrl() << " " << args._param_strs;
  165. });
  166. //监听播放或推流结束时消耗流量事件
  167. NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
  168. DebugL << "播放器(推流器)断开连接事件:" << args.getUrl() << " " << args._param_strs
  169. << "\r\n使用流量:" << totalBytes << " bytes,连接时长:" << totalDuration << "秒";
  170. });
  171. }, nullptr);
  172. }
  173. #if !defined(SIGHUP)
  174. #define SIGHUP 1
  175. #endif
  176. int main(int argc,char *argv[]) {
  177. //设置日志
  178. Logger::Instance().add(std::make_shared<ConsoleChannel>());
  179. Logger::Instance().add(std::make_shared<FileChannel>());
  180. Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
  181. //加载配置文件,如果配置文件不存在就创建一个
  182. loadIniConfig();
  183. initEventListener();
  184. //这里是拉流地址,支持rtmp/rtsp协议,负载必须是H264+AAC
  185. //如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频)
  186. auto urlList = {"rtsp://admin:admin123@192.168.1.64:554/cam/realmonitor?channel=1&subtype=1"
  187. //rtsp链接支持输入用户名密码
  188. /*"rtsp://admin:jzan123456@192.168.0.122/"*/};
  189. map<string, PlayerProxy::Ptr> proxyMap;
  190. int i = 0;
  191. for (auto &url : urlList) {
  192. //PlayerProxy构造函数前两个参数分别为应用名(app),流id(streamId)
  193. //比如说应用为live,流id为0,那么直播地址为:
  194. //hls地址 : http://127.0.0.1/live/0/hls.m3u8
  195. //http-flv地址 : http://127.0.0.1/live/0.flv
  196. //rtsp地址 : rtsp://127.0.0.1/live/0
  197. //rtmp地址 : rtmp://127.0.0.1/live/0
  198. //录像地址为(当然vlc不支持这么多级的rtmp url,可以用test_player测试rtmp点播):
  199. //http://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
  200. //rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
  201. //rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
  202. PlayerProxy::Ptr player(new PlayerProxy(DEFAULT_VHOST, "live", std::string("chn") + to_string(i).data(), ProtocolOption()));
  203. //指定RTP over TCP(播放rtsp时有效)
  204. (*player)[Client::kRtpType] = Rtsp::RTP_TCP;
  205. //开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试
  206. player->play(url);
  207. //需要保存PlayerProxy,否则作用域结束就会销毁该对象
  208. proxyMap.emplace(to_string(i), player);
  209. ++i;
  210. }
  211. DebugL << "\r\n"
  212. " PlayerProxy构造函数前两个参数分别为应用名(app),流id(streamId)\n"
  213. " 比如说应用为live,流id为0,那么直播地址为:\n"
  214. " hls地址 : http://127.0.0.1/live/0/hls.m3u8\n"
  215. " http-flv地址 : http://127.0.0.1/live/0.flv\n"
  216. " rtsp地址 : rtsp://127.0.0.1/live/0\n"
  217. " rtmp地址 : rtmp://127.0.0.1/live/0";
  218. //加载证书,证书包含公钥和私钥
  219. SSL_Initor::Instance().loadCertificate((exeDir() + "ssl.p12").data());
  220. //信任某个自签名证书
  221. SSL_Initor::Instance().trustCertificate((exeDir() + "ssl.p12").data());
  222. //不忽略无效证书证书(例如自签名或过期证书)
  223. SSL_Initor::Instance().ignoreInvalidCertificate(false);
  224. uint16_t shellPort = mINI::Instance()[Shell::kPort];
  225. uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
  226. uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
  227. uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
  228. uint16_t httpPort = mINI::Instance()[Http::kPort];
  229. uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
  230. //简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
  231. //测试方法:telnet 127.0.0.1 9000
  232. TcpServer::Ptr shellSrv(new TcpServer());
  233. TcpServer::Ptr rtspSrv(new TcpServer());
  234. TcpServer::Ptr rtmpSrv(new TcpServer());
  235. TcpServer::Ptr httpSrv(new TcpServer());
  236. shellSrv->start<ShellSession>(shellPort);
  237. rtspSrv->start<RtspSession>(rtspPort);//默认554
  238. rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
  239. //http服务器
  240. httpSrv->start<HttpSession>(httpPort);//默认80
  241. //如果支持ssl,还可以开启https服务器
  242. TcpServer::Ptr httpsSrv(new TcpServer());
  243. //https服务器
  244. httpsSrv->start<HttpsSession>(httpsPort);//默认443
  245. //支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
  246. TcpServer::Ptr rtspSSLSrv(new TcpServer());
  247. rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
  248. //服务器支持动态切换端口(不影响现有连接)
  249. NoticeCenter::Instance().addListener(ReloadConfigTag,Broadcast::kBroadcastReloadConfig,[&](BroadcastReloadConfigArgs){
  250. //重新创建服务器
  251. if(shellPort != mINI::Instance()[Shell::kPort].as<uint16_t>()){
  252. shellPort = mINI::Instance()[Shell::kPort];
  253. shellSrv->start<ShellSession>(shellPort);
  254. InfoL << "重启shell服务器:" << shellPort;
  255. }
  256. if(rtspPort != mINI::Instance()[Rtsp::kPort].as<uint16_t>()){
  257. rtspPort = mINI::Instance()[Rtsp::kPort];
  258. rtspSrv->start<RtspSession>(rtspPort);
  259. InfoL << "重启rtsp服务器" << rtspPort;
  260. }
  261. if(rtmpPort != mINI::Instance()[Rtmp::kPort].as<uint16_t>()){
  262. rtmpPort = mINI::Instance()[Rtmp::kPort];
  263. rtmpSrv->start<RtmpSession>(rtmpPort);
  264. InfoL << "重启rtmp服务器" << rtmpPort;
  265. }
  266. if(httpPort != mINI::Instance()[Http::kPort].as<uint16_t>()){
  267. httpPort = mINI::Instance()[Http::kPort];
  268. httpSrv->start<HttpSession>(httpPort);
  269. InfoL << "重启http服务器" << httpPort;
  270. }
  271. if(httpsPort != mINI::Instance()[Http::kSSLPort].as<uint16_t>()){
  272. httpsPort = mINI::Instance()[Http::kSSLPort];
  273. httpsSrv->start<HttpsSession>(httpsPort);
  274. InfoL << "重启https服务器" << httpsPort;
  275. }
  276. if(rtspsPort != mINI::Instance()[Rtsp::kSSLPort].as<uint16_t>()){
  277. rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
  278. rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);
  279. InfoL << "重启rtsps服务器" << rtspsPort;
  280. }
  281. });
  282. //设置退出信号处理函数
  283. static semaphore sem;
  284. signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
  285. signal(SIGHUP, [](int) { loadIniConfig(); });
  286. sem.wait();
  287. lock_guard<mutex> lck(s_mtxFlvRecorder);
  288. s_mapFlvRecorder.clear();
  289. return 0;
  290. }