Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

514 Zeilen
19KB

  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 <string.h>
  11. #include "mk_mediakit.h"
  12. #define LOG_LEV 4
  13. /**
  14. * 注册或反注册MediaSource事件广播
  15. * @param regist 注册为1,注销为0
  16. * @param sender 该MediaSource对象
  17. */
  18. void API_CALL on_mk_media_changed(int regist,
  19. const mk_media_source sender) {
  20. log_printf(LOG_LEV,"%d %s/%s/%s/%s",(int)regist,
  21. mk_media_source_get_schema(sender),
  22. mk_media_source_get_vhost(sender),
  23. mk_media_source_get_app(sender),
  24. mk_media_source_get_stream(sender));
  25. }
  26. /**
  27. * 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
  28. * @see mk_publish_auth_invoker_do
  29. * @param url_info 推流url相关信息
  30. * @param invoker 执行invoker返回鉴权结果
  31. * @param sender 该tcp客户端相关信息
  32. */
  33. void API_CALL on_mk_media_publish(const mk_media_info url_info,
  34. const mk_publish_auth_invoker invoker,
  35. const mk_sock_info sender) {
  36. char ip[64];
  37. log_printf(LOG_LEV,
  38. "client info, local: %s:%d, peer: %s:%d\n"
  39. "%s/%s/%s/%s, url params: %s",
  40. mk_sock_info_local_ip(sender,ip),
  41. mk_sock_info_local_port(sender),
  42. mk_sock_info_peer_ip(sender,ip + 32),
  43. mk_sock_info_peer_port(sender),
  44. mk_media_info_get_schema(url_info),
  45. mk_media_info_get_vhost(url_info),
  46. mk_media_info_get_app(url_info),
  47. mk_media_info_get_stream(url_info),
  48. mk_media_info_get_params(url_info));
  49. //允许推流,并且允许转hls/mp4
  50. mk_publish_auth_invoker_do(invoker, NULL, 1, 1);
  51. }
  52. /**
  53. * 播放rtsp/rtmp/http-flv/hls事件广播,通过该事件控制播放鉴权
  54. * @see mk_auth_invoker_do
  55. * @param url_info 播放url相关信息
  56. * @param invoker 执行invoker返回鉴权结果
  57. * @param sender 播放客户端相关信息
  58. */
  59. void API_CALL on_mk_media_play(const mk_media_info url_info,
  60. const mk_auth_invoker invoker,
  61. const mk_sock_info sender) {
  62. char ip[64];
  63. log_printf(LOG_LEV,
  64. "client info, local: %s:%d, peer: %s:%d\n"
  65. "%s/%s/%s/%s, url params: %s",
  66. mk_sock_info_local_ip(sender,ip),
  67. mk_sock_info_local_port(sender),
  68. mk_sock_info_peer_ip(sender,ip + 32),
  69. mk_sock_info_peer_port(sender),
  70. mk_media_info_get_schema(url_info),
  71. mk_media_info_get_vhost(url_info),
  72. mk_media_info_get_app(url_info),
  73. mk_media_info_get_stream(url_info),
  74. mk_media_info_get_params(url_info));
  75. //允许播放
  76. mk_auth_invoker_do(invoker, NULL);
  77. }
  78. /**
  79. * 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了
  80. * @param url_info 播放url相关信息
  81. * @param sender 播放客户端相关信息
  82. * @return 1 直接关闭
  83. * 0 等待流注册
  84. */
  85. int API_CALL on_mk_media_not_found(const mk_media_info url_info,
  86. const mk_sock_info sender) {
  87. char ip[64];
  88. log_printf(LOG_LEV,
  89. "client info, local: %s:%d, peer: %s:%d\n"
  90. "%s/%s/%s/%s, url params: %s",
  91. mk_sock_info_local_ip(sender,ip),
  92. mk_sock_info_local_port(sender),
  93. mk_sock_info_peer_ip(sender,ip + 32),
  94. mk_sock_info_peer_port(sender),
  95. mk_media_info_get_schema(url_info),
  96. mk_media_info_get_vhost(url_info),
  97. mk_media_info_get_app(url_info),
  98. mk_media_info_get_stream(url_info),
  99. mk_media_info_get_params(url_info));
  100. return 0;
  101. }
  102. /**
  103. * 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑
  104. * @param sender 该MediaSource对象
  105. */
  106. void API_CALL on_mk_media_no_reader(const mk_media_source sender) {
  107. log_printf(LOG_LEV,
  108. "%s/%s/%s/%s",
  109. mk_media_source_get_schema(sender),
  110. mk_media_source_get_vhost(sender),
  111. mk_media_source_get_app(sender),
  112. mk_media_source_get_stream(sender));
  113. }
  114. //按照json转义规则转义webrtc answer sdp
  115. static char *escape_string(const char *ptr){
  116. char *escaped = malloc(2 * strlen(ptr));
  117. char *ptr_escaped = escaped;
  118. while (1) {
  119. switch (*ptr) {
  120. case '\r': {
  121. *(ptr_escaped++) = '\\';
  122. *(ptr_escaped++) = 'r';
  123. break;
  124. }
  125. case '\n': {
  126. *(ptr_escaped++) = '\\';
  127. *(ptr_escaped++) = 'n';
  128. break;
  129. }
  130. case '\t': {
  131. *(ptr_escaped++) = '\\';
  132. *(ptr_escaped++) = 't';
  133. break;
  134. }
  135. default: {
  136. *(ptr_escaped++) = *ptr;
  137. if (!*ptr) {
  138. return escaped;
  139. }
  140. break;
  141. }
  142. }
  143. ++ptr;
  144. }
  145. }
  146. static void on_mk_webrtc_get_answer_sdp_func(void *user_data, const char *answer, const char *err) {
  147. const char *response_header[] = { "Content-Type", "application/json", "Access-Control-Allow-Origin", "*" , NULL};
  148. if (answer) {
  149. answer = escape_string(answer);
  150. }
  151. size_t len = answer ? 2 * strlen(answer) : 1024;
  152. char *response_content = (char *)malloc(len);
  153. if (answer) {
  154. snprintf(response_content, len,
  155. "{"
  156. "\"sdp\":\"%s\","
  157. "\"type\":\"answer\","
  158. "\"code\":0"
  159. "}",
  160. answer);
  161. } else {
  162. snprintf(response_content, len,
  163. "{"
  164. "\"msg\":\"%s\","
  165. "\"code\":-1"
  166. "}",
  167. err);
  168. }
  169. mk_http_response_invoker_do_string(user_data, 200, response_header, response_content);
  170. mk_http_response_invoker_clone_release(user_data);
  171. free(response_content);
  172. if (answer) {
  173. free((void *)answer);
  174. }
  175. }
  176. /**
  177. * 收到http api请求广播(包括GET/POST)
  178. * @param parser http请求内容对象
  179. * @param invoker 执行该invoker返回http回复
  180. * @param consumed 置1则说明我们要处理该事件
  181. * @param sender http客户端相关信息
  182. */
  183. //测试url : http://127.0.0.1/api/test
  184. void API_CALL on_mk_http_request(const mk_parser parser,
  185. const mk_http_response_invoker invoker,
  186. int *consumed,
  187. const mk_sock_info sender) {
  188. char ip[64];
  189. log_printf(LOG_LEV,
  190. "client info, local: %s:%d, peer: %s:%d\n"
  191. "%s %s?%s %s\n"
  192. "User-Agent: %s\n"
  193. "%s",
  194. mk_sock_info_local_ip(sender,ip),
  195. mk_sock_info_local_port(sender),
  196. mk_sock_info_peer_ip(sender,ip + 32),
  197. mk_sock_info_peer_port(sender),
  198. mk_parser_get_method(parser),
  199. mk_parser_get_url(parser),
  200. mk_parser_get_url_params(parser),
  201. mk_parser_get_tail(parser),
  202. mk_parser_get_header(parser, "User-Agent"),
  203. mk_parser_get_content(parser,NULL));
  204. const char *url = mk_parser_get_url(parser);
  205. *consumed = 1;
  206. //拦截api: /api/test
  207. if (strcmp(url, "/api/test") == 0) {
  208. const char *response_header[] = { "Content-Type", "text/html", NULL };
  209. const char *content = "<html>"
  210. "<head>"
  211. "<title>hello world</title>"
  212. "</head>"
  213. "<body bgcolor=\"white\">"
  214. "<center><h1>hello world</h1></center><hr>"
  215. "<center>"
  216. "ZLMediaKit-4.0</center>"
  217. "</body>"
  218. "</html>";
  219. mk_http_body body = mk_http_body_from_string(content, 0);
  220. mk_http_response_invoker_do(invoker, 200, response_header, body);
  221. mk_http_body_release(body);
  222. } else if (strcmp(url, "/index/api/webrtc") == 0) {
  223. //拦截api: /index/api/webrtc
  224. char rtc_url[1024];
  225. snprintf(rtc_url, sizeof(rtc_url), "rtc://%s/%s/%s?%s", mk_parser_get_header(parser, "Host"),
  226. mk_parser_get_url_param(parser, "app"), mk_parser_get_url_param(parser, "stream"),
  227. mk_parser_get_url_params(parser));
  228. mk_webrtc_get_answer_sdp(mk_http_response_invoker_clone(invoker), on_mk_webrtc_get_answer_sdp_func,
  229. mk_parser_get_url_param(parser, "type"), mk_parser_get_content(parser, NULL), rtc_url);
  230. } else {
  231. *consumed = 0;
  232. return;
  233. }
  234. }
  235. /**
  236. * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
  237. * @param parser http请求内容对象
  238. * @param path 文件绝对路径
  239. * @param is_dir path是否为文件夹
  240. * @param invoker 执行invoker返回本次访问文件的结果
  241. * @param sender http客户端相关信息
  242. */
  243. void API_CALL on_mk_http_access(const mk_parser parser,
  244. const char *path,
  245. int is_dir,
  246. const mk_http_access_path_invoker invoker,
  247. const mk_sock_info sender) {
  248. char ip[64];
  249. log_printf(LOG_LEV,
  250. "client info, local: %s:%d, peer: %s:%d, path: %s ,is_dir: %d\n"
  251. "%s %s?%s %s\n"
  252. "User-Agent: %s\n"
  253. "%s",
  254. mk_sock_info_local_ip(sender, ip),
  255. mk_sock_info_local_port(sender),
  256. mk_sock_info_peer_ip(sender, ip + 32),
  257. mk_sock_info_peer_port(sender),
  258. path,(int)is_dir,
  259. mk_parser_get_method(parser),
  260. mk_parser_get_url(parser),
  261. mk_parser_get_url_params(parser),
  262. mk_parser_get_tail(parser),
  263. mk_parser_get_header(parser,"User-Agent"),
  264. mk_parser_get_content(parser,NULL));
  265. //有访问权限,每次访问文件都需要鉴权
  266. mk_http_access_path_invoker_do(invoker, NULL, NULL, 0);
  267. }
  268. /**
  269. * 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
  270. * 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的
  271. * @param parser http请求内容对象
  272. * @param path 文件绝对路径,覆盖之可以重定向到其他文件
  273. * @param sender http客户端相关信息
  274. */
  275. void API_CALL on_mk_http_before_access(const mk_parser parser,
  276. char *path,
  277. const mk_sock_info sender) {
  278. char ip[64];
  279. log_printf(LOG_LEV,
  280. "client info, local: %s:%d, peer: %s:%d, path: %s\n"
  281. "%s %s?%s %s\n"
  282. "User-Agent: %s\n"
  283. "%s",
  284. mk_sock_info_local_ip(sender,ip),
  285. mk_sock_info_local_port(sender),
  286. mk_sock_info_peer_ip(sender,ip + 32),
  287. mk_sock_info_peer_port(sender),
  288. path,
  289. mk_parser_get_method(parser),
  290. mk_parser_get_url(parser),
  291. mk_parser_get_url_params(parser),
  292. mk_parser_get_tail(parser),
  293. mk_parser_get_header(parser, "User-Agent"),
  294. mk_parser_get_content(parser,NULL));
  295. //覆盖path的值可以重定向文件
  296. }
  297. /**
  298. * 该rtsp流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm
  299. * @param url_info 请求rtsp url相关信息
  300. * @param invoker 执行invoker返回是否需要rtsp专属认证
  301. * @param sender rtsp客户端相关信息
  302. */
  303. void API_CALL on_mk_rtsp_get_realm(const mk_media_info url_info,
  304. const mk_rtsp_get_realm_invoker invoker,
  305. const mk_sock_info sender) {
  306. char ip[64];
  307. log_printf(LOG_LEV,
  308. "client info, local: %s:%d, peer: %s:%d\n"
  309. "%s/%s/%s/%s, url params: %s",
  310. mk_sock_info_local_ip(sender,ip),
  311. mk_sock_info_local_port(sender),
  312. mk_sock_info_peer_ip(sender,ip + 32),
  313. mk_sock_info_peer_port(sender),
  314. mk_media_info_get_schema(url_info),
  315. mk_media_info_get_vhost(url_info),
  316. mk_media_info_get_app(url_info),
  317. mk_media_info_get_stream(url_info),
  318. mk_media_info_get_params(url_info));
  319. //rtsp播放默认鉴权
  320. mk_rtsp_get_realm_invoker_do(invoker, "zlmediakit");
  321. }
  322. /**
  323. * 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为1,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
  324. * 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码
  325. * @param url_info 请求rtsp url相关信息
  326. * @param realm rtsp认证realm
  327. * @param user_name rtsp认证用户名
  328. * @param must_no_encrypt 如果为1,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
  329. * @param invoker 执行invoker返回rtsp专属认证的密码
  330. * @param sender rtsp客户端信息
  331. */
  332. void API_CALL on_mk_rtsp_auth(const mk_media_info url_info,
  333. const char *realm,
  334. const char *user_name,
  335. int must_no_encrypt,
  336. const mk_rtsp_auth_invoker invoker,
  337. const mk_sock_info sender) {
  338. char ip[64];
  339. log_printf(LOG_LEV,
  340. "client info, local: %s:%d, peer: %s:%d\n"
  341. "%s/%s/%s/%s, url params: %s\n"
  342. "realm: %s, user_name: %s, must_no_encrypt: %d",
  343. mk_sock_info_local_ip(sender,ip),
  344. mk_sock_info_local_port(sender),
  345. mk_sock_info_peer_ip(sender,ip + 32),
  346. mk_sock_info_peer_port(sender),
  347. mk_media_info_get_schema(url_info),
  348. mk_media_info_get_vhost(url_info),
  349. mk_media_info_get_app(url_info),
  350. mk_media_info_get_stream(url_info),
  351. mk_media_info_get_params(url_info),
  352. realm,user_name,(int)must_no_encrypt);
  353. //rtsp播放用户名跟密码一致
  354. mk_rtsp_auth_invoker_do(invoker,0,user_name);
  355. }
  356. /**
  357. * 录制mp4分片文件成功后广播
  358. */
  359. void API_CALL on_mk_record_mp4(const mk_mp4_info mp4) {
  360. log_printf(LOG_LEV,
  361. "\nstart_time: %d\n"
  362. "time_len: %d\n"
  363. "file_size: %d\n"
  364. "file_path: %s\n"
  365. "file_name: %s\n"
  366. "folder: %s\n"
  367. "url: %s\n"
  368. "vhost: %s\n"
  369. "app: %s\n"
  370. "stream: %s\n",
  371. mk_mp4_info_get_start_time(mp4),
  372. mk_mp4_info_get_time_len(mp4),
  373. mk_mp4_info_get_file_size(mp4),
  374. mk_mp4_info_get_file_path(mp4),
  375. mk_mp4_info_get_file_name(mp4),
  376. mk_mp4_info_get_folder(mp4),
  377. mk_mp4_info_get_url(mp4),
  378. mk_mp4_info_get_vhost(mp4),
  379. mk_mp4_info_get_app(mp4),
  380. mk_mp4_info_get_stream(mp4));
  381. }
  382. /**
  383. * shell登录鉴权
  384. */
  385. void API_CALL on_mk_shell_login(const char *user_name,
  386. const char *passwd,
  387. const mk_auth_invoker invoker,
  388. const mk_sock_info sender) {
  389. char ip[64];
  390. log_printf(LOG_LEV,"client info, local: %s:%d, peer: %s:%d\n"
  391. "user_name: %s, passwd: %s",
  392. mk_sock_info_local_ip(sender,ip),
  393. mk_sock_info_local_port(sender),
  394. mk_sock_info_peer_ip(sender,ip + 32),
  395. mk_sock_info_peer_port(sender),
  396. user_name, passwd);
  397. //允许登录shell
  398. mk_auth_invoker_do(invoker, NULL);
  399. }
  400. /**
  401. * 停止rtsp/rtmp/http-flv会话后流量汇报事件广播
  402. * @param url_info 播放url相关信息
  403. * @param total_bytes 耗费上下行总流量,单位字节数
  404. * @param total_seconds 本次tcp会话时长,单位秒
  405. * @param is_player 客户端是否为播放器
  406. * @param peer_ip 客户端ip
  407. * @param peer_port 客户端端口号
  408. */
  409. void API_CALL on_mk_flow_report(const mk_media_info url_info,
  410. size_t total_bytes,
  411. size_t total_seconds,
  412. int is_player,
  413. const mk_sock_info sender) {
  414. char ip[64];
  415. log_printf(LOG_LEV,"%s/%s/%s/%s, url params: %s,"
  416. "total_bytes: %d, total_seconds: %d, is_player: %d, peer_ip:%s, peer_port:%d",
  417. mk_media_info_get_schema(url_info),
  418. mk_media_info_get_vhost(url_info),
  419. mk_media_info_get_app(url_info),
  420. mk_media_info_get_stream(url_info),
  421. mk_media_info_get_params(url_info),
  422. (int)total_bytes,
  423. (int)total_seconds,
  424. (int)is_player,
  425. mk_sock_info_peer_ip(sender,ip),
  426. (int)mk_sock_info_peer_port(sender));
  427. }
  428. int main(int argc, char *argv[]) {
  429. char *ini_path = mk_util_get_exe_dir("c_api.ini");
  430. char *ssl_path = mk_util_get_exe_dir("ssl.p12");
  431. mk_config config = {
  432. .ini = ini_path,
  433. .ini_is_path = 1,
  434. .log_level = 0,
  435. .log_mask = LOG_CONSOLE,
  436. .log_file_path = NULL,
  437. .log_file_days = 0,
  438. .ssl = ssl_path,
  439. .ssl_is_path = 1,
  440. .ssl_pwd = NULL,
  441. .thread_num = 0
  442. };
  443. mk_env_init(&config);
  444. free(ini_path);
  445. free(ssl_path);
  446. mk_http_server_start(80, 0);
  447. mk_http_server_start(443, 1);
  448. mk_rtsp_server_start(554, 0);
  449. mk_rtmp_server_start(1935, 0);
  450. mk_shell_server_start(9000);
  451. mk_rtp_server_start(10000);
  452. mk_rtc_server_start(8000);
  453. mk_srt_server_start(9000);
  454. mk_events events = {
  455. .on_mk_media_changed = on_mk_media_changed,
  456. .on_mk_media_publish = on_mk_media_publish,
  457. .on_mk_media_play = on_mk_media_play,
  458. .on_mk_media_not_found = on_mk_media_not_found,
  459. .on_mk_media_no_reader = on_mk_media_no_reader,
  460. .on_mk_http_request = on_mk_http_request,
  461. .on_mk_http_access = on_mk_http_access,
  462. .on_mk_http_before_access = on_mk_http_before_access,
  463. .on_mk_rtsp_get_realm = on_mk_rtsp_get_realm,
  464. .on_mk_rtsp_auth = on_mk_rtsp_auth,
  465. .on_mk_record_mp4 = on_mk_record_mp4,
  466. .on_mk_shell_login = on_mk_shell_login,
  467. .on_mk_flow_report = on_mk_flow_report
  468. };
  469. mk_events_listen(&events);
  470. log_info("media server %s", "stared!");
  471. log_info("enter any key to exit");
  472. getchar();
  473. mk_stop_all_server();
  474. return 0;
  475. }