mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-30 07:26:53 +08:00
commit
87d4a8ed78
25
.github/workflows/ccpp.yml
vendored
Normal file
25
.github/workflows/ccpp.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: C/C++ CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: 下载submodule源码
|
||||
run: git submodule update --init
|
||||
|
||||
- name: apt-get安装依赖库(非必选)
|
||||
run: sudo apt-get update && sudo apt-get install -y cmake libssl-dev libmp4v2-dev libsdl-dev libavcodec-dev libavutil-dev
|
||||
|
||||
- name: 编译
|
||||
run: mkdir -p linux_build && cd linux_build && cmake .. && make -j4
|
||||
|
||||
- name: 运行MediaServer
|
||||
run: pwd && cd release/linux/Debug && sudo ./MediaServer -d &
|
||||
|
||||
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -32,6 +32,15 @@
|
||||
*.DS_Store
|
||||
|
||||
/cmake-build-debug/
|
||||
/cmake-build-release/
|
||||
/linux/
|
||||
/.vs/
|
||||
/.idea/
|
||||
/c_wrapper/.idea/
|
||||
/release/mac/Debug/
|
||||
/release/
|
||||
/Android/.idea/
|
||||
/Android/app/src/main/cpp/libs_export/
|
||||
/3rdpart/media-server/.idea/
|
||||
/3rdpart/media-server/.idea/
|
||||
/build/
|
||||
/3rdpart/media-server/.idea/
|
||||
|
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,6 +1,6 @@
|
||||
[submodule "ZLToolKit"]
|
||||
path = 3rdpart/ZLToolKit
|
||||
url = https://github.com/xiongziliang/ZLToolKit.git
|
||||
url = https://gitee.com/xiahcu/ZLToolKit
|
||||
[submodule "3rdpart/media-server"]
|
||||
path = 3rdpart/media-server
|
||||
url = https://github.com/xiongziliang/media-server
|
||||
url = https://gitee.com/xiahcu/media-server
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 8d1681b5bb247e7f47ae0f8c414f6eeb376b742b
|
||||
Subproject commit 34b42499ce9ee055b5c7cac9a270239984d0e839
|
@ -1 +1 @@
|
||||
Subproject commit 40edf6243d9d99676062062efdec203b24a178aa
|
||||
Subproject commit 8d40dad3dbdce171756691d4511aca49fcf2a231
|
170
CMakeLists.txt
170
CMakeLists.txt
@ -1,4 +1,4 @@
|
||||
project(ZLMediaKit)
|
||||
project(ZLMediaKit)
|
||||
cmake_minimum_required(VERSION 3.1.3)
|
||||
#使能c++11
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
@ -20,6 +20,7 @@ set(RELEASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/release)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType})
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType})
|
||||
add_compile_options(-fPIC)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/windows/${BuildType})
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/windows/${BuildType})
|
||||
@ -30,27 +31,15 @@ endif ()
|
||||
|
||||
LINK_DIRECTORIES(${LIBRARY_OUTPUT_PATH})
|
||||
|
||||
|
||||
#设置工程源码根目录
|
||||
set(ToolKit_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ZLToolKit/src)
|
||||
set(MediaKit_Root ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
set(MediaServer_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/media-server)
|
||||
|
||||
#设置头文件目录
|
||||
INCLUDE_DIRECTORIES(${ToolKit_Root})
|
||||
INCLUDE_DIRECTORIES(${MediaKit_Root})
|
||||
|
||||
#收集源代码
|
||||
file(GLOB ToolKit_src_list ${ToolKit_Root}/*/*.cpp ${ToolKit_Root}/*/*.h ${ToolKit_Root}/*/*.c)
|
||||
file(GLOB MediaKit_src_list ${MediaKit_Root}/*/*.cpp ${MediaKit_Root}/*/*.h ${MediaKit_Root}/*/*.c)
|
||||
|
||||
#去除win32的适配代码
|
||||
if (NOT WIN32)
|
||||
list(REMOVE_ITEM ToolKit_src_list ${ToolKit_Root}/win32/getopt.c)
|
||||
else()
|
||||
#防止Windows.h包含Winsock.h
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN -DMP4V2_NO_STDINT_DEFS)
|
||||
endif ()
|
||||
|
||||
set(ENABLE_HLS true)
|
||||
set(ENABLE_OPENSSL true)
|
||||
set(ENABLE_MYSQL false)
|
||||
@ -58,23 +47,10 @@ set(ENABLE_MP4V2 true)
|
||||
set(ENABLE_FAAC false)
|
||||
set(ENABLE_X264 false)
|
||||
set(ENABLE_MP4RECORD true)
|
||||
set(ENABLE_RTPPROXY true)
|
||||
|
||||
#添加两个静态库
|
||||
if(ENABLE_HLS)
|
||||
message(STATUS "ENABLE_HLS defined")
|
||||
add_definitions(-DENABLE_HLS)
|
||||
set(MediaServer_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/media-server)
|
||||
set(LINK_LIB_LIST zlmediakit zltoolkit mpeg)
|
||||
else()
|
||||
set(LINK_LIB_LIST zlmediakit zltoolkit)
|
||||
endif()
|
||||
|
||||
if(ENABLE_MP4RECORD)
|
||||
message(STATUS "ENABLE_MP4RECORD defined")
|
||||
add_definitions(-DENABLE_MP4RECORD)
|
||||
set(MediaServer_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/media-server)
|
||||
list(APPEND LINK_LIB_LIST mov flv)
|
||||
endif()
|
||||
#查找openssl是否安装
|
||||
find_package(OpenSSL QUIET)
|
||||
if (OPENSSL_FOUND AND ENABLE_OPENSSL)
|
||||
@ -121,38 +97,6 @@ if (FAAC_FOUND AND ENABLE_FAAC)
|
||||
list(APPEND LINK_LIB_LIST ${FAAC_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
|
||||
#添加库
|
||||
add_library(zltoolkit STATIC ${ToolKit_src_list})
|
||||
add_library(zlmediakit STATIC ${MediaKit_src_list})
|
||||
|
||||
|
||||
set(VS_FALGS "/wd4819 /wd4996 /wd4018 /wd4267 /wd4244 /wd4101 /wd4828 /wd4309 /wd4573" )
|
||||
#libmpeg
|
||||
if(ENABLE_HLS)
|
||||
aux_source_directory(${MediaServer_Root}/libmpeg/include src_mpeg)
|
||||
aux_source_directory(${MediaServer_Root}/libmpeg/source src_mpeg)
|
||||
include_directories(${MediaServer_Root}/libmpeg/include)
|
||||
add_library(mpeg STATIC ${src_mpeg})
|
||||
if(WIN32)
|
||||
set_target_properties(mpeg PROPERTIES COMPILE_FLAGS ${VS_FALGS} )
|
||||
endif(WIN32)
|
||||
endif()
|
||||
|
||||
if(ENABLE_MP4RECORD)
|
||||
aux_source_directory(${MediaServer_Root}/libmov/include src_mov)
|
||||
aux_source_directory(${MediaServer_Root}/libmov/source src_mov)
|
||||
include_directories(${MediaServer_Root}/libmov/include)
|
||||
aux_source_directory(${MediaServer_Root}/libflv/include src_flv)
|
||||
aux_source_directory(${MediaServer_Root}/libflv/source src_flv)
|
||||
include_directories(${MediaServer_Root}/libflv/include)
|
||||
add_library(mov STATIC ${src_mov})
|
||||
add_library(flv STATIC ${src_flv})
|
||||
if(WIN32)
|
||||
set_target_properties(mov flv PROPERTIES COMPILE_FLAGS ${VS_FALGS} )
|
||||
endif(WIN32)
|
||||
endif()
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
|
||||
#查找jemalloc是否安装
|
||||
find_package(JEMALLOC QUIET)
|
||||
@ -163,6 +107,79 @@ if(${CMAKE_BUILD_TYPE} MATCHES "Release")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(VS_FALGS "/wd4819 /wd4996 /wd4018 /wd4267 /wd4244 /wd4101 /wd4828 /wd4309 /wd4573" )
|
||||
|
||||
#添加mpeg用于支持ts生成
|
||||
if(ENABLE_HLS)
|
||||
message(STATUS "ENABLE_HLS defined")
|
||||
add_definitions(-DENABLE_HLS)
|
||||
|
||||
aux_source_directory(${MediaServer_Root}/libmpeg/include src_mpeg)
|
||||
aux_source_directory(${MediaServer_Root}/libmpeg/source src_mpeg)
|
||||
include_directories(${MediaServer_Root}/libmpeg/include)
|
||||
|
||||
add_library(mpeg STATIC ${src_mpeg})
|
||||
list(APPEND LINK_LIB_LIST mpeg)
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(mpeg PROPERTIES COMPILE_FLAGS ${VS_FALGS} )
|
||||
endif(WIN32)
|
||||
endif()
|
||||
|
||||
#添加mov、flv库用于MP4录制
|
||||
if(ENABLE_MP4RECORD)
|
||||
message(STATUS "ENABLE_MP4RECORD defined")
|
||||
add_definitions(-DENABLE_MP4RECORD)
|
||||
|
||||
aux_source_directory(${MediaServer_Root}/libmov/include src_mov)
|
||||
aux_source_directory(${MediaServer_Root}/libmov/source src_mov)
|
||||
include_directories(${MediaServer_Root}/libmov/include)
|
||||
|
||||
aux_source_directory(${MediaServer_Root}/libflv/include src_flv)
|
||||
aux_source_directory(${MediaServer_Root}/libflv/source src_flv)
|
||||
include_directories(${MediaServer_Root}/libflv/include)
|
||||
|
||||
add_library(mov STATIC ${src_mov})
|
||||
add_library(flv STATIC ${src_flv})
|
||||
list(APPEND LINK_LIB_LIST mov flv)
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(mov flv PROPERTIES COMPILE_FLAGS ${VS_FALGS} )
|
||||
endif(WIN32)
|
||||
endif()
|
||||
|
||||
#添加rtp库用于rtp转ps/ts
|
||||
if(ENABLE_RTPPROXY AND ENABLE_HLS)
|
||||
message(STATUS "ENABLE_RTPPROXY defined")
|
||||
aux_source_directory(${MediaServer_Root}/librtp/include src_rtp)
|
||||
aux_source_directory(${MediaServer_Root}/librtp/source src_rtp)
|
||||
aux_source_directory(${MediaServer_Root}/librtp/payload src_rtp)
|
||||
include_directories(${MediaServer_Root}/librtp/include)
|
||||
add_library(rtp STATIC ${src_rtp})
|
||||
add_definitions(-DENABLE_RTPPROXY)
|
||||
list(APPEND LINK_LIB_LIST rtp)
|
||||
endif()
|
||||
|
||||
#收集源代码
|
||||
file(GLOB ToolKit_src_list ${ToolKit_Root}/*/*.cpp ${ToolKit_Root}/*/*.h ${ToolKit_Root}/*/*.c)
|
||||
if(IOS)
|
||||
list(APPEND ToolKit_src_list ${ToolKit_Root}/Network/Socket_ios.mm)
|
||||
endif()
|
||||
|
||||
file(GLOB MediaKit_src_list ${MediaKit_Root}/*/*.cpp ${MediaKit_Root}/*/*.h ${MediaKit_Root}/*/*.c)
|
||||
|
||||
#去除win32的适配代码
|
||||
if (NOT WIN32)
|
||||
list(REMOVE_ITEM ToolKit_src_list ${ToolKit_Root}/win32/getopt.c)
|
||||
else()
|
||||
#防止Windows.h包含Winsock.h
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN -DMP4V2_NO_STDINT_DEFS -DOS_WINDOWS)
|
||||
endif ()
|
||||
|
||||
#添加库
|
||||
add_library(zltoolkit STATIC ${ToolKit_src_list})
|
||||
add_library(zlmediakit STATIC ${MediaKit_src_list})
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND LINK_LIB_LIST WS2_32 Iphlpapi shlwapi)
|
||||
set_target_properties(zltoolkit PROPERTIES COMPILE_FLAGS ${VS_FALGS} )
|
||||
@ -171,35 +188,16 @@ elseif(NOT ANDROID OR IOS)
|
||||
list(APPEND LINK_LIB_LIST pthread)
|
||||
endif ()
|
||||
|
||||
#复制文件过来
|
||||
execute_process(COMMAND cp -r ${CMAKE_CURRENT_SOURCE_DIR}/www ${EXECUTABLE_OUTPUT_PATH}/)
|
||||
execute_process(COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini ${EXECUTABLE_OUTPUT_PATH}/)
|
||||
|
||||
#添加c库
|
||||
add_subdirectory(api)
|
||||
|
||||
if (NOT IOS)
|
||||
#测试程序
|
||||
add_subdirectory(tests)
|
||||
|
||||
#主服务器
|
||||
add_subdirectory(server)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
endif ()
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,8 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
Copyright (c) 2018 huohuo <913481084@qq.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
371
README.md
371
README.md
@ -1,63 +1,95 @@
|
||||
![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png)
|
||||
|
||||
# A lightweight ,high performance and stable stream server and client framework based on C++11.
|
||||
[english readme](https://github.com/xiongziliang/ZLMediaKit/blob/master/README_en.md)
|
||||
|
||||
# 一个基于C++11的高性能运营级流媒体服务框架
|
||||
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
|
||||
|
||||
[中文](https://github.com/xiongziliang/ZLMediaKit/blob/master/README_CN.md)
|
||||
|
||||
## Why ZLMediaKit?
|
||||
- Developed based on C++ 11, the code is stable and reliable, avoiding the use of raw pointers, cross-platform porting is simple and convenient, and the code is clear and concise.
|
||||
- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/Websocket-flv`),and support Inter-protocol conversion.
|
||||
- Multiplexing asynchronous network IO based on epoll and multi thread,extreme performance.
|
||||
- Well performance and stable test,can be used commercially.
|
||||
- Support linux, macos, ios, android, Windows Platforms.
|
||||
- Very low latency(lower then one second), video opened immediately.
|
||||
## 国内用户请使用gitee镜像下载
|
||||
```
|
||||
git clone --depth 1 https://gitee.com/xiahcu/ZLMediaKit
|
||||
cd ZLMediaKit
|
||||
git submodule update --init
|
||||
```
|
||||
## 项目特点
|
||||
- 基于C++11开发,避免使用裸指针,代码稳定可靠;同时跨平台移植简单方便,代码清晰简洁。
|
||||
- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV),支持协议间的互相转换,提供一站式的服务。
|
||||
- 使用epoll+线程池+异步网络IO模式开发,并发性能优越。
|
||||
- 已实现主流的的H264/H265+AAC流媒体方案,代码精简,脉络清晰,适合学习。
|
||||
- 编码格式与框架代码解耦,方便自由简洁的添加支持其他编码格式。
|
||||
- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。
|
||||
- 支持linux、macos、ios、android、windows平台。
|
||||
- 支持画面秒开(GOP缓存)、极低延时([500毫秒内,最低可达100毫秒](https://github.com/zlmediakit/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95))。
|
||||
- [ZLMediaKit高并发实现原理](https://github.com/xiongziliang/ZLMediaKit/wiki/ZLMediaKit%E9%AB%98%E5%B9%B6%E5%8F%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)。
|
||||
- 提供完善的标准[C API](https://github.com/xiongziliang/ZLMediaKit/tree/master/api/include),可以作SDK用,或供其他语言调用。
|
||||
- 提供完整的[MediaServer](https://github.com/xiongziliang/ZLMediaKit/tree/master/server)服务器,可以免开发直接部署为商用服务器。
|
||||
|
||||
## Features
|
||||
## 项目定位
|
||||
- 移动嵌入式跨平台流媒体解决方案。
|
||||
- 商用级流媒体服务器。
|
||||
- 网络编程二次开发SDK。
|
||||
|
||||
|
||||
## 功能清单
|
||||
- RTSP
|
||||
- RTSP[S] server,support rtsp push.
|
||||
- RTSP player and pusher.
|
||||
- RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` .
|
||||
- Basic/Digest/Url Authentication.
|
||||
- H265/H264/AAC codec.
|
||||
- Recorded as mp4.
|
||||
- Vod of mp4.
|
||||
- RTSP 服务器,支持RTMP/MP4转RTSP
|
||||
- RTSPS 服务器,支持亚马逊echo show这样的设备
|
||||
- RTSP 播放器,支持RTSP代理,支持生成静音音频
|
||||
- RTSP 推流客户端与服务器
|
||||
- 支持 `rtp over udp` `rtp over tcp` `rtp over http` `rtp组播` 四种RTP传输方式
|
||||
- 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口
|
||||
- 支持H265编码
|
||||
- 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式)
|
||||
- 支持任意编码格式的rtsp推流,只是除H264/H265+AAC外无法转协议
|
||||
|
||||
- RTMP
|
||||
- RTMP server,support player and pusher.
|
||||
- RTMP player and pusher.
|
||||
- Support HTTP-FLV player.
|
||||
- H264/AAC codec.
|
||||
- Recorded as flv or mp4.
|
||||
- Vod of mp4.
|
||||
- RTMP 播放服务器,支持RTSP/MP4转RTMP
|
||||
- RTMP 发布服务器,支持录制发布流
|
||||
- RTMP 播放器,支持RTMP代理,支持生成静音音频
|
||||
- RTMP 推流客户端
|
||||
- 支持http[s]-flv直播
|
||||
- 支持websocket-flv直播
|
||||
- 支持任意编码格式的rtmp推流,只是除H264/H265+AAC外无法转协议
|
||||
|
||||
- HLS
|
||||
- RTSP RTMP can be converted into HLS,built-in HTTP server.
|
||||
- Play authentication based on cookie.
|
||||
- 支持HLS文件生成,自带HTTP文件服务器
|
||||
- 通过cookie追踪技术,可以模拟HLS播放为长连接,实现丰富的业务逻辑
|
||||
- 支持完备的HLS用户追踪、播放统计等业务功能,可以实现HLS按需拉流等业务
|
||||
|
||||
- HTTP[S]
|
||||
- HTTP server,suppor directory meun、RESTful http api.
|
||||
- HTTP client,downloader,uploader,and http api requester.
|
||||
- Cookie supported.
|
||||
- WebSocket Server and Client.
|
||||
- File access authentication.
|
||||
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
|
||||
- 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`
|
||||
- 完整HTTP API服务器,可以作为web后台开发框架
|
||||
- 支持跨域访问
|
||||
- 支持http客户端、服务器cookie
|
||||
- 支持WebSocket服务器和客户端
|
||||
- 支持http文件访问鉴权
|
||||
|
||||
- Others
|
||||
- Support stream proxy by ffmpeg.
|
||||
- RESTful http api and http hook event api.
|
||||
- Config file hot loading.
|
||||
- Vhost supported.
|
||||
- Auto close stream when nobody played.
|
||||
- Play and push authentication.
|
||||
- Pull stream on Demand.
|
||||
- GB28181
|
||||
- 支持UDP/TCP国标RTP推流,可以转换成RTSP/RTMP/HLS等协议
|
||||
|
||||
- 点播
|
||||
- 支持录制为FLV/HLS/MP4
|
||||
- RTSP/RTMP/HTTP-FLV/WS-FLV支持MP4文件点播,支持seek
|
||||
|
||||
- 其他
|
||||
- 支持丰富的restful api以及web hook事件
|
||||
- 支持简单的telnet调试
|
||||
- 支持配置文件热加载
|
||||
- 支持流量统计、推拉流鉴权等事件
|
||||
- 支持虚拟主机,可以隔离不同域名
|
||||
- 支持按需拉流,无人观看自动关断拉流
|
||||
- 支持先拉流后推流,提高及时推流画面打开率
|
||||
- 提供c api sdk
|
||||
|
||||
|
||||
|
||||
- Protocol conversion:
|
||||
## 其他功能细节表
|
||||
|
||||
| protocol/codec | H264 | H265 | AAC | other |
|
||||
- 转协议:
|
||||
|
||||
| 功能/编码格式 | H264 | H265 | AAC | other |
|
||||
| :------------------------------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N |
|
||||
| RTMP --> RTSP[S] | Y | N | Y | N |
|
||||
@ -68,18 +100,18 @@
|
||||
| MP4 --> RTSP[S] | Y | N | Y | N |
|
||||
| MP4 --> RTMP | Y | N | Y | N |
|
||||
|
||||
- Stream generation:
|
||||
- 流生成:
|
||||
|
||||
| feature/codec | H264 | H265 | AAC | other |
|
||||
| :-----------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] push | Y | Y | Y | Y |
|
||||
| RTSP proxy | Y | Y | Y | Y |
|
||||
| RTMP push | Y | Y | Y | Y |
|
||||
| RTMP proxy | Y | Y | Y | Y |
|
||||
| 功能/编码格式 | H264 | H265 | AAC | other |
|
||||
| :------------------------------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S]推流 | Y | Y | Y | Y |
|
||||
| RTSP拉流代理 | Y | Y | Y | Y |
|
||||
| RTMP推流 | Y | Y | Y | Y |
|
||||
| RTMP拉流代理 | Y | Y | Y | Y |
|
||||
|
||||
- RTP transport:
|
||||
- RTP传输方式:
|
||||
|
||||
| feature/transport | tcp | udp | http | udp_multicast |
|
||||
| 功能/RTP传输方式 | tcp | udp | http | udp_multicast |
|
||||
| :-----------------: | :--: | :--: | :--: | :-----------: |
|
||||
| RTSP[S] Play Server | Y | Y | Y | Y |
|
||||
| RTSP[S] Push Server | Y | Y | N | N |
|
||||
@ -87,18 +119,18 @@
|
||||
| RTSP Pusher | Y | Y | N | N |
|
||||
|
||||
|
||||
- Server supported:
|
||||
- 支持的服务器类型列表
|
||||
|
||||
| Server | Y/N |
|
||||
| 服务类型 | Y/N |
|
||||
| :-----------------: | :--: |
|
||||
| RTSP[S] Play Server | Y |
|
||||
| RTSP[S] Push Server | Y |
|
||||
| RTMP | Y |
|
||||
| HTTP[S]/WebSocket[S] | Y |
|
||||
|
||||
- Client supported:
|
||||
- 支持的客户端类型
|
||||
|
||||
| Client | Y/N |
|
||||
| 客户端类型 | Y/N |
|
||||
| :---------: | :--: |
|
||||
| RTSP Player | Y |
|
||||
| RTSP Pusher | Y |
|
||||
@ -107,136 +139,119 @@
|
||||
| HTTP[S] | Y |
|
||||
| WebSocket[S] | Y |
|
||||
|
||||
## 后续任务
|
||||
- 完善支持H265
|
||||
|
||||
## 编译要求
|
||||
- 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上
|
||||
- cmake3.2或以上
|
||||
|
||||
## System Requirements
|
||||
## 编译前必看!!!
|
||||
|
||||
- Compiler support c++11,GCC4.8/Clang3.3/VC2015 or above.
|
||||
- cmake3.1 or above.
|
||||
- All Linux , both 32 and 64 bits
|
||||
- Apple OSX(Darwin), both 32 and 64bits.
|
||||
- All hardware with x86/x86_64/arm/mips cpu.
|
||||
- Windows.
|
||||
- **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.You can do it like this:**
|
||||
- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:**
|
||||
```
|
||||
git clone https://github.com/zlmediakit/ZLMediaKit.git
|
||||
cd ZLMediaKit
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
|
||||
|
||||
## How to build
|
||||
|
||||
It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumbersome, and some features are not compiled by default.
|
||||
|
||||
### Build on linux
|
||||
|
||||
- My environment
|
||||
- Ubuntu16.04 64 bit and gcc5.4
|
||||
## 编译(Linux)
|
||||
- 我的编译环境
|
||||
- Ubuntu16.04 64 bit + gcc5.4
|
||||
- cmake 3.5.1
|
||||
- Guidance
|
||||
- 编译
|
||||
|
||||
```
|
||||
# If it is on centos6.x, you need to install the newer version of GCC and cmake first,
|
||||
# and then compile manually according to the script "build_for_linux.sh".
|
||||
# If it is on a newer version of a system such as Ubuntu or Debain,
|
||||
# step 4 can be manipulated directly.
|
||||
//如果是centos6.x,需要先安装较新版本的gcc以及cmake,然后打开脚本build_for_linux.sh手动编译
|
||||
//如果是ubuntu这样的比较新的系统版本可以直接操作第4步
|
||||
|
||||
# 1、Install GCC5.2 (this step can be skipped if the GCC version is higher than 4.7)
|
||||
1、安装GCC5.2(如果gcc版本高于4.7可以跳过此步骤)
|
||||
sudo yum install centos-release-scl -y
|
||||
sudo yum install devtoolset-4-toolchain -y
|
||||
scl enable devtoolset-4 bash
|
||||
|
||||
# 2、Install cmake (this step can be skipped if the cmake version is higher than 3.1)
|
||||
tar -xvf cmake-3.10.0-rc4.tar.gz #you need download cmake source file manually
|
||||
2、安装cmake
|
||||
#需要安装新版本cmake,当然你也可以通过yum或者apt-get方式安装(前提是版本够新)
|
||||
tar -xvf cmake-3.10.0-rc4.tar.gz
|
||||
cd cmake-3.10.0-rc4
|
||||
./configure
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
# 3、Switch to high version GCC
|
||||
3、切换高版本gcc
|
||||
scl enable devtoolset-4 bash
|
||||
|
||||
# 4、build
|
||||
4、编译
|
||||
cd ZLMediaKit
|
||||
./build_for_linux.sh
|
||||
```
|
||||
|
||||
### Build on macOS
|
||||
|
||||
- My environment
|
||||
## 编译(macOS)
|
||||
- 我的编译环境
|
||||
- macOS Sierra(10.12.1) + xcode8.3.1
|
||||
- Homebrew 1.1.3
|
||||
- cmake 3.8.0
|
||||
- Guidance
|
||||
- 编译
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
./build_for_mac.sh
|
||||
```
|
||||
|
||||
### Build on iOS
|
||||
|
||||
This build method is no longer recommended.It is recommended that make Xcode project by yourself.
|
||||
|
||||
- My environment
|
||||
|
||||
Same with Build on macOS
|
||||
|
||||
- Guidance
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
./build_for_ios.sh
|
||||
```
|
||||
|
||||
- You can also generate Xcode projects and recompile them:
|
||||
## 编译(iOS)
|
||||
- 编译环境:`请参考macOS的编译指导。`
|
||||
- 生成Xcode工程再编译,[了解更多](https://github.com/leetal/ios-cmake):
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
mkdir -p build
|
||||
cd build
|
||||
# Generate Xcode project, project file is in build directory
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=SIMULATOR64 -G "Xcode"
|
||||
# 生成Xcode工程,工程文件在build目录下
|
||||
cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.toolchain.cmake -DPLATFORM=OS64COMBINED
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Build on Android
|
||||
|
||||
Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project.
|
||||
|
||||
- My environment
|
||||
## 编译(Android)
|
||||
- 我的编译环境
|
||||
- macOS Sierra(10.12.1) + xcode8.3.1
|
||||
- Homebrew 1.1.3
|
||||
- cmake 3.8.0
|
||||
- [android-ndk-r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip)
|
||||
- Guidance
|
||||
- 编译
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
export ANDROID_NDK_ROOT=/path/to/ndk
|
||||
./build_for_android.sh
|
||||
```
|
||||
### Build on Windows
|
||||
|
||||
- My environment
|
||||
## 编译(Windows)
|
||||
- 我的编译环境
|
||||
- windows 10
|
||||
- visual studio 2017
|
||||
- [cmake-gui](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi)
|
||||
|
||||
- Guidance
|
||||
- 编译
|
||||
```
|
||||
1 Enter the ZLMediaKit directory and execute git submodule update -- init downloads the code for ZLToolKit
|
||||
2 Open the project with cmake-gui and generate the vs project file.
|
||||
3 Find the project file (ZLMediaKit.sln), double-click to open it with vs2017.
|
||||
4 Choose to compile Release version. Find the target file and run the test case.
|
||||
1 进入ZLMediaKit目录执行 git submodule update --init 以下载ZLToolKit的代码
|
||||
2 使用cmake-gui打开工程并生成vs工程文件.
|
||||
3 找到工程文件(ZLMediaKit.sln),双击用vs2017打开.
|
||||
4 选择编译Release 版本.
|
||||
5 找到目标文件并运行测试用例.
|
||||
```
|
||||
## Usage
|
||||
|
||||
- As server:
|
||||
## Docker Image
|
||||
You can pull a pre-built docker image from Docker Hub and run with
|
||||
```bash
|
||||
docker run -id -p 1935:1935 -p 8080:80 gemfield/zlmediakit
|
||||
```
|
||||
|
||||
Dockerfile is also supplied to build images on Ubuntu 16.04
|
||||
```bash
|
||||
cd docker
|
||||
docker build -t zlmediakit .
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
- 作为服务器:
|
||||
```cpp
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
@ -249,7 +264,7 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
||||
httpsSrv->start<HttpsSession>(mINI::Instance()[Config::Http::kSSLPort]);
|
||||
```
|
||||
|
||||
- As player:
|
||||
- 作为播放器:
|
||||
```cpp
|
||||
MediaPlayer::Ptr player(new MediaPlayer());
|
||||
weak_ptr<MediaPlayer> weakPlayer = player;
|
||||
@ -262,11 +277,11 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
||||
|
||||
auto viedoTrack = strongPlayer->getTrack(TrackVideo);
|
||||
if (!viedoTrack) {
|
||||
WarnL << "none video Track!";
|
||||
WarnL << "没有视频Track!";
|
||||
return;
|
||||
}
|
||||
viedoTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([](const Frame::Ptr &frame) {
|
||||
//please decode video here
|
||||
//此处解码并播放
|
||||
}));
|
||||
});
|
||||
|
||||
@ -274,11 +289,11 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
||||
ErrorL << "OnShutdown:" << ex.what();
|
||||
});
|
||||
|
||||
//rtp transport over tcp
|
||||
//支持rtmp、rtsp
|
||||
(*player)[Client::kRtpType] = Rtsp::RTP_TCP;
|
||||
player->play("rtsp://admin:jzan123456@192.168.0.122/");
|
||||
```
|
||||
- As proxy server:
|
||||
- 作为代理服务器:
|
||||
```cpp
|
||||
//support rtmp and rtsp url
|
||||
//just support H264+AAC
|
||||
@ -287,63 +302,111 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
||||
map<string , PlayerProxy::Ptr> proxyMap;
|
||||
int i=0;
|
||||
for(auto url : urlList){
|
||||
//PlayerProxy构造函数前两个参数分别为应用名(app),流id(streamId)
|
||||
//比如说应用为live,流id为0,那么直播地址为:
|
||||
//http://127.0.0.1/live/0/hls.m3u8
|
||||
//rtsp://127.0.0.1/live/0
|
||||
//rtmp://127.0.0.1/live/0
|
||||
//录像地址为:
|
||||
//http://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
|
||||
//rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
|
||||
//rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
|
||||
PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data()));
|
||||
player->play(url);
|
||||
proxyMap.emplace(string(url),player);
|
||||
}
|
||||
```
|
||||
|
||||
- As puser:
|
||||
- 作为推流客户端器:
|
||||
```cpp
|
||||
PlayerProxy::Ptr player(new PlayerProxy("app","stream"));
|
||||
//拉一个流,生成一个RtmpMediaSource,源的名称是"app/stream"
|
||||
//你也可以以其他方式生成RtmpMediaSource,比如说MP4文件(请研读MediaReader代码)
|
||||
player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks");
|
||||
|
||||
RtmpPusher::Ptr pusher;
|
||||
//监听RtmpMediaSource注册事件,在PlayerProxy播放成功后触发。
|
||||
NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastRtmpSrcRegisted,
|
||||
[&pusher](BroadcastRtmpSrcRegistedArgs){
|
||||
//媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源
|
||||
const_cast<RtmpPusher::Ptr &>(pusher).reset(new RtmpPusher(app,stream));
|
||||
|
||||
//推流地址,请改成你自己的服务器。
|
||||
//这个范例地址(也是基于mediakit)是可用的,但是带宽只有1mb,访问可能很卡顿。
|
||||
pusher->publish("rtmp://jizan.iok.la/live/test");
|
||||
});
|
||||
|
||||
```
|
||||
## QA
|
||||
- 怎么测试服务器性能?
|
||||
|
||||
## Mirrors
|
||||
ZLMediaKit提供了测试性能的示例,代码在tests/test_benchmark.cpp。
|
||||
|
||||
这里是测试报告:[benchmark.md](https://github.com/xiongziliang/ZLMediaKit/blob/master/benchmark.md)
|
||||
|
||||
- github下载太慢了,有其他下载方式吗?
|
||||
|
||||
你可以在通过开源中国获取最新的代码,地址为:
|
||||
|
||||
[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit)
|
||||
|
||||
[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit)
|
||||
|
||||
|
||||
## Licence
|
||||
- 在windows下编译很多错误?
|
||||
|
||||
```
|
||||
MIT License
|
||||
由于本项目主体代码在macOS/linux下开发,部分源码采用的是无bom头的UTF-8编码;由于windows对于utf-8支持不甚友好,所以如果发现编译错误请先尝试添 加bom头再编译。
|
||||
也可以通过参考这篇博客解决:
|
||||
[vs2015:/utf-8选项解决UTF-8 without BOM 源码中文输出乱码问题](https://blog.csdn.net/10km/article/details/80203286)
|
||||
|
||||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
## 参考案例
|
||||
- [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xiahcu/IOSMedia)
|
||||
- [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xiahcu/IOSPlayer)
|
||||
- [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xiongziliang/ZLMediaPlayer)
|
||||
- [配套的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
## 授权协议
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。
|
||||
但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除;
|
||||
由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
## 联系方式
|
||||
- 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复)
|
||||
- QQ群:542509000
|
||||
|
||||
## 怎么提问?
|
||||
如果要对项目有相关疑问,建议您这么做:
|
||||
- 1、仔细看下readme、wiki,如果有必要可以查看下issue.
|
||||
- 2、如果您的问题还没解决,可以提issue.
|
||||
- 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提.
|
||||
- 4、QQ私聊一般不接受无偿技术咨询和支持(谈谈人生理想还是可以的😂),毕竟精力有限,谢谢理解.
|
||||
|
||||
## 致谢
|
||||
感谢以下各位对本项目包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后:
|
||||
|
||||
[老陈](https://github.com/ireader)
|
||||
[Gemfield](https://github.com/gemfield)
|
||||
[南冠彤](https://github.com/nanguantong2)
|
||||
[凹凸慢](https://github.com/tsingeye)
|
||||
[chenxiaolei](https://github.com/chenxiaolei)
|
||||
[史前小虫](https://github.com/zqsong)
|
||||
[清涩绿茶](https://github.com/baiyfcu)
|
||||
[3503207480](https://github.com/3503207480)
|
||||
[DroidChow](https://github.com/DroidChow)
|
||||
[阿塞](https://github.com/HuoQiShuai)
|
||||
[火宣](https://github.com/ChinaCCF)
|
||||
[γ瑞γミ](https://github.com/JerryLinGd)
|
||||
[linkingvision](https://www.linkingvision.com/)
|
||||
[茄子](https://github.com/taotaobujue2008)
|
||||
[好心情](<409257224@qq.com>)
|
||||
|
||||
## 捐赠
|
||||
欢迎捐赠以便更好的推动项目的发展,谢谢您的支持!
|
||||
|
||||
[支付宝](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3919.JPG)
|
||||
|
||||
[微信](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3920.JPG)
|
||||
|
||||
|
||||
|
||||
## Contact
|
||||
- Email:<771730766@qq.com>
|
||||
- QQ chat group:542509000
|
||||
|
||||
|
||||
|
361
README_CN.md
361
README_CN.md
@ -1,361 +0,0 @@
|
||||
![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png)
|
||||
# 一个基于C++11的高性能运营级流媒体服务框架
|
||||
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
|
||||
|
||||
## 项目特点
|
||||
- 基于C++11开发,避免使用裸指针,代码稳定可靠;同时跨平台移植简单方便,代码清晰简洁。
|
||||
- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV),支持协议间的互相转换,提供一站式的服务。
|
||||
- 使用epoll+线程池+异步网络IO模式开发,并发性能优越。
|
||||
- 已实现主流的的H264/H265+AAC流媒体方案,代码精简,脉络清晰,适合学习。
|
||||
- 编码格式与框架代码解耦,方便自由简洁的添加支持其他编码格式
|
||||
- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。
|
||||
- 支持linux、macos、ios、android、windows平台
|
||||
- 支持画面秒开(GOP缓存)、极低延时([500毫秒内,最低可达100毫秒](https://github.com/zlmediakit/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95))
|
||||
- [ZLMediaKit高并发实现原理](https://github.com/xiongziliang/ZLMediaKit/wiki/ZLMediaKit%E9%AB%98%E5%B9%B6%E5%8F%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)
|
||||
|
||||
## 项目定位
|
||||
- 移动嵌入式跨平台流媒体解决方案。
|
||||
- 商用级流媒体服务器。
|
||||
- 网络编程二次开发SDK。
|
||||
|
||||
|
||||
## 功能清单
|
||||
- RTSP
|
||||
- RTSP 服务器,支持RTMP/MP4转RTSP。
|
||||
- RTSPS 服务器,支持亚马逊echo show这样的设备
|
||||
- RTSP 播放器,支持RTSP代理,支持生成静音音频
|
||||
- RTSP 推流客户端与服务器
|
||||
- 支持 `rtp over udp` `rtp over tcp` `rtp over http` `rtp组播` 四种RTP传输方式 。
|
||||
- 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口。
|
||||
- 支持H265编码
|
||||
- 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式)
|
||||
- 支持任意编码格式的rtsp推流,只是除H264/H265+AAC外无法转协议
|
||||
|
||||
- RTMP
|
||||
- RTMP 播放服务器,支持RTSP/MP4转RTMP。
|
||||
- RTMP 发布服务器,支持录制发布流。
|
||||
- RTMP 播放器,支持RTMP代理,支持生成静音音频
|
||||
- RTMP 推流客户端。
|
||||
- 支持http-flv直播。
|
||||
- 支持https-flv直播。
|
||||
- 支持任意编码格式的rtmp推流,只是除H264/H265+AAC外无法转协议
|
||||
|
||||
- HLS
|
||||
- 支持HLS文件生成,自带HTTP文件服务器。
|
||||
- 支持播放鉴权,鉴权结果可以缓存为cookie
|
||||
|
||||
- HTTP[S]
|
||||
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`。
|
||||
- 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`。
|
||||
- 完整HTTP API服务器,可以作为web后台开发框架。
|
||||
- 支持跨域访问。
|
||||
- 支持http客户端、服务器cookie
|
||||
- 支持WebSocket服务器和客户端
|
||||
- 支持http文件访问鉴权
|
||||
|
||||
- 其他
|
||||
- 支持输入YUV+PCM自动生成RTSP/RTMP/HLS/MP4.
|
||||
- 支持简单的telnet调试。
|
||||
- 支持H264的解析,支持B帧的POC计算排序。
|
||||
- 支持配置文件热加载
|
||||
- 支持流量统计、推流播放鉴权等事件
|
||||
- 支持rtsp/rtmp/http虚拟主机
|
||||
- 支持flv、mp4文件录制
|
||||
- 支持rtps/rtmp协议的mp4点播,支持seek
|
||||
- 支持按需拉流,无人观看自动关断拉流
|
||||
- 支持先拉流后推流,提高及时推流画面打开率
|
||||
- 支持rtsp/rtmp/http-flv/hls播放鉴权(url参数方式)
|
||||
|
||||
|
||||
|
||||
## 其他功能细节表
|
||||
|
||||
- 转协议:
|
||||
|
||||
| 功能/编码格式 | H264 | H265 | AAC | other |
|
||||
| :------------------------------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N |
|
||||
| RTMP --> RTSP[S] | Y | N | Y | N |
|
||||
| RTSP[S] --> HLS | Y | Y | Y | N |
|
||||
| RTMP --> HLS | Y | N | Y | N |
|
||||
| RTSP[S] --> MP4 | Y | Y | Y | N |
|
||||
| RTMP --> MP4 | Y | N | Y | N |
|
||||
| MP4 --> RTSP[S] | Y | N | Y | N |
|
||||
| MP4 --> RTMP | Y | N | Y | N |
|
||||
|
||||
- 流生成:
|
||||
|
||||
| 功能/编码格式 | H264 | H265 | AAC | other |
|
||||
| :------------------------------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S]推流 | Y | Y | Y | Y |
|
||||
| RTSP拉流代理 | Y | Y | Y | Y |
|
||||
| RTMP推流 | Y | Y | Y | Y |
|
||||
| RTMP拉流代理 | Y | Y | Y | Y |
|
||||
|
||||
- RTP传输方式:
|
||||
|
||||
| 功能/RTP传输方式 | tcp | udp | http | udp_multicast |
|
||||
| :-----------------: | :--: | :--: | :--: | :-----------: |
|
||||
| RTSP[S] Play Server | Y | Y | Y | Y |
|
||||
| RTSP[S] Push Server | Y | Y | N | N |
|
||||
| RTSP Player | Y | Y | N | Y |
|
||||
| RTSP Pusher | Y | Y | N | N |
|
||||
|
||||
|
||||
- 支持的服务器类型列表
|
||||
|
||||
| 服务类型 | Y/N |
|
||||
| :-----------------: | :--: |
|
||||
| RTSP[S] Play Server | Y |
|
||||
| RTSP[S] Push Server | Y |
|
||||
| RTMP | Y |
|
||||
| HTTP[S]/WebSocket[S] | Y |
|
||||
|
||||
- 支持的客户端类型
|
||||
|
||||
| 客户端类型 | Y/N |
|
||||
| :---------: | :--: |
|
||||
| RTSP Player | Y |
|
||||
| RTSP Pusher | Y |
|
||||
| RTMP Player | Y |
|
||||
| RTMP Pusher | Y |
|
||||
| HTTP[S] | Y |
|
||||
| WebSocket[S] | Y |
|
||||
|
||||
## 后续任务
|
||||
- 完善支持H265
|
||||
|
||||
## 编译要求
|
||||
- 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上
|
||||
- cmake3.2或以上
|
||||
- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:**
|
||||
```
|
||||
git clone https://github.com/zlmediakit/ZLMediaKit.git
|
||||
cd ZLMediaKit
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
## 编译(Linux)
|
||||
- 我的编译环境
|
||||
- Ubuntu16.04 64 bit + gcc5.4
|
||||
- cmake 3.5.1
|
||||
- 编译
|
||||
|
||||
```
|
||||
//如果是centos6.x,需要先安装较新版本的gcc以及cmake,然后打开脚本build_for_linux.sh手动编译
|
||||
//如果是ubuntu这样的比较新的系统版本可以直接操作第4步
|
||||
|
||||
1、安装GCC5.2(如果gcc版本高于4.7可以跳过此步骤)
|
||||
sudo yum install centos-release-scl -y
|
||||
sudo yum install devtoolset-4-toolchain -y
|
||||
scl enable devtoolset-4 bash
|
||||
|
||||
2、安装cmake
|
||||
#需要安装新版本cmake,当然你也可以通过yum或者apt-get方式安装(前提是版本够新)
|
||||
tar -xvf cmake-3.10.0-rc4.tar.gz
|
||||
cd cmake-3.10.0-rc4
|
||||
./configure
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
3、切换高版本gcc
|
||||
scl enable devtoolset-4 bash
|
||||
|
||||
4、编译
|
||||
cd ZLMediaKit
|
||||
./build_for_linux.sh
|
||||
```
|
||||
|
||||
## 编译(macOS)
|
||||
- 我的编译环境
|
||||
- macOS Sierra(10.12.1) + xcode8.3.1
|
||||
- Homebrew 1.1.3
|
||||
- cmake 3.8.0
|
||||
- 编译
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
./build_for_mac.sh
|
||||
```
|
||||
|
||||
## 编译(iOS)
|
||||
- 编译环境:`请参考macOS的编译指导。`
|
||||
- 编译
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
./build_for_ios.sh
|
||||
```
|
||||
- 你也可以生成Xcode工程再编译:
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
mkdir -p build
|
||||
cd build
|
||||
# 生成Xcode工程,工程文件在build目录下
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=SIMULATOR64 -G "Xcode"
|
||||
```
|
||||
|
||||
## 编译(Android)
|
||||
- 我的编译环境
|
||||
- macOS Sierra(10.12.1) + xcode8.3.1
|
||||
- Homebrew 1.1.3
|
||||
- cmake 3.8.0
|
||||
- [android-ndk-r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip)
|
||||
- 编译
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
export ANDROID_NDK_ROOT=/path/to/ndk
|
||||
./build_for_android.sh
|
||||
```
|
||||
## 编译(Windows)
|
||||
- 我的编译环境
|
||||
- windows 10
|
||||
- visual studio 2017
|
||||
- [cmake-gui](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi)
|
||||
|
||||
- 编译
|
||||
```
|
||||
1 进入ZLMediaKit目录执行 git submodule update --init 以下载ZLToolKit的代码
|
||||
2 使用cmake-gui打开工程并生成vs工程文件.
|
||||
3 找到工程文件(ZLMediaKit.sln),双击用vs2017打开.
|
||||
4 选择编译Release 版本.
|
||||
5 找到目标文件并运行测试用例.
|
||||
```
|
||||
## 使用方法
|
||||
- 作为服务器:
|
||||
```cpp
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||
|
||||
rtspSrv->start<RtspSession>(mINI::Instance()[Config::Rtsp::kPort]);
|
||||
rtmpSrv->start<RtmpSession>(mINI::Instance()[Config::Rtmp::kPort]);
|
||||
httpSrv->start<HttpSession>(mINI::Instance()[Config::Http::kPort]);
|
||||
httpsSrv->start<HttpsSession>(mINI::Instance()[Config::Http::kSSLPort]);
|
||||
```
|
||||
|
||||
- 作为播放器:
|
||||
```cpp
|
||||
MediaPlayer::Ptr player(new MediaPlayer());
|
||||
weak_ptr<MediaPlayer> weakPlayer = player;
|
||||
player->setOnPlayResult([weakPlayer](const SockException &ex) {
|
||||
InfoL << "OnPlayResult:" << ex.what();
|
||||
auto strongPlayer = weakPlayer.lock();
|
||||
if (ex || !strongPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto viedoTrack = strongPlayer->getTrack(TrackVideo);
|
||||
if (!viedoTrack) {
|
||||
WarnL << "没有视频Track!";
|
||||
return;
|
||||
}
|
||||
viedoTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([](const Frame::Ptr &frame) {
|
||||
//此处解码并播放
|
||||
}));
|
||||
});
|
||||
|
||||
player->setOnShutdown([](const SockException &ex) {
|
||||
ErrorL << "OnShutdown:" << ex.what();
|
||||
});
|
||||
|
||||
//支持rtmp、rtsp
|
||||
(*player)[Client::kRtpType] = Rtsp::RTP_TCP;
|
||||
player->play("rtsp://admin:jzan123456@192.168.0.122/");
|
||||
```
|
||||
- 作为代理服务器:
|
||||
```cpp
|
||||
//support rtmp and rtsp url
|
||||
//just support H264+AAC
|
||||
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks",
|
||||
"rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"};
|
||||
map<string , PlayerProxy::Ptr> proxyMap;
|
||||
int i=0;
|
||||
for(auto url : urlList){
|
||||
//PlayerProxy构造函数前两个参数分别为应用名(app),流id(streamId)
|
||||
//比如说应用为live,流id为0,那么直播地址为:
|
||||
//http://127.0.0.1/live/0/hls.m3u8
|
||||
//rtsp://127.0.0.1/live/0
|
||||
//rtmp://127.0.0.1/live/0
|
||||
//录像地址为:
|
||||
//http://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
|
||||
//rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
|
||||
//rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
|
||||
PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data()));
|
||||
player->play(url);
|
||||
proxyMap.emplace(string(url),player);
|
||||
}
|
||||
```
|
||||
|
||||
- 作为推流客户端器:
|
||||
```cpp
|
||||
PlayerProxy::Ptr player(new PlayerProxy("app","stream"));
|
||||
//拉一个流,生成一个RtmpMediaSource,源的名称是"app/stream"
|
||||
//你也可以以其他方式生成RtmpMediaSource,比如说MP4文件(请研读MediaReader代码)
|
||||
player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks");
|
||||
|
||||
RtmpPusher::Ptr pusher;
|
||||
//监听RtmpMediaSource注册事件,在PlayerProxy播放成功后触发。
|
||||
NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastRtmpSrcRegisted,
|
||||
[&pusher](BroadcastRtmpSrcRegistedArgs){
|
||||
//媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源
|
||||
const_cast<RtmpPusher::Ptr &>(pusher).reset(new RtmpPusher(app,stream));
|
||||
|
||||
//推流地址,请改成你自己的服务器。
|
||||
//这个范例地址(也是基于mediakit)是可用的,但是带宽只有1mb,访问可能很卡顿。
|
||||
pusher->publish("rtmp://jizan.iok.la/live/test");
|
||||
});
|
||||
|
||||
```
|
||||
## QA
|
||||
- 怎么测试服务器性能?
|
||||
|
||||
ZLMediaKit提供了测试性能的示例,代码在tests/test_benchmark.cpp。
|
||||
|
||||
这里是测试报告:[benchmark.md](https://github.com/xiongziliang/ZLMediaKit/blob/master/benchmark.md)
|
||||
|
||||
- github下载太慢了,有其他下载方式吗?
|
||||
|
||||
你可以在通过开源中国获取最新的代码,地址为:
|
||||
|
||||
[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit)
|
||||
|
||||
[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit)
|
||||
|
||||
|
||||
- 在windows下编译很多错误?
|
||||
|
||||
由于本项目主体代码在macOS/linux下开发,部分源码采用的是无bom头的UTF-8编码;由于windows对于utf-8支持不甚友好,所以如果发现编译错误请先尝试添 加bom头再编译。
|
||||
也可以通过参考这篇博客解决:
|
||||
[vs2015:/utf-8选项解决UTF-8 without BOM 源码中文输出乱码问题](https://blog.csdn.net/10km/article/details/80203286)
|
||||
|
||||
## 参考案例
|
||||
- [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xiahcu/IOSMedia)
|
||||
- [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xiahcu/IOSPlayer)
|
||||
- [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xiongziliang/ZLMediaPlayer)
|
||||
|
||||
上述工程可能在最新的代码的情况下编译不过,请手动修改
|
||||
|
||||
|
||||
## 授权协议
|
||||
|
||||
本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。
|
||||
但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除;
|
||||
由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。
|
||||
|
||||
## 联系方式
|
||||
- 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复)
|
||||
- QQ群:542509000
|
||||
|
||||
## 捐赠
|
||||
如果本项目能切实帮助您减少重复开发的工作量,您可以在自愿的基础上支持下作者,谢谢!
|
||||
|
||||
[支付宝](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3919.JPG)
|
||||
|
||||
[微信](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3920.JPG)
|
||||
|
||||
|
||||
|
348
README_en.md
Normal file
348
README_en.md
Normal file
@ -0,0 +1,348 @@
|
||||
![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png)
|
||||
|
||||
# A lightweight ,high performance and stable stream server and client framework based on C++11.
|
||||
|
||||
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
|
||||
|
||||
|
||||
## Why ZLMediaKit?
|
||||
- Developed based on C++ 11, the code is stable and reliable, avoiding the use of raw pointers, cross-platform porting is simple and convenient, and the code is clear and concise.
|
||||
- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/Websocket-flv`),and support Inter-protocol conversion.
|
||||
- Multiplexing asynchronous network IO based on epoll and multi thread,extreme performance.
|
||||
- Well performance and stable test,can be used commercially.
|
||||
- Support linux, macos, ios, android, Windows Platforms.
|
||||
- Very low latency(lower then one second), video opened immediately.
|
||||
|
||||
## Features
|
||||
|
||||
- RTSP
|
||||
- RTSP[S] server,support rtsp push.
|
||||
- RTSP player and pusher.
|
||||
- RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` .
|
||||
- Basic/Digest/Url Authentication.
|
||||
- H265/H264/AAC codec.
|
||||
- Recorded as mp4.
|
||||
- Vod of mp4.
|
||||
|
||||
- RTMP
|
||||
- RTMP server,support player and pusher.
|
||||
- RTMP player and pusher.
|
||||
- Support HTTP-FLV player.
|
||||
- H264/AAC codec.
|
||||
- Recorded as flv or mp4.
|
||||
- Vod of mp4.
|
||||
|
||||
- HLS
|
||||
- RTSP RTMP can be converted into HLS,built-in HTTP server.
|
||||
- Play authentication based on cookie.
|
||||
|
||||
- HTTP[S]
|
||||
- HTTP server,suppor directory meun、RESTful http api.
|
||||
- HTTP client,downloader,uploader,and http api requester.
|
||||
- Cookie supported.
|
||||
- WebSocket Server and Client.
|
||||
- File access authentication.
|
||||
|
||||
- Others
|
||||
- Support stream proxy by ffmpeg.
|
||||
- RESTful http api and http hook event api.
|
||||
- Config file hot loading.
|
||||
- Vhost supported.
|
||||
- Auto close stream when nobody played.
|
||||
- Play and push authentication.
|
||||
- Pull stream on Demand.
|
||||
|
||||
|
||||
|
||||
- Protocol conversion:
|
||||
|
||||
| protocol/codec | H264 | H265 | AAC | other |
|
||||
| :------------------------------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N |
|
||||
| RTMP --> RTSP[S] | Y | N | Y | N |
|
||||
| RTSP[S] --> HLS | Y | Y | Y | N |
|
||||
| RTMP --> HLS | Y | N | Y | N |
|
||||
| RTSP[S] --> MP4 | Y | Y | Y | N |
|
||||
| RTMP --> MP4 | Y | N | Y | N |
|
||||
| MP4 --> RTSP[S] | Y | N | Y | N |
|
||||
| MP4 --> RTMP | Y | N | Y | N |
|
||||
|
||||
- Stream generation:
|
||||
|
||||
| feature/codec | H264 | H265 | AAC | other |
|
||||
| :-----------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] push | Y | Y | Y | Y |
|
||||
| RTSP proxy | Y | Y | Y | Y |
|
||||
| RTMP push | Y | Y | Y | Y |
|
||||
| RTMP proxy | Y | Y | Y | Y |
|
||||
|
||||
- RTP transport:
|
||||
|
||||
| feature/transport | tcp | udp | http | udp_multicast |
|
||||
| :-----------------: | :--: | :--: | :--: | :-----------: |
|
||||
| RTSP[S] Play Server | Y | Y | Y | Y |
|
||||
| RTSP[S] Push Server | Y | Y | N | N |
|
||||
| RTSP Player | Y | Y | N | Y |
|
||||
| RTSP Pusher | Y | Y | N | N |
|
||||
|
||||
|
||||
- Server supported:
|
||||
|
||||
| Server | Y/N |
|
||||
| :-----------------: | :--: |
|
||||
| RTSP[S] Play Server | Y |
|
||||
| RTSP[S] Push Server | Y |
|
||||
| RTMP | Y |
|
||||
| HTTP[S]/WebSocket[S] | Y |
|
||||
|
||||
- Client supported:
|
||||
|
||||
| Client | Y/N |
|
||||
| :---------: | :--: |
|
||||
| RTSP Player | Y |
|
||||
| RTSP Pusher | Y |
|
||||
| RTMP Player | Y |
|
||||
| RTMP Pusher | Y |
|
||||
| HTTP[S] | Y |
|
||||
| WebSocket[S] | Y |
|
||||
|
||||
|
||||
|
||||
## System Requirements
|
||||
|
||||
- Compiler support c++11,GCC4.8/Clang3.3/VC2015 or above.
|
||||
- cmake3.1 or above.
|
||||
- All Linux , both 32 and 64 bits
|
||||
- Apple OSX(Darwin), both 32 and 64bits.
|
||||
- All hardware with x86/x86_64/arm/mips cpu.
|
||||
- Windows.
|
||||
|
||||
## How to build
|
||||
|
||||
It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumbersome, and some features are not compiled by default.
|
||||
|
||||
### Before build
|
||||
- **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.You can do it like this:**
|
||||
```
|
||||
git clone https://github.com/zlmediakit/ZLMediaKit.git
|
||||
cd ZLMediaKit
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
### Build on linux
|
||||
|
||||
- My environment
|
||||
- Ubuntu16.04 64 bit and gcc5.4
|
||||
- cmake 3.5.1
|
||||
- Guidance
|
||||
|
||||
```
|
||||
# If it is on centos6.x, you need to install the newer version of GCC and cmake first,
|
||||
# and then compile manually according to the script "build_for_linux.sh".
|
||||
# If it is on a newer version of a system such as Ubuntu or Debain,
|
||||
# step 4 can be manipulated directly.
|
||||
|
||||
# 1、Install GCC5.2 (this step can be skipped if the GCC version is higher than 4.7)
|
||||
sudo yum install centos-release-scl -y
|
||||
sudo yum install devtoolset-4-toolchain -y
|
||||
scl enable devtoolset-4 bash
|
||||
|
||||
# 2、Install cmake (this step can be skipped if the cmake version is higher than 3.1)
|
||||
tar -xvf cmake-3.10.0-rc4.tar.gz #you need download cmake source file manually
|
||||
cd cmake-3.10.0-rc4
|
||||
./configure
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
# 3、Switch to high version GCC
|
||||
scl enable devtoolset-4 bash
|
||||
|
||||
# 4、build
|
||||
cd ZLMediaKit
|
||||
./build_for_linux.sh
|
||||
```
|
||||
|
||||
### Build on macOS
|
||||
|
||||
- My environment
|
||||
- macOS Sierra(10.12.1) + xcode8.3.1
|
||||
- Homebrew 1.1.3
|
||||
- cmake 3.8.0
|
||||
- Guidance
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
./build_for_mac.sh
|
||||
```
|
||||
|
||||
### Build on iOS
|
||||
- You can generate Xcode projects and recompile them , [learn more](https://github.com/leetal/ios-cmake):
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
mkdir -p build
|
||||
cd build
|
||||
# Generate Xcode project, project file is in build directory
|
||||
cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.toolchain.cmake -DPLATFORM=OS64COMBINED
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Build on Android
|
||||
|
||||
Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project.
|
||||
|
||||
- My environment
|
||||
- macOS Sierra(10.12.1) + xcode8.3.1
|
||||
- Homebrew 1.1.3
|
||||
- cmake 3.8.0
|
||||
- [android-ndk-r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip)
|
||||
- Guidance
|
||||
|
||||
```
|
||||
cd ZLMediaKit
|
||||
export ANDROID_NDK_ROOT=/path/to/ndk
|
||||
./build_for_android.sh
|
||||
```
|
||||
### Build on Windows
|
||||
|
||||
- My environment
|
||||
- windows 10
|
||||
- visual studio 2017
|
||||
- [cmake-gui](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi)
|
||||
|
||||
- Guidance
|
||||
```
|
||||
1 Enter the ZLMediaKit directory and execute git submodule update -- init downloads the code for ZLToolKit
|
||||
2 Open the project with cmake-gui and generate the vs project file.
|
||||
3 Find the project file (ZLMediaKit.sln), double-click to open it with vs2017.
|
||||
4 Choose to compile Release version. Find the target file and run the test case.
|
||||
```
|
||||
## Usage
|
||||
|
||||
- As server:
|
||||
```cpp
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||
|
||||
rtspSrv->start<RtspSession>(mINI::Instance()[Config::Rtsp::kPort]);
|
||||
rtmpSrv->start<RtmpSession>(mINI::Instance()[Config::Rtmp::kPort]);
|
||||
httpSrv->start<HttpSession>(mINI::Instance()[Config::Http::kPort]);
|
||||
httpsSrv->start<HttpsSession>(mINI::Instance()[Config::Http::kSSLPort]);
|
||||
```
|
||||
|
||||
- As player:
|
||||
```cpp
|
||||
MediaPlayer::Ptr player(new MediaPlayer());
|
||||
weak_ptr<MediaPlayer> weakPlayer = player;
|
||||
player->setOnPlayResult([weakPlayer](const SockException &ex) {
|
||||
InfoL << "OnPlayResult:" << ex.what();
|
||||
auto strongPlayer = weakPlayer.lock();
|
||||
if (ex || !strongPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto viedoTrack = strongPlayer->getTrack(TrackVideo);
|
||||
if (!viedoTrack) {
|
||||
WarnL << "none video Track!";
|
||||
return;
|
||||
}
|
||||
viedoTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([](const Frame::Ptr &frame) {
|
||||
//please decode video here
|
||||
}));
|
||||
});
|
||||
|
||||
player->setOnShutdown([](const SockException &ex) {
|
||||
ErrorL << "OnShutdown:" << ex.what();
|
||||
});
|
||||
|
||||
//rtp transport over tcp
|
||||
(*player)[Client::kRtpType] = Rtsp::RTP_TCP;
|
||||
player->play("rtsp://admin:jzan123456@192.168.0.122/");
|
||||
```
|
||||
- As proxy server:
|
||||
```cpp
|
||||
//support rtmp and rtsp url
|
||||
//just support H264+AAC
|
||||
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks",
|
||||
"rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"};
|
||||
map<string , PlayerProxy::Ptr> proxyMap;
|
||||
int i=0;
|
||||
for(auto url : urlList){
|
||||
PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data()));
|
||||
player->play(url);
|
||||
proxyMap.emplace(string(url),player);
|
||||
}
|
||||
```
|
||||
|
||||
- As puser:
|
||||
```cpp
|
||||
PlayerProxy::Ptr player(new PlayerProxy("app","stream"));
|
||||
player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks");
|
||||
|
||||
RtmpPusher::Ptr pusher;
|
||||
NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastRtmpSrcRegisted,
|
||||
[&pusher](BroadcastRtmpSrcRegistedArgs){
|
||||
const_cast<RtmpPusher::Ptr &>(pusher).reset(new RtmpPusher(app,stream));
|
||||
pusher->publish("rtmp://jizan.iok.la/live/test");
|
||||
});
|
||||
|
||||
```
|
||||
## Docker Image
|
||||
You can pull a pre-built docker image from Docker Hub and run with
|
||||
```bash
|
||||
docker run -id -p 1935:1935 -p 8080:80 gemfield/zlmediakit
|
||||
```
|
||||
|
||||
Dockerfile is also supplied to build images on Ubuntu 16.04
|
||||
```bash
|
||||
cd docker
|
||||
docker build -t zlmediakit .
|
||||
```
|
||||
|
||||
## Mirrors
|
||||
|
||||
[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit)
|
||||
|
||||
[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit)
|
||||
|
||||
|
||||
## Licence
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
Copyright (c) 2018 huohuo <913481084@qq.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Contact
|
||||
- Email:<771730766@qq.com>
|
||||
- QQ chat group:542509000
|
||||
|
||||
|
28
api/CMakeLists.txt
Normal file
28
api/CMakeLists.txt
Normal file
@ -0,0 +1,28 @@
|
||||
include_directories(include source)
|
||||
|
||||
file(GLOB api_src_list include/*.h source/*.cpp source/*.h source/*.c)
|
||||
if (IOS)
|
||||
add_library(mk_api STATIC ${api_src_list})
|
||||
target_link_libraries(mk_api ${LINK_LIB_LIST})
|
||||
else ()
|
||||
add_library(mk_api SHARED ${api_src_list})
|
||||
if (WIN32)
|
||||
add_definitions(-DMediaKitApi_EXPORTS)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(mk_api ${LINK_LIB_LIST})
|
||||
add_subdirectory(tests)
|
||||
|
||||
#安装目录
|
||||
if (WIN32)
|
||||
set(INSTALL_PATH_LIB $ENV{HOME}/${CMAKE_PROJECT_NAME}/lib)
|
||||
set(INSTALL_PATH_INCLUDE $ENV{HOME}/${CMAKE_PROJECT_NAME}/include)
|
||||
else ()
|
||||
set(INSTALL_PATH_LIB lib)
|
||||
set(INSTALL_PATH_INCLUDE include)
|
||||
endif ()
|
||||
|
||||
file(GLOB api_header_list include/*.h)
|
||||
install(FILES ${api_header_list} DESTINATION ${INSTALL_PATH_INCLUDE})
|
||||
install(TARGETS mk_api ARCHIVE DESTINATION ${INSTALL_PATH_LIB} LIBRARY DESTINATION ${INSTALL_PATH_LIB})
|
||||
endif ()
|
147
api/include/mk_common.h
Executable file
147
api/include/mk_common.h
Executable file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_COMMON_H
|
||||
#define MK_COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(MediaKitApi_EXPORTS)
|
||||
#define API_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define API_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#define API_CALL __cdecl
|
||||
#else
|
||||
#define API_EXPORT
|
||||
#define API_CALL
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
// 线程数
|
||||
int thread_num;
|
||||
// 日志级别,支持0~4
|
||||
int log_level;
|
||||
|
||||
// 配置文件是内容还是路径
|
||||
int ini_is_path;
|
||||
// 配置文件内容或路径,可以为NULL,如果该文件不存在,那么将导出默认配置至该文件
|
||||
const char *ini;
|
||||
|
||||
// ssl证书是内容还是路径
|
||||
int ssl_is_path;
|
||||
// ssl证书内容或路径,可以为NULL
|
||||
const char *ssl;
|
||||
// 证书密码,可以为NULL
|
||||
const char *ssl_pwd;
|
||||
} mk_config;
|
||||
|
||||
/**
|
||||
* 初始化环境,调用该库前需要先调用此函数
|
||||
* @param cfg 库运行相关参数
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_env_init(const mk_config *cfg);
|
||||
|
||||
/**
|
||||
* 关闭所有服务器,请在main函数退出时调用
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_stop_all_server();
|
||||
|
||||
/**
|
||||
* 基础类型参数版本的mk_env_init,为了方便其他语言调用
|
||||
* @param thread_num 线程数
|
||||
* @param log_level 日志级别,支持0~4
|
||||
* @param ini_is_path 配置文件是内容还是路径
|
||||
* @param ini 配置文件内容或路径,可以为NULL,如果该文件不存在,那么将导出默认配置至该文件
|
||||
* @param ssl_is_path ssl证书是内容还是路径
|
||||
* @param ssl ssl证书内容或路径,可以为NULL
|
||||
* @param ssl_pwd 证书密码,可以为NULL
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_env_init1( int thread_num,
|
||||
int log_level,
|
||||
int ini_is_path,
|
||||
const char *ini,
|
||||
int ssl_is_path,
|
||||
const char *ssl,
|
||||
const char *ssl_pwd);
|
||||
|
||||
/**
|
||||
* 设置配置项
|
||||
* @param key 配置项名
|
||||
* @param val 配置项值
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_set_option(const char *key, const char *val);
|
||||
|
||||
/**
|
||||
* 创建http[s]服务器
|
||||
* @param port htt监听端口,推荐80,传入0则随机分配
|
||||
* @param ssl 是否为ssl类型服务器
|
||||
* @return 0:失败,非0:端口号
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_http_server_start(uint16_t port, int ssl);
|
||||
|
||||
/**
|
||||
* 创建rtsp[s]服务器
|
||||
* @param port rtsp监听端口,推荐554,传入0则随机分配
|
||||
* @param ssl 是否为ssl类型服务器
|
||||
* @return 0:失败,非0:端口号
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_rtsp_server_start(uint16_t port, int ssl);
|
||||
|
||||
/**
|
||||
* 创建rtmp[s]服务器
|
||||
* @param port rtmp监听端口,推荐1935,传入0则随机分配
|
||||
* @param ssl 是否为ssl类型服务器
|
||||
* @return 0:失败,非0:端口号
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_rtmp_server_start(uint16_t port, int ssl);
|
||||
|
||||
/**
|
||||
* 创建rtp服务器
|
||||
* @param port rtp监听端口(包括udp/tcp)
|
||||
* @return 0:失败,非0:端口号
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port);
|
||||
|
||||
/**
|
||||
* 创建shell服务器
|
||||
* @param port shell监听端口
|
||||
* @return 0:失败,非0:端口号
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_shell_server_start(uint16_t port);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* MK_COMMON_H */
|
188
api/include/mk_events.h
Normal file
188
api/include/mk_events.h
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_EVENTS_H
|
||||
#define MK_EVENTS_H
|
||||
|
||||
#include "mk_common.h"
|
||||
#include "mk_events_objects.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* 注册或反注册MediaSource事件广播
|
||||
* @param regist 注册为1,注销为0
|
||||
* @param sender 该MediaSource对象
|
||||
*/
|
||||
void (API_CALL *on_mk_media_changed)(int regist,
|
||||
const mk_media_source sender);
|
||||
|
||||
/**
|
||||
* 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
||||
* @see mk_publish_auth_invoker_do
|
||||
* @param url_info 推流url相关信息
|
||||
* @param invoker 执行invoker返回鉴权结果
|
||||
* @param sender 该tcp客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_media_publish)(const mk_media_info url_info,
|
||||
const mk_publish_auth_invoker invoker,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 播放rtsp/rtmp/http-flv/hls事件广播,通过该事件控制播放鉴权
|
||||
* @see mk_auth_invoker_do
|
||||
* @param url_info 播放url相关信息
|
||||
* @param invoker 执行invoker返回鉴权结果
|
||||
* @param sender 播放客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_media_play)(const mk_media_info url_info,
|
||||
const mk_auth_invoker invoker,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了
|
||||
* @param url_info 播放url相关信息
|
||||
* @param sender 播放客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_media_not_found)(const mk_media_info url_info,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑
|
||||
* @param sender 该MediaSource对象
|
||||
*/
|
||||
void (API_CALL *on_mk_media_no_reader)(const mk_media_source sender);
|
||||
|
||||
/**
|
||||
* 收到http api请求广播(包括GET/POST)
|
||||
* @param parser http请求内容对象
|
||||
* @param invoker 执行该invoker返回http回复
|
||||
* @param consumed 置1则说明我们要处理该事件
|
||||
* @param sender http客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_http_request)(const mk_parser parser,
|
||||
const mk_http_response_invoker invoker,
|
||||
int *consumed,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
|
||||
* @param parser http请求内容对象
|
||||
* @param path 文件绝对路径
|
||||
* @param is_dir path是否为文件夹
|
||||
* @param invoker 执行invoker返回本次访问文件的结果
|
||||
* @param sender http客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_http_access)(const mk_parser parser,
|
||||
const char *path,
|
||||
int is_dir,
|
||||
const mk_http_access_path_invoker invoker,
|
||||
mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
|
||||
* 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的
|
||||
* @param parser http请求内容对象
|
||||
* @param path 文件绝对路径,覆盖之可以重定向到其他文件
|
||||
* @param sender http客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_http_before_access)(const mk_parser parser,
|
||||
char *path,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 该rtsp流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm
|
||||
* @param url_info 请求rtsp url相关信息
|
||||
* @param invoker 执行invoker返回是否需要rtsp专属认证
|
||||
* @param sender rtsp客户端相关信息
|
||||
*/
|
||||
void (API_CALL *on_mk_rtsp_get_realm)(const mk_media_info url_info,
|
||||
const mk_rtsp_get_realm_invoker invoker,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
|
||||
* 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码
|
||||
* @param url_info 请求rtsp url相关信息
|
||||
* @param realm rtsp认证realm
|
||||
* @param user_name rtsp认证用户名
|
||||
* @param must_no_encrypt 如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
|
||||
* @param invoker 执行invoker返回rtsp专属认证的密码
|
||||
* @param sender rtsp客户端信息
|
||||
*/
|
||||
void (API_CALL *on_mk_rtsp_auth)(const mk_media_info url_info,
|
||||
const char *realm,
|
||||
const char *user_name,
|
||||
int must_no_encrypt,
|
||||
const mk_rtsp_auth_invoker invoker,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 录制mp4分片文件成功后广播
|
||||
*/
|
||||
void (API_CALL *on_mk_record_mp4)(const mk_mp4_info mp4);
|
||||
|
||||
/**
|
||||
* shell登录鉴权
|
||||
*/
|
||||
void (API_CALL *on_mk_shell_login)(const char *user_name,
|
||||
const char *passwd,
|
||||
const mk_auth_invoker invoker,
|
||||
const mk_tcp_session sender);
|
||||
|
||||
/**
|
||||
* 停止rtsp/rtmp/http-flv会话后流量汇报事件广播
|
||||
* @param url_info 播放url相关信息
|
||||
* @param total_bytes 耗费上下行总流量,单位字节数
|
||||
* @param total_seconds 本次tcp会话时长,单位秒
|
||||
* @param is_player 客户端是否为播放器
|
||||
* @param peer_ip 客户端ip
|
||||
* @param peer_port 客户端端口号
|
||||
*/
|
||||
void (API_CALL *on_mk_flow_report)(const mk_media_info url_info,
|
||||
uint64_t total_bytes,
|
||||
uint64_t total_seconds,
|
||||
int is_player,
|
||||
const char *peer_ip,
|
||||
uint16_t peer_port);
|
||||
} mk_events;
|
||||
|
||||
|
||||
/**
|
||||
* 监听ZLMediaKit里面的事件
|
||||
* @param events 各个事件的结构体,这个对象在内部会再拷贝一次,可以设置为null以便取消监听
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_events_listen(const mk_events *events);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //MK_EVENTS_H
|
||||
|
323
api/include/mk_events_objects.h
Normal file
323
api/include/mk_events_objects.h
Normal file
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_EVENT_OBJECTS_H
|
||||
#define MK_EVENT_OBJECTS_H
|
||||
#include "mk_common.h"
|
||||
#include "mk_tcp.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
||||
//MP4Info对象的C映射
|
||||
typedef void* mk_mp4_info;
|
||||
//MP4Info::ui64StartedTime
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx);
|
||||
//MP4Info::ui64TimeLen
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx);
|
||||
//MP4Info::ui64FileSize
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx);
|
||||
//MP4Info::strFilePath
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx);
|
||||
//MP4Info::strFileName
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx);
|
||||
//MP4Info::strFolder
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx);
|
||||
//MP4Info::strUrl
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx);
|
||||
//MP4Info::strVhost
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx);
|
||||
//MP4Info::strAppName
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx);
|
||||
//MP4Info::strStreamId
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx);
|
||||
|
||||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||
//Parser对象的C映射
|
||||
typedef void* mk_parser;
|
||||
//Parser::Method(),获取命令字,譬如GET/POST
|
||||
API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx);
|
||||
//Parser::Url(),获取HTTP的访问url(不包括?后面的参数)
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx);
|
||||
//Parser::FullUrl(),包括?后面的参数
|
||||
API_EXPORT const char* API_CALL mk_parser_get_full_url(const mk_parser ctx);
|
||||
//Parser::Params(),?后面的参数字符串
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx);
|
||||
//Parser::getUrlArgs()["key"],获取?后面的参数中的特定参数
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key);
|
||||
//Parser::Tail(),获取协议相关信息,譬如 HTTP/1.1
|
||||
API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx);
|
||||
//Parser::getValues()["key"],获取HTTP头中特定字段
|
||||
API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key);
|
||||
//Parser::Content(),获取HTTP body
|
||||
API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, int *length);
|
||||
|
||||
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
|
||||
//MediaInfo对象的C映射
|
||||
typedef void* mk_media_info;
|
||||
//MediaInfo::_param_strs
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx);
|
||||
//MediaInfo::_schema
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx);
|
||||
//MediaInfo::_vhost
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx);
|
||||
//MediaInfo::_app
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx);
|
||||
//MediaInfo::_streamid
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx);
|
||||
|
||||
|
||||
///////////////////////////////////////////MediaSource/////////////////////////////////////////////
|
||||
//MediaSource对象的C映射
|
||||
typedef void* mk_media_source;
|
||||
//查找MediaSource的回调函数
|
||||
typedef void(API_CALL *on_mk_media_source_find_cb)(void *user_data, const mk_media_source ctx);
|
||||
|
||||
//MediaSource::getSchema()
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source ctx);
|
||||
//MediaSource::getVhost()
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx);
|
||||
//MediaSource::getApp()
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx);
|
||||
//MediaSource::getId()
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx);
|
||||
//MediaSource::readerCount()
|
||||
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx);
|
||||
//MediaSource::totalReaderCount()
|
||||
API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx);
|
||||
//MediaSource::close()
|
||||
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force);
|
||||
//MediaSource::seekTo()
|
||||
API_EXPORT int API_CALL mk_media_source_seek_to(const mk_media_source ctx,uint32_t stamp);
|
||||
|
||||
//MediaSource::find()
|
||||
API_EXPORT void API_CALL mk_media_source_find(const char *schema,
|
||||
const char *vhost,
|
||||
const char *app,
|
||||
const char *stream,
|
||||
void *user_data,
|
||||
on_mk_media_source_find_cb cb);
|
||||
//MediaSource::for_each_media()
|
||||
API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb);
|
||||
|
||||
///////////////////////////////////////////HttpBody/////////////////////////////////////////////
|
||||
//HttpBody对象的C映射
|
||||
typedef void* mk_http_body;
|
||||
/**
|
||||
* 生成HttpStringBody
|
||||
* @param str 字符串指针
|
||||
* @param len 字符串长度,为0则用strlen获取
|
||||
*/
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_string(const char *str,int len);
|
||||
|
||||
/**
|
||||
* 生成HttpFileBody
|
||||
* @param file_path 文件完整路径
|
||||
*/
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_file(const char *file_path);
|
||||
|
||||
/**
|
||||
* 生成HttpMultiFormBody
|
||||
* @param key_val 参数key-value
|
||||
* @param file_path 文件完整路径
|
||||
*/
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_multi_form(const char *key_val[],const char *file_path);
|
||||
|
||||
/**
|
||||
* 销毁HttpBody
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_body_release(mk_http_body ctx);
|
||||
|
||||
///////////////////////////////////////////HttpResponseInvoker/////////////////////////////////////////////
|
||||
//HttpSession::HttpResponseInvoker对象的C映射
|
||||
typedef void* mk_http_response_invoker;
|
||||
|
||||
/**
|
||||
* HttpSession::HttpResponseInvoker(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body);
|
||||
* @param response_code 譬如200 OK
|
||||
* @param response_header 返回的http头,譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾
|
||||
* @param response_body body对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_do(const mk_http_response_invoker ctx,
|
||||
const char *response_code,
|
||||
const char **response_header,
|
||||
const mk_http_body response_body);
|
||||
|
||||
/**
|
||||
* HttpSession::HttpResponseInvoker(const string &codeOut, const StrCaseMap &headerOut, const string &body);
|
||||
* @param response_code 譬如200 OK
|
||||
* @param response_header 返回的http头,譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾
|
||||
* @param response_content 返回的content部分,譬如一个网页内容
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_do_string(const mk_http_response_invoker ctx,
|
||||
const char *response_code,
|
||||
const char **response_header,
|
||||
const char *response_content);
|
||||
/**
|
||||
* HttpSession::HttpResponseInvoker(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath);
|
||||
* @param request_parser 请求事件中的mk_parser对象,用于提取其中http头中的Range字段,通过该字段先fseek然后再发送文件部分片段
|
||||
* @param response_header 返回的http头,譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾
|
||||
* @param response_file_path 返回的content部分,譬如/path/to/html/file
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_do_file(const mk_http_response_invoker ctx,
|
||||
const mk_parser request_parser,
|
||||
const char *response_header[],
|
||||
const char *response_file_path);
|
||||
/**
|
||||
* 克隆mk_http_response_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_http_response_invoker_do
|
||||
* 如果是同步执行mk_http_response_invoker_do,那么没必要克隆对象
|
||||
*/
|
||||
API_EXPORT mk_http_response_invoker API_CALL mk_http_response_invoker_clone(const mk_http_response_invoker ctx);
|
||||
|
||||
/**
|
||||
* 销毁堆上的克隆对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_clone_release(const mk_http_response_invoker ctx);
|
||||
|
||||
///////////////////////////////////////////HttpAccessPathInvoker/////////////////////////////////////////////
|
||||
//HttpSession::HttpAccessPathInvoker对象的C映射
|
||||
typedef void* mk_http_access_path_invoker;
|
||||
/**
|
||||
* HttpSession::HttpAccessPathInvoker(const string &errMsg,const string &accessPath, int cookieLifeSecond);
|
||||
* @param err_msg 如果为空,则代表鉴权通过,否则为错误提示,可以为null
|
||||
* @param access_path 运行或禁止访问的根目录,可以为null
|
||||
* @param cookie_life_second 鉴权cookie有效期
|
||||
**/
|
||||
API_EXPORT void API_CALL mk_http_access_path_invoker_do(const mk_http_access_path_invoker ctx,
|
||||
const char *err_msg,
|
||||
const char *access_path,
|
||||
int cookie_life_second);
|
||||
|
||||
/**
|
||||
* 克隆mk_http_access_path_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_http_access_path_invoker_do
|
||||
* 如果是同步执行mk_http_access_path_invoker_do,那么没必要克隆对象
|
||||
*/
|
||||
API_EXPORT mk_http_access_path_invoker API_CALL mk_http_access_path_invoker_clone(const mk_http_access_path_invoker ctx);
|
||||
|
||||
/**
|
||||
* 销毁堆上的克隆对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_access_path_invoker_clone_release(const mk_http_access_path_invoker ctx);
|
||||
|
||||
///////////////////////////////////////////RtspSession::onGetRealm/////////////////////////////////////////////
|
||||
//RtspSession::onGetRealm对象的C映射
|
||||
typedef void* mk_rtsp_get_realm_invoker;
|
||||
/**
|
||||
* 执行RtspSession::onGetRealm
|
||||
* @param realm 该rtsp流是否需要开启rtsp专属鉴权,至null或空字符串则不鉴权
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_do(const mk_rtsp_get_realm_invoker ctx,
|
||||
const char *realm);
|
||||
|
||||
/**
|
||||
* 克隆mk_rtsp_get_realm_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_rtsp_get_realm_invoker_do
|
||||
* 如果是同步执行mk_rtsp_get_realm_invoker_do,那么没必要克隆对象
|
||||
*/
|
||||
API_EXPORT mk_rtsp_get_realm_invoker API_CALL mk_rtsp_get_realm_invoker_clone(const mk_rtsp_get_realm_invoker ctx);
|
||||
|
||||
/**
|
||||
* 销毁堆上的克隆对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_clone_release(const mk_rtsp_get_realm_invoker ctx);
|
||||
|
||||
///////////////////////////////////////////RtspSession::onAuth/////////////////////////////////////////////
|
||||
//RtspSession::onAuth对象的C映射
|
||||
typedef void* mk_rtsp_auth_invoker;
|
||||
|
||||
/**
|
||||
* 执行RtspSession::onAuth
|
||||
* @param encrypted 为true是则表明是md5加密的密码,否则是明文密码, 在请求明文密码时如果提供md5密码者则会导致认证失败
|
||||
* @param pwd_or_md5 明文密码或者md5加密的密码
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_rtsp_auth_invoker_do(const mk_rtsp_auth_invoker ctx,
|
||||
int encrypted,
|
||||
const char *pwd_or_md5);
|
||||
|
||||
/**
|
||||
* 克隆mk_rtsp_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_rtsp_auth_invoker_do
|
||||
* 如果是同步执行mk_rtsp_auth_invoker_do,那么没必要克隆对象
|
||||
*/
|
||||
API_EXPORT mk_rtsp_auth_invoker API_CALL mk_rtsp_auth_invoker_clone(const mk_rtsp_auth_invoker ctx);
|
||||
|
||||
/**
|
||||
* 销毁堆上的克隆对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_rtsp_auth_invoker_clone_release(const mk_rtsp_auth_invoker ctx);
|
||||
|
||||
///////////////////////////////////////////Broadcast::PublishAuthInvoker/////////////////////////////////////////////
|
||||
//Broadcast::PublishAuthInvoker对象的C映射
|
||||
typedef void* mk_publish_auth_invoker;
|
||||
|
||||
/**
|
||||
* 执行Broadcast::PublishAuthInvoker
|
||||
* @param err_msg 为空或null则代表鉴权成功
|
||||
* @param enable_rtxp rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp
|
||||
* @param enable_hls 是否允许转换hls
|
||||
* @param enable_mp4 是否运行MP4录制
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
||||
const char *err_msg,
|
||||
int enable_rtxp,
|
||||
int enable_hls,
|
||||
int enable_mp4);
|
||||
|
||||
/**
|
||||
* 克隆mk_publish_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_publish_auth_invoker_do
|
||||
* 如果是同步执行mk_publish_auth_invoker_do,那么没必要克隆对象
|
||||
*/
|
||||
API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx);
|
||||
|
||||
/**
|
||||
* 销毁堆上的克隆对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_clone_release(const mk_publish_auth_invoker ctx);
|
||||
|
||||
///////////////////////////////////////////Broadcast::AuthInvoker/////////////////////////////////////////////
|
||||
//Broadcast::AuthInvoker对象的C映射
|
||||
typedef void* mk_auth_invoker;
|
||||
|
||||
/**
|
||||
* 执行Broadcast::AuthInvoker
|
||||
* @param err_msg 为空或null则代表鉴权成功
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_auth_invoker_do(const mk_auth_invoker ctx, const char *err_msg);
|
||||
|
||||
/**
|
||||
* 克隆mk_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_auth_invoker_do
|
||||
* 如果是同步执行mk_auth_invoker_do,那么没必要克隆对象
|
||||
*/
|
||||
API_EXPORT mk_auth_invoker API_CALL mk_auth_invoker_clone(const mk_auth_invoker ctx);
|
||||
|
||||
/**
|
||||
* 销毁堆上的克隆对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_auth_invoker_clone_release(const mk_auth_invoker ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //MK_EVENT_OBJECTS_H
|
174
api/include/mk_httpclient.h
Executable file
174
api/include/mk_httpclient.h
Executable file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_HTTPCLIENT_H_
|
||||
#define MK_HTTPCLIENT_H_
|
||||
|
||||
#include "mk_common.h"
|
||||
#include "mk_events_objects.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////HttpDownloader/////////////////////////////////////////////
|
||||
|
||||
typedef void *mk_http_downloader;
|
||||
|
||||
/**
|
||||
* @param user_data 用户数据指针
|
||||
* @param code 错误代码,0代表成功
|
||||
* @param err_msg 错误提示
|
||||
* @param file_path 文件保存路径
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_download_complete)(void *user_data, int code, const char *err_msg, const char *file_path);
|
||||
|
||||
/**
|
||||
* 创建http[s]下载器
|
||||
* @return 下载器指针
|
||||
*/
|
||||
API_EXPORT mk_http_downloader API_CALL mk_http_downloader_create();
|
||||
|
||||
/**
|
||||
* 销毁http[s]下载器
|
||||
* @param ctx 下载器指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_downloader_release(mk_http_downloader ctx);
|
||||
|
||||
/**
|
||||
* 开始http[s]下载
|
||||
* @param ctx 下载器指针
|
||||
* @param url http[s]下载url
|
||||
* @param file 文件保存路径
|
||||
* @param cb 回调函数
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_downloader_start(mk_http_downloader ctx, const char *url, const char *file, on_mk_download_complete cb, void *user_data);
|
||||
|
||||
|
||||
///////////////////////////////////////////HttpRequester/////////////////////////////////////////////
|
||||
typedef void *mk_http_requester;
|
||||
|
||||
/**
|
||||
* http请求结果回调
|
||||
* 在code == 0时代表本次http会话是完整的(收到了http回复)
|
||||
* 用户应该通过user_data获取到mk_http_requester对象
|
||||
* 然后通过mk_http_requester_get_response等函数获取相关回复数据
|
||||
* 在回调结束时,应该通过mk_http_requester_release函数销毁该对象
|
||||
* 或者调用mk_http_requester_clear函数后再复用该对象
|
||||
* @param user_data 用户数据指针
|
||||
* @param code 错误代码,0代表成功
|
||||
* @param err_msg 错误提示
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_http_requester_complete)(void *user_data, int code, const char *err_msg);
|
||||
|
||||
/**
|
||||
* 创建HttpRequester
|
||||
*/
|
||||
API_EXPORT mk_http_requester API_CALL mk_http_requester_create();
|
||||
|
||||
/**
|
||||
* 在复用mk_http_requester对象时才需要用到此方法
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_clear(mk_http_requester ctx);
|
||||
|
||||
/**
|
||||
* 销毁HttpRequester
|
||||
* 如果调用了mk_http_requester_start函数且正在等待http回复,
|
||||
* 也可以调用mk_http_requester_release方法取消本次http请求
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_release(mk_http_requester ctx);
|
||||
|
||||
/**
|
||||
* 设置HTTP方法,譬如GET/POST
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_set_method(mk_http_requester ctx,const char *method);
|
||||
|
||||
/**
|
||||
* 批量设置设置HTTP头
|
||||
* @param header 譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_set_header(mk_http_requester ctx, const char *header[]);
|
||||
|
||||
/**
|
||||
* 添加HTTP头
|
||||
* @param key 譬如Content-Type
|
||||
* @param value 譬如 text/html
|
||||
* @param force 如果已经存在该key,是否强制替换
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,const char *key,const char *value,int force);
|
||||
|
||||
/**
|
||||
* 设置消息体,
|
||||
* @param body mk_http_body对象,通过mk_http_body_from_string等函数生成,使用完毕后请调用mk_http_body_release释放之
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_set_body(mk_http_requester ctx, mk_http_body body);
|
||||
|
||||
/**
|
||||
* 在收到HTTP回复后可调用该方法获取状态码
|
||||
* @return 譬如 200 OK
|
||||
*/
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx);
|
||||
|
||||
/**
|
||||
* 在收到HTTP回复后可调用该方法获取响应HTTP头
|
||||
* @param key HTTP头键名
|
||||
* @return HTTP头键值
|
||||
*/
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key);
|
||||
|
||||
/**
|
||||
* 在收到HTTP回复后可调用该方法获取响应HTTP body
|
||||
* @param length 返回body长度,可以为null
|
||||
* @return body指针
|
||||
*/
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requester ctx, int *length);
|
||||
|
||||
/**
|
||||
* 在收到HTTP回复后可调用该方法获取响应
|
||||
* @return 响应对象
|
||||
*/
|
||||
API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx);
|
||||
|
||||
/**
|
||||
* 设置回调函数
|
||||
* @param cb 回调函数,不能为空
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_set_cb(mk_http_requester ctx,on_mk_http_requester_complete cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 开始url请求
|
||||
* @param url 请求url,支持http/https
|
||||
* @param timeout_second 最大超时时间
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_http_requester_start(mk_http_requester ctx,const char *url, float timeout_second);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MK_HTTPCLIENT_H_ */
|
161
api/include/mk_media.h
Executable file
161
api/include/mk_media.h
Executable file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_MEDIA_H_
|
||||
#define MK_MEDIA_H_
|
||||
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *mk_media;
|
||||
|
||||
/**
|
||||
* 创建一个媒体源
|
||||
* @param vhost 虚拟主机名,一般为__defaultVhost__
|
||||
* @param app 应用名,推荐为live
|
||||
* @param stream 流id,例如camera
|
||||
* @param duration 时长(单位秒),直播则为0
|
||||
* @param hls_enabled 是否生成hls
|
||||
* @param mp4_enabled 是否生成mp4
|
||||
* @return 对象指针
|
||||
*/
|
||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration, int hls_enabled, int mp4_enabled);
|
||||
|
||||
/**
|
||||
* 销毁媒体源
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_release(mk_media ctx);
|
||||
|
||||
/**
|
||||
* 添加h264视频轨道
|
||||
* @param ctx 对象指针
|
||||
* @param width 视频宽度
|
||||
* @param height 视频高度
|
||||
* @param fps 视频fps
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_init_h264(mk_media ctx, int width, int height, int fps);
|
||||
|
||||
/**
|
||||
* 添加h265视频轨道
|
||||
* @param ctx 对象指针
|
||||
* @param width 视频宽度
|
||||
* @param height 视频高度
|
||||
* @param fps 视频fps
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_init_h265(mk_media ctx, int width, int height, int fps);
|
||||
|
||||
/**
|
||||
* 添加aac音频轨道
|
||||
* @param ctx 对象指针
|
||||
* @param channel 通道数
|
||||
* @param sample_bit 采样位数,只支持16
|
||||
* @param sample_rate 采样率
|
||||
* @param profile aac编码profile,在不输入adts头时用于生产adts头
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_init_aac(mk_media ctx, int channel, int sample_bit, int sample_rate, int profile);
|
||||
|
||||
/**
|
||||
* 初始化h264/h265/aac完毕后调用此函数,
|
||||
* 在单track(只有音频或视频)时,因为ZLMediaKit不知道后续是否还要添加track,所以会多等待3秒钟
|
||||
* 如果产生的流是单Track类型,请调用此函数以便加快流生成速度,当然不调用该函数,影响也不大(会多等待3秒)
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_init_complete(mk_media ctx);
|
||||
|
||||
/**
|
||||
* 输入单帧H264视频,帧起始字节00 00 01,00 00 00 01均可
|
||||
* @param ctx 对象指针
|
||||
* @param data 单帧H264数据
|
||||
* @param len 单帧H264数据字节数
|
||||
* @param dts 解码时间戳,单位毫秒
|
||||
* @param pts 播放时间戳,单位毫秒
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts);
|
||||
|
||||
/**
|
||||
* 输入单帧H265视频,帧起始字节00 00 01,00 00 00 01均可
|
||||
* @param ctx 对象指针
|
||||
* @param data 单帧H265数据
|
||||
* @param len 单帧H265数据字节数
|
||||
* @param dts 解码时间戳,单位毫秒
|
||||
* @param pts 播放时间戳,单位毫秒
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_input_h265(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts);
|
||||
|
||||
/**
|
||||
* 输入单帧AAC音频
|
||||
* @param ctx 对象指针
|
||||
* @param data 单帧AAC数据
|
||||
* @param len 单帧AAC数据字节数
|
||||
* @param dts 时间戳,毫秒
|
||||
* @param with_adts_header data中是否包含7个字节的adts头
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_input_aac(mk_media ctx, void *data, int len, uint32_t dts, int with_adts_header);
|
||||
|
||||
/**
|
||||
* 输入单帧AAC音频(单独指定adts头)
|
||||
* @param ctx 对象指针
|
||||
* @param data 不包含adts头的单帧AAC数据
|
||||
* @param len 单帧AAC数据字节数
|
||||
* @param dts 时间戳,毫秒
|
||||
* @param adts adts头
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_input_aac1(mk_media ctx, void *data, int len, uint32_t dts, void *adts);
|
||||
|
||||
/**
|
||||
* 在调用对应的MediaSource.close()时会触发该回调
|
||||
* 你可以在该事件中做清理工作(比如说关闭摄像头,同时调用mk_media_release函数销毁该对象)
|
||||
* @param user_data 用户数据指针,通过mk_media_set_on_close函数设置
|
||||
* @return 返回0告知事件触发者关闭媒体失败,非0代表成功
|
||||
*/
|
||||
typedef int(API_CALL *on_mk_media_close)(void *user_data);
|
||||
|
||||
/**
|
||||
* 监听MediaSource.close()事件
|
||||
* 在选择关闭一个MediaSource时,将会最终触发到该回调
|
||||
* 你可以通过该事件选择删除对象,当然你在该事件中也可以什么都不做
|
||||
* @param ctx 对象指针
|
||||
* @param cb 回调指针
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_set_on_close(mk_media ctx, on_mk_media_close cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 获取总的观看人数
|
||||
* @param ctx 对象指针
|
||||
* @return 观看人数
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_media_total_reader_count(mk_media ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MK_MEDIA_H_ */
|
42
api/include/mk_mediakit.h
Executable file
42
api/include/mk_mediakit.h
Executable file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_API_H_
|
||||
#define MK_API_H_
|
||||
|
||||
#include "mk_common.h"
|
||||
#include "mk_httpclient.h"
|
||||
#include "mk_media.h"
|
||||
#include "mk_proxyplayer.h"
|
||||
#include "mk_recorder.h"
|
||||
#include "mk_player.h"
|
||||
#include "mk_pusher.h"
|
||||
#include "mk_events.h"
|
||||
#include "mk_tcp.h"
|
||||
#include "mk_util.h"
|
||||
#include "mk_thread.h"
|
||||
|
||||
#endif /* MK_API_H_ */
|
176
api/include/mk_player.h
Executable file
176
api/include/mk_player.h
Executable file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_PLAYER_H_
|
||||
#define MK_PLAYER_H_
|
||||
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* mk_player;
|
||||
|
||||
/**
|
||||
* 播放结果或播放中断事件的回调
|
||||
* @param user_data 用户数据指针
|
||||
* @param err_code 错误代码,0为成功
|
||||
* @param err_msg 错误提示
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_play_event)(void *user_data,int err_code,const char *err_msg);
|
||||
|
||||
/**
|
||||
* 收到音视频数据回调
|
||||
* @param user_data 用户数据指针
|
||||
* @param track_type 0:视频,1:音频
|
||||
* @param codec_id 0:H264,1:H265,2:AAC
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
* @param dts 解码时间戳,单位毫秒
|
||||
* @param pts 显示时间戳,单位毫秒
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_play_data)(void *user_data,int track_type,int codec_id,void *data,int len,uint32_t dts,uint32_t pts);
|
||||
|
||||
/**
|
||||
* 创建一个播放器,支持rtmp[s]/rtsp[s]
|
||||
* @return 播放器指针
|
||||
*/
|
||||
API_EXPORT mk_player API_CALL mk_player_create();
|
||||
|
||||
/**
|
||||
* 销毁播放器
|
||||
* @param ctx 播放器指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_release(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 设置播放器配置选项
|
||||
* @param ctx 播放器指针
|
||||
* @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/max_analysis_ms
|
||||
* @param val 配置项值,如果是整形,需要转换成统一转换成string
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_set_option(mk_player ctx, const char *key, const char *val);
|
||||
|
||||
/**
|
||||
* 开始播放url
|
||||
* @param ctx 播放器指针
|
||||
* @param url rtsp[s]/rtmp[s] url
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_play(mk_player ctx, const char *url);
|
||||
|
||||
/**
|
||||
* 暂停或恢复播放,仅对点播有用
|
||||
* @param ctx 播放器指针
|
||||
* @param pause 1:暂停播放,0:恢复播放
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_pause(mk_player ctx, int pause);
|
||||
|
||||
/**
|
||||
* 设置点播进度条
|
||||
* @param ctx 对象指针
|
||||
* @param progress 取值范围未 0.0~1.0
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress);
|
||||
|
||||
/**
|
||||
* 设置播放器开启播放结果回调函数
|
||||
* @param ctx 播放器指针
|
||||
* @param cb 回调函数指针,不得为null
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_set_on_result(mk_player ctx, on_mk_play_event cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 设置播放被异常中断的回调
|
||||
* @param ctx 播放器指针
|
||||
* @param cb 回调函数指针,不得为null
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_event cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 设置音视频数据回调函数
|
||||
* 该接口只能在播放成功事件触发后才能调用
|
||||
* @param ctx 播放器指针
|
||||
* @param cb 回调函数指针,不得为null
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 获取视频宽度
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_player_video_width(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取视频高度
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_player_video_height(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取视频帧率
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_player_video_fps(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取音频采样率
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_player_audio_samplerate(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取音频采样位数,一般为16
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_player_audio_bit(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取音频通道数
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_player_audio_channel(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取点播节目时长,如果是直播返回0,否则返回秒数
|
||||
*/
|
||||
API_EXPORT float API_CALL mk_player_duration(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取点播播放进度,取值范围未 0.0~1.0
|
||||
*/
|
||||
API_EXPORT float API_CALL mk_player_progress(mk_player ctx);
|
||||
|
||||
/**
|
||||
* 获取丢包率,rtsp时有效
|
||||
* @param ctx 对象指针
|
||||
* @param track_type 0:视频,1:音频
|
||||
*/
|
||||
API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MK_PLAYER_H_ */
|
75
api/include/mk_proxyplayer.h
Normal file
75
api/include/mk_proxyplayer.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_PROXY_PLAYER_H_
|
||||
#define MK_PROXY_PLAYER_H_
|
||||
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *mk_proxy_player;
|
||||
|
||||
/**
|
||||
* 创建一个代理播放器
|
||||
* @param vhost 虚拟主机名,一般为__defaultVhost__
|
||||
* @param app 应用名
|
||||
* @param stream 流名
|
||||
* @param rtp_type rtsp播放方式:RTP_TCP = 0, RTP_UDP = 1, RTP_MULTICAST = 2
|
||||
* @param hls_enabled 是否生成hls
|
||||
* @param mp4_enabled 是否生成mp4
|
||||
* @return 对象指针
|
||||
*/
|
||||
API_EXPORT mk_proxy_player API_CALL mk_proxy_player_create(const char *vhost, const char *app, const char *stream, int hls_enabled, int mp4_enabled);
|
||||
|
||||
/**
|
||||
* 销毁代理播放器
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_proxy_player_release(mk_proxy_player ctx);
|
||||
|
||||
/**
|
||||
* 设置代理播放器配置选项
|
||||
* @param ctx 代理播放器指针
|
||||
* @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/max_analysis_ms
|
||||
* @param val 配置项值,如果是整形,需要转换成统一转换成string
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_option(mk_proxy_player ctx, const char *key, const char *val);
|
||||
|
||||
/**
|
||||
* 开始播放
|
||||
* @param ctx 对象指针
|
||||
* @param url 播放url,支持rtsp/rtmp
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_proxy_player_play(mk_proxy_player ctx, const char *url);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MK_PROXY_PLAYER_H_ */
|
100
api/include/mk_pusher.h
Normal file
100
api/include/mk_pusher.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MK_PUSHER_H
|
||||
#define MK_PUSHER_H
|
||||
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* mk_pusher;
|
||||
|
||||
/**
|
||||
* 推流结果或推流中断事件的回调
|
||||
* @param user_data 用户数据指针
|
||||
* @param err_code 错误代码,0为成功
|
||||
* @param err_msg 错误提示
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_push_event)(void *user_data,int err_code,const char *err_msg);
|
||||
|
||||
/**
|
||||
* 绑定的MediaSource对象并创建rtmp[s]/rtsp[s]推流器
|
||||
* MediaSource通过mk_media_create或mk_proxy_player_create生成
|
||||
* 该MediaSource对象必须已注册
|
||||
*
|
||||
* @param schema 绑定的MediaSource对象所属协议,支持rtsp/rtmp
|
||||
* @param vhost 绑定的MediaSource对象的虚拟主机,一般为__defaultVhost__
|
||||
* @param app 绑定的MediaSource对象的应用名,一般为live
|
||||
* @param stream 绑定的MediaSource对象的流id
|
||||
* @return 对象指针
|
||||
*/
|
||||
API_EXPORT mk_pusher API_CALL mk_pusher_create(const char *schema,const char *vhost,const char *app, const char *stream);
|
||||
|
||||
/**
|
||||
* 释放推流器
|
||||
* @param ctx 推流器指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_pusher_release(mk_pusher ctx);
|
||||
|
||||
/**
|
||||
* 设置推流器配置选项
|
||||
* @param ctx 推流器指针
|
||||
* @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/max_analysis_ms
|
||||
* @param val 配置项值,如果是整形,需要转换成统一转换成string
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_pusher_set_option(mk_pusher ctx, const char *key, const char *val);
|
||||
|
||||
/**
|
||||
* 开始推流
|
||||
* @param ctx 推流器指针
|
||||
* @param url 推流地址,支持rtsp[s]/rtmp[s]
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_pusher_publish(mk_pusher ctx,const char *url);
|
||||
|
||||
/**
|
||||
* 设置推流器推流结果回调函数
|
||||
* @param ctx 推流器指针
|
||||
* @param cb 回调函数指针,不得为null
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_pusher_set_on_result(mk_pusher ctx, on_mk_push_event cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 设置推流被异常中断的回调
|
||||
* @param ctx 推流器指针
|
||||
* @param cb 回调函数指针,不得为null
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_pusher_set_on_shutdown(mk_pusher ctx, on_mk_push_event cb, void *user_data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //MK_PUSHER_H
|
108
api/include/mk_recorder.h
Normal file
108
api/include/mk_recorder.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_RECORDER_API_H_
|
||||
#define MK_RECORDER_API_H_
|
||||
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////flv录制/////////////////////////////////////////////
|
||||
|
||||
typedef void* mk_flv_recorder;
|
||||
|
||||
/**
|
||||
* 创建flv录制器
|
||||
* @return
|
||||
*/
|
||||
API_EXPORT mk_flv_recorder API_CALL mk_flv_recorder_create();
|
||||
|
||||
/**
|
||||
* 释放flv录制器
|
||||
* @param ctx
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_flv_recorder_release(mk_flv_recorder ctx);
|
||||
|
||||
/**
|
||||
* 开始录制flv
|
||||
* @param ctx flv录制器
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 绑定的RtmpMediaSource的 app名
|
||||
* @param stream 绑定的RtmpMediaSource的 stream名
|
||||
* @param file_path 文件存放地址
|
||||
* @return 0:开始超过,-1:失败,打开文件失败或该RtmpMediaSource不存在
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *vhost, const char *app, const char *stream, const char *file_path);
|
||||
|
||||
|
||||
///////////////////////////////////////////hls/mp4录制/////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 获取录制状态
|
||||
* @param type 0:hls,1:MP4
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 应用名
|
||||
* @param stream 流id
|
||||
* @return 录制状态,0:未录制,1:等待MediaSource注册,注册成功后立即开始录制,2:MediaSource已注册,并且正在录制
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_recorder_status(int type, const char *vhost, const char *app, const char *stream);
|
||||
|
||||
/**
|
||||
* 开始录制
|
||||
* @param type 0:hls,1:MP4
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 应用名
|
||||
* @param stream 流id
|
||||
* @param customized_path 录像文件保存自定义目录,默认为空或null则自动生成
|
||||
* @param wait_for_record 是否等待流注册后再录制,未注册时,置false将返回失败
|
||||
* @param continue_record 流注销时是否继续等待录制还是立即停止录制
|
||||
* @return 0代表成功,负数代表失败
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const char *app, const char *stream, const char *customized_path, int wait_for_record, int continue_record);
|
||||
|
||||
/**
|
||||
* 停止录制
|
||||
* @param type 0:hls,1:MP4
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 应用名
|
||||
* @param stream 流id
|
||||
* @return 1:成功,0:失败
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_recorder_stop(int type, const char *vhost, const char *app, const char *stream);
|
||||
|
||||
/**
|
||||
* 停止所有录制,一般程序退出时调用
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_recorder_stop_all();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MK_RECORDER_API_H_ */
|
226
api/include/mk_tcp.h
Normal file
226
api/include/mk_tcp.h
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_TCP_H
|
||||
#define MK_TCP_H
|
||||
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////TcpSession/////////////////////////////////////////////
|
||||
//TcpSession对象的C映射
|
||||
typedef void* mk_tcp_session;
|
||||
//TcpSession::safeShutdown()
|
||||
API_EXPORT void API_CALL mk_tcp_session_shutdown(const mk_tcp_session ctx,int err,const char *err_msg);
|
||||
//TcpSession::get_peer_ip()
|
||||
API_EXPORT const char* API_CALL mk_tcp_session_peer_ip(const mk_tcp_session ctx);
|
||||
//TcpSession::get_local_ip()
|
||||
API_EXPORT const char* API_CALL mk_tcp_session_local_ip(const mk_tcp_session ctx);
|
||||
//TcpSession::get_peer_port()
|
||||
API_EXPORT uint16_t API_CALL mk_tcp_session_peer_port(const mk_tcp_session ctx);
|
||||
//TcpSession::get_local_port()
|
||||
API_EXPORT uint16_t API_CALL mk_tcp_session_local_port(const mk_tcp_session ctx);
|
||||
//TcpSession::send()
|
||||
API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx,const char *data,int len);
|
||||
//切换到该对象所在线程后再TcpSession::send()
|
||||
API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx,const char *data,int len);
|
||||
|
||||
///////////////////////////////////////////自定义tcp服务/////////////////////////////////////////////
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* 收到mk_tcp_session创建对象
|
||||
* @param server_port 服务器端口号
|
||||
* @param session 会话处理对象
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_session_create)(uint16_t server_port,mk_tcp_session session);
|
||||
|
||||
/**
|
||||
* 收到客户端发过来的数据
|
||||
* @param server_port 服务器端口号
|
||||
* @param session 会话处理对象
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_session_data)(uint16_t server_port,mk_tcp_session session,const char *data,int len);
|
||||
|
||||
/**
|
||||
* 每隔2秒的定时器,用于管理超时等任务
|
||||
* @param server_port 服务器端口号
|
||||
* @param session 会话处理对象
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_session_manager)(uint16_t server_port,mk_tcp_session session);
|
||||
|
||||
/**
|
||||
* 一般由于客户端断开tcp触发
|
||||
* @param server_port 服务器端口号
|
||||
* @param session 会话处理对象
|
||||
* @param code 错误代码
|
||||
* @param msg 错误提示
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_session_disconnect)(uint16_t server_port,mk_tcp_session session,int code,const char *msg);
|
||||
} mk_tcp_session_events;
|
||||
|
||||
|
||||
typedef enum {
|
||||
//普通的tcp
|
||||
mk_type_tcp = 0,
|
||||
//ssl类型的tcp
|
||||
mk_type_ssl = 1,
|
||||
//基于websocket的连接
|
||||
mk_type_ws = 2,
|
||||
//基于ssl websocket的连接
|
||||
mk_type_wss = 3
|
||||
}mk_tcp_type;
|
||||
|
||||
/**
|
||||
* tcp会话对象附着用户数据
|
||||
* 该函数只对mk_tcp_server_server_start启动的服务类型有效
|
||||
* @param session 会话对象
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_session_set_user_data(mk_tcp_session session,void *user_data);
|
||||
|
||||
/**
|
||||
* 获取tcp会话对象上附着的用户数据
|
||||
* 该函数只对mk_tcp_server_server_start启动的服务类型有效
|
||||
* @param session tcp会话对象
|
||||
* @return 用户数据指针
|
||||
*/
|
||||
API_EXPORT void* API_CALL mk_tcp_session_get_user_data(mk_tcp_session session);
|
||||
|
||||
/**
|
||||
* 开启tcp服务器
|
||||
* @param port 监听端口号,0则为随机
|
||||
* @param type 服务器类型
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_tcp_server_start(uint16_t port, mk_tcp_type type);
|
||||
|
||||
/**
|
||||
* 监听tcp服务器事件
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_server_events_listen(const mk_tcp_session_events *events);
|
||||
|
||||
|
||||
///////////////////////////////////////////自定义tcp客户端/////////////////////////////////////////////
|
||||
|
||||
typedef void* mk_tcp_client;
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* tcp客户端连接服务器成功或失败回调
|
||||
* @param client tcp客户端
|
||||
* @param code 0为连接成功,否则为失败原因
|
||||
* @param msg 连接失败错误提示
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_client_connect)(mk_tcp_client client,int code,const char *msg);
|
||||
|
||||
/**
|
||||
* tcp客户端与tcp服务器之间断开回调
|
||||
* 一般是eof事件导致
|
||||
* @param client tcp客户端
|
||||
* @param code 错误代码
|
||||
* @param msg 错误提示
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_client_disconnect)(mk_tcp_client client,int code,const char *msg);
|
||||
|
||||
/**
|
||||
* 收到tcp服务器发来的数据
|
||||
* @param client tcp客户端
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_client_data)(mk_tcp_client client,const char *data,int len);
|
||||
|
||||
/**
|
||||
* 每隔2秒的定时器,用于管理超时等任务
|
||||
* @param client tcp客户端
|
||||
*/
|
||||
void (API_CALL *on_mk_tcp_client_manager)(mk_tcp_client client);
|
||||
} mk_tcp_client_events;
|
||||
|
||||
/**
|
||||
* 创建tcp客户端
|
||||
* @param events 回调函数结构体
|
||||
* @param user_data 用户数据指针
|
||||
* @param type 客户端类型
|
||||
* @return 客户端对象
|
||||
*/
|
||||
API_EXPORT mk_tcp_client API_CALL mk_tcp_client_create(mk_tcp_client_events *events, mk_tcp_type type);
|
||||
|
||||
/**
|
||||
* 释放tcp客户端
|
||||
* @param ctx 客户端对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_client_release(mk_tcp_client ctx);
|
||||
|
||||
/**
|
||||
* 发起连接
|
||||
* @param ctx 客户端对象
|
||||
* @param host 服务器ip或域名
|
||||
* @param port 服务器端口号
|
||||
* @param time_out_sec 超时时间
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_client_connect(mk_tcp_client ctx, const char *host, uint16_t port, float time_out_sec);
|
||||
|
||||
/**
|
||||
* 非线程安全的发送数据
|
||||
* 开发者如果能确保在本对象网络线程内,可以调用此此函数
|
||||
* @param ctx 客户端对象
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度,等于0时,内部通过strlen获取
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_client_send(mk_tcp_client ctx, const char *data, int len);
|
||||
|
||||
/**
|
||||
* 切换到本对象的网络线程后再发送数据
|
||||
* @param ctx 客户端对象
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度,等于0时,内部通过strlen获取
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_client_send_safe(mk_tcp_client ctx, const char *data, int len);
|
||||
|
||||
/**
|
||||
* 客户端附着用户数据
|
||||
* @param ctx 客户端对象
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_tcp_client_set_user_data(mk_tcp_client ctx,void *user_data);
|
||||
|
||||
/**
|
||||
* 获取客户端对象上附着的用户数据
|
||||
* @param ctx 客户端对象
|
||||
* @return 用户数据指针
|
||||
*/
|
||||
API_EXPORT void* API_CALL mk_tcp_client_get_user_data(mk_tcp_client ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //MK_TCP_H
|
102
api/include/mk_thread.h
Normal file
102
api/include/mk_thread.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_THREAD_H
|
||||
#define MK_THREAD_H
|
||||
|
||||
#include <assert.h>
|
||||
#include "mk_common.h"
|
||||
#include "mk_tcp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////事件线程/////////////////////////////////////////////
|
||||
typedef void* mk_thread;
|
||||
|
||||
/**
|
||||
* 获取tcp会话对象所在事件线程
|
||||
* @param ctx tcp会话对象
|
||||
* @return 对象所在事件线程
|
||||
*/
|
||||
API_EXPORT mk_thread API_CALL mk_thread_from_tcp_session(mk_tcp_session ctx);
|
||||
|
||||
/**
|
||||
* 获取tcp客户端对象所在事件线程
|
||||
* @param ctx tcp客户端
|
||||
* @return 对象所在事件线程
|
||||
*/
|
||||
API_EXPORT mk_thread API_CALL mk_thread_from_tcp_client(mk_tcp_client ctx);
|
||||
|
||||
///////////////////////////////////////////线程切换/////////////////////////////////////////////
|
||||
typedef void (API_CALL *on_mk_async)(void *user_data);
|
||||
|
||||
/**
|
||||
* 切换到事件线程并异步执行
|
||||
* @param ctx 事件线程
|
||||
* @param cb 回调函数
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_async_do(mk_thread ctx,on_mk_async cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 切换到事件线程并同步执行
|
||||
* @param ctx 事件线程
|
||||
* @param cb 回调函数
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_sync_do(mk_thread ctx,on_mk_async cb, void *user_data);
|
||||
|
||||
///////////////////////////////////////////定时器/////////////////////////////////////////////
|
||||
typedef void* mk_timer;
|
||||
|
||||
/**
|
||||
* 定时器触发事件
|
||||
* @return 下一次触发延时(单位毫秒),返回0则不再重复
|
||||
*/
|
||||
typedef uint64_t (API_CALL *on_mk_timer)(void *user_data);
|
||||
|
||||
/**
|
||||
* 创建定时器
|
||||
* @param ctx 线程对象
|
||||
* @param delay_ms 执行延时,单位毫秒
|
||||
* @param cb 回调函数
|
||||
* @param user_data 用户数据指针
|
||||
* @return 定时器对象
|
||||
*/
|
||||
API_EXPORT mk_timer API_CALL mk_timer_create(mk_thread ctx,uint64_t delay_ms, on_mk_timer cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 销毁和取消定时器
|
||||
* @param ctx 定时器对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_timer_release(mk_timer ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //MK_THREAD_H
|
94
api/include/mk_util.h
Normal file
94
api/include/mk_util.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_UTIL_H
|
||||
#define MK_UTIL_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 获取本程序可执行文件路径
|
||||
* @return 文件路径,使用完后需要自己free
|
||||
*/
|
||||
API_EXPORT char* API_CALL mk_util_get_exe_path();
|
||||
|
||||
/**
|
||||
* 获取本程序可执行文件相同目录下文件的绝对路径
|
||||
* @param relative_path 同目录下文件的路径相对,可以为null
|
||||
* @return 文件路径,使用完后需要自己free
|
||||
*/
|
||||
API_EXPORT char* API_CALL mk_util_get_exe_dir(const char *relative_path);
|
||||
|
||||
/**
|
||||
* 获取unix标准的系统时间戳
|
||||
* @return 当前系统时间戳
|
||||
*/
|
||||
API_EXPORT uint64_t API_CALL mk_util_get_current_millisecond();
|
||||
|
||||
/**
|
||||
* 获取时间字符串
|
||||
* @param fmt 时间格式,譬如%Y-%m-%d %H:%M:%S
|
||||
* @return 时间字符串,使用完后需要自己free
|
||||
*/
|
||||
API_EXPORT char* API_CALL mk_util_get_current_time_string(const char *fmt);
|
||||
|
||||
/**
|
||||
* 打印二进制为字符串
|
||||
* @param buf 二进制数据
|
||||
* @param len 数据长度
|
||||
* @return 可打印的调试信息,使用完后需要自己free
|
||||
*/
|
||||
API_EXPORT char* API_CALL mk_util_hex_dump(const void *buf, int len);
|
||||
///////////////////////////////////////////日志/////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 打印日志
|
||||
* @param level 日志级别,支持0~4
|
||||
* @param file __FILE__
|
||||
* @param function __FUNCTION__
|
||||
* @param line __LINE__
|
||||
* @param fmt printf类型的格式控制字符串
|
||||
* @param ... 不定长参数
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_log_printf(int level, const char *file, const char *function, int line, const char *fmt, ...);
|
||||
|
||||
// 以下宏可以替换printf使用
|
||||
#define log_trace(fmt,...) mk_log_printf(0,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__)
|
||||
#define log_debug(fmt,...) mk_log_printf(1,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__)
|
||||
#define log_info(fmt,...) mk_log_printf(2,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__)
|
||||
#define log_warn(fmt,...) mk_log_printf(3,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__)
|
||||
#define log_error(fmt,...) mk_log_printf(4,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__)
|
||||
#define log_printf(lev,fmt,...) mk_log_printf(lev,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //MK_UTIL_H
|
210
api/source/mk_common.cpp
Executable file
210
api/source/mk_common.cpp
Executable file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_common.h"
|
||||
#include <stdarg.h>
|
||||
#include <unordered_map>
|
||||
#include "Util/logger.h"
|
||||
#include "Util/SSLBox.h"
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
#include "Rtsp/RtspSession.h"
|
||||
#include "Rtmp/RtmpSession.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Shell/ShellSession.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
static TcpServer::Ptr rtsp_server[2];
|
||||
static TcpServer::Ptr rtmp_server[2];
|
||||
static TcpServer::Ptr http_server[2];
|
||||
static TcpServer::Ptr shell_server;
|
||||
|
||||
#ifdef ENABLE_RTPPROXY
|
||||
#include "Rtp/UdpRecver.h"
|
||||
#include "Rtp/RtpSession.h"
|
||||
static std::shared_ptr<UdpRecver> udpRtpServer;
|
||||
static TcpServer::Ptr tcpRtpServer;
|
||||
#endif
|
||||
|
||||
//////////////////////////environment init///////////////////////////
|
||||
API_EXPORT void API_CALL mk_env_init(const mk_config *cfg) {
|
||||
assert(cfg);
|
||||
mk_env_init1(cfg->thread_num,
|
||||
cfg->log_level,
|
||||
cfg->ini_is_path,
|
||||
cfg->ini,
|
||||
cfg->ssl_is_path,
|
||||
cfg->ssl,
|
||||
cfg->ssl_pwd);
|
||||
}
|
||||
|
||||
extern void stopAllTcpServer();
|
||||
|
||||
API_EXPORT void API_CALL mk_stop_all_server(){
|
||||
CLEAR_ARR(rtsp_server);
|
||||
CLEAR_ARR(rtmp_server);
|
||||
CLEAR_ARR(http_server);
|
||||
#ifdef ENABLE_RTPPROXY
|
||||
udpRtpServer = nullptr;
|
||||
tcpRtpServer = nullptr;
|
||||
#endif
|
||||
stopAllTcpServer();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_env_init1( int thread_num,
|
||||
int log_level,
|
||||
int ini_is_path,
|
||||
const char *ini,
|
||||
int ssl_is_path,
|
||||
const char *ssl,
|
||||
const char *ssl_pwd) {
|
||||
|
||||
static onceToken token([&]() {
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("console", (LogLevel) log_level));
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
|
||||
EventPollerPool::setPoolSize(thread_num);
|
||||
WorkThreadPool::setPoolSize(thread_num);
|
||||
|
||||
if (ini && ini[0]) {
|
||||
//设置配置文件
|
||||
if (ini_is_path) {
|
||||
try{
|
||||
mINI::Instance().parseFile(ini);
|
||||
}catch (std::exception &ex) {
|
||||
InfoL << "dump ini file to:" << ini;
|
||||
mINI::Instance().dumpFile(ini);
|
||||
}
|
||||
} else {
|
||||
mINI::Instance().parse(ini);
|
||||
}
|
||||
}
|
||||
|
||||
if (ssl && ssl[0]) {
|
||||
//设置ssl证书
|
||||
SSL_Initor::Instance().loadCertificate(ssl, true, ssl_pwd ? ssl_pwd : "", ssl_is_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_set_option(const char *key, const char *val) {
|
||||
assert(key && val);
|
||||
if (mINI::Instance().find(key) == mINI::Instance().end()) {
|
||||
WarnL << "key:" << key << " not existed!";
|
||||
return;
|
||||
}
|
||||
mINI::Instance()[key] = val;
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_http_server_start(uint16_t port, int ssl) {
|
||||
ssl = MAX(0,MIN(ssl,1));
|
||||
try {
|
||||
http_server[ssl] = std::make_shared<TcpServer>();
|
||||
if(ssl){
|
||||
http_server[ssl]->start<TcpSessionWithSSL<HttpSession> >(port);
|
||||
} else{
|
||||
http_server[ssl]->start<HttpSession>(port);
|
||||
}
|
||||
return http_server[ssl]->getPort();
|
||||
} catch (std::exception &ex) {
|
||||
http_server[ssl].reset();
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_rtsp_server_start(uint16_t port, int ssl) {
|
||||
ssl = MAX(0,MIN(ssl,1));
|
||||
try {
|
||||
rtsp_server[ssl] = std::make_shared<TcpServer>();
|
||||
if(ssl){
|
||||
rtsp_server[ssl]->start<TcpSessionWithSSL<RtspSession> >(port);
|
||||
}else{
|
||||
rtsp_server[ssl]->start<RtspSession>(port);
|
||||
}
|
||||
return rtsp_server[ssl]->getPort();
|
||||
} catch (std::exception &ex) {
|
||||
rtsp_server[ssl].reset();
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_rtmp_server_start(uint16_t port, int ssl) {
|
||||
ssl = MAX(0,MIN(ssl,1));
|
||||
try {
|
||||
rtmp_server[ssl] = std::make_shared<TcpServer>();
|
||||
if(ssl){
|
||||
rtmp_server[ssl]->start<TcpSessionWithSSL<RtmpSession> >(port);
|
||||
}else{
|
||||
rtmp_server[ssl]->start<RtmpSession>(port);
|
||||
}
|
||||
return rtmp_server[ssl]->getPort();
|
||||
} catch (std::exception &ex) {
|
||||
rtmp_server[ssl].reset();
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port){
|
||||
#ifdef ENABLE_RTPPROXY
|
||||
try {
|
||||
//创建rtp tcp服务器
|
||||
tcpRtpServer = std::make_shared<TcpServer>();
|
||||
tcpRtpServer->start<RtpSession>(port);
|
||||
|
||||
//创建rtp udp服务器
|
||||
auto ret = tcpRtpServer->getPort();
|
||||
udpRtpServer = std::make_shared<UdpRecver>();
|
||||
udpRtpServer->initSock(port);
|
||||
return ret;
|
||||
} catch (std::exception &ex) {
|
||||
tcpRtpServer.reset();
|
||||
udpRtpServer.reset();
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
WarnL << "未启用该功能!";
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_shell_server_start(uint16_t port){
|
||||
try {
|
||||
shell_server = std::make_shared<TcpServer>();
|
||||
shell_server->start<ShellSession>(port);
|
||||
return shell_server->getPort();
|
||||
} catch (std::exception &ex) {
|
||||
shell_server.reset();
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
}
|
174
api/source/mk_events.cpp
Normal file
174
api/source/mk_events.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_events.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Rtsp/RtspSession.h"
|
||||
#include "Record/MP4Recorder.h"
|
||||
using namespace mediakit;
|
||||
|
||||
static void* s_tag;
|
||||
static mk_events s_events = {0};
|
||||
|
||||
API_EXPORT void API_CALL mk_events_listen(const mk_events *events){
|
||||
if(events){
|
||||
memcpy(&s_events,events, sizeof(s_events));
|
||||
}else{
|
||||
memset(&s_events,0,sizeof(s_events));
|
||||
}
|
||||
|
||||
static onceToken tokne([]{
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastMediaChanged,[](BroadcastMediaChangedArgs){
|
||||
if(s_events.on_mk_media_changed){
|
||||
s_events.on_mk_media_changed(bRegist,
|
||||
(mk_media_source)&sender);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
|
||||
if(s_events.on_mk_record_mp4){
|
||||
s_events.on_mk_record_mp4((mk_mp4_info)&info);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpRequest,[](BroadcastHttpRequestArgs){
|
||||
if(s_events.on_mk_http_request){
|
||||
int consumed_int = consumed;
|
||||
s_events.on_mk_http_request((mk_parser)&parser,
|
||||
(mk_http_response_invoker)&invoker,
|
||||
&consumed_int,
|
||||
(mk_tcp_session)&sender);
|
||||
consumed = consumed_int;
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
|
||||
if(s_events.on_mk_http_access){
|
||||
s_events.on_mk_http_access((mk_parser)&parser,
|
||||
path.c_str(),
|
||||
is_dir,
|
||||
(mk_http_access_path_invoker)&invoker,
|
||||
(mk_tcp_session)&sender);
|
||||
} else{
|
||||
invoker("","",0);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpBeforeAccess,[](BroadcastHttpBeforeAccessArgs){
|
||||
if(s_events.on_mk_http_before_access){
|
||||
char path_c[4 * 1024] = {0};
|
||||
strcpy(path_c,path.c_str());
|
||||
s_events.on_mk_http_before_access((mk_parser) &parser,
|
||||
path_c,
|
||||
(mk_tcp_session) &sender);
|
||||
path = path_c;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastOnGetRtspRealm,[](BroadcastOnGetRtspRealmArgs){
|
||||
if (s_events.on_mk_rtsp_get_realm) {
|
||||
s_events.on_mk_rtsp_get_realm((mk_media_info) &args,
|
||||
(mk_rtsp_get_realm_invoker) &invoker,
|
||||
(mk_tcp_session) &sender);
|
||||
}else{
|
||||
invoker("");
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastOnRtspAuth,[](BroadcastOnRtspAuthArgs){
|
||||
if (s_events.on_mk_rtsp_auth) {
|
||||
s_events.on_mk_rtsp_auth((mk_media_info) &args,
|
||||
realm.c_str(),
|
||||
user_name.c_str(),
|
||||
must_no_encrypt,
|
||||
(mk_rtsp_auth_invoker) &invoker,
|
||||
(mk_tcp_session) &sender);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
|
||||
if (s_events.on_mk_media_publish) {
|
||||
s_events.on_mk_media_publish((mk_media_info) &args,
|
||||
(mk_publish_auth_invoker) &invoker,
|
||||
(mk_tcp_session) &sender);
|
||||
}else{
|
||||
GET_CONFIG(bool,toRtxp,General::kPublishToRtxp);
|
||||
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
||||
GET_CONFIG(bool,toMP4,General::kPublishToMP4);
|
||||
invoker("",toRtxp,toHls,toMP4);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastMediaPlayed,[](BroadcastMediaPlayedArgs){
|
||||
if (s_events.on_mk_media_play) {
|
||||
s_events.on_mk_media_play((mk_media_info) &args,
|
||||
(mk_auth_invoker) &invoker,
|
||||
(mk_tcp_session) &sender);
|
||||
}else{
|
||||
invoker("");
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){
|
||||
if (s_events.on_mk_shell_login) {
|
||||
s_events.on_mk_shell_login(user_name.c_str(),
|
||||
passwd.c_str(),
|
||||
(mk_auth_invoker) &invoker,
|
||||
(mk_tcp_session) &sender);
|
||||
}else{
|
||||
invoker("");
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){
|
||||
if (s_events.on_mk_flow_report) {
|
||||
s_events.on_mk_flow_report((mk_media_info) &args,
|
||||
totalBytes,
|
||||
totalDuration,
|
||||
isPlayer,
|
||||
peerIP.c_str(),
|
||||
peerPort);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){
|
||||
if (s_events.on_mk_media_not_found) {
|
||||
s_events.on_mk_media_not_found((mk_media_info) &args,
|
||||
(mk_tcp_session) &sender);
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastStreamNoneReader,[](BroadcastStreamNoneReaderArgs){
|
||||
if (s_events.on_mk_media_no_reader) {
|
||||
s_events.on_mk_media_no_reader((mk_media_source) &sender);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
424
api/source/mk_events_objects.cpp
Normal file
424
api/source/mk_events_objects.cpp
Normal file
@ -0,0 +1,424 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include "mk_events_objects.h"
|
||||
#include "Common/config.h"
|
||||
#include "Record/MP4Recorder.h"
|
||||
#include "Network/TcpSession.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Http/HttpBody.h"
|
||||
#include "Http/HttpClient.h"
|
||||
#include "Rtsp/RtspSession.h"
|
||||
using namespace mediakit;
|
||||
|
||||
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->ui64StartedTime;
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->ui64TimeLen;
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->ui64FileSize;
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strFilePath.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strFileName.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strFolder.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strUrl.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strVhost.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strAppName.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strStreamId.c_str();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||
API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Method().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Url().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_full_url(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->FullUrl().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Params().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key){
|
||||
assert(ctx && key);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->getUrlArgs()[key].c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Tail().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){
|
||||
assert(ctx && key);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->getValues()[key].c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, int *length){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
if(length){
|
||||
*length = parser->Content().size();
|
||||
}
|
||||
return parser->Content().c_str();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx){
|
||||
assert(ctx);
|
||||
MediaInfo *info = (MediaInfo *)ctx;
|
||||
return info->_param_strs.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx){
|
||||
assert(ctx);
|
||||
MediaInfo *info = (MediaInfo *)ctx;
|
||||
return info->_schema.c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx){
|
||||
assert(ctx);
|
||||
MediaInfo *info = (MediaInfo *)ctx;
|
||||
return info->_vhost.c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx){
|
||||
assert(ctx);
|
||||
MediaInfo *info = (MediaInfo *)ctx;
|
||||
return info->_app.c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx){
|
||||
assert(ctx);
|
||||
MediaInfo *info = (MediaInfo *)ctx;
|
||||
return info->_streamid.c_str();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////MediaSource/////////////////////////////////////////////
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getSchema().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getVhost().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getApp().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getId().c_str();
|
||||
}
|
||||
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->readerCount();
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->totalReaderCount();
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->close(force);
|
||||
}
|
||||
API_EXPORT int API_CALL mk_media_source_seek_to(const mk_media_source ctx,uint32_t stamp){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->seekTo(stamp);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_source_find(const char *schema,
|
||||
const char *vhost,
|
||||
const char *app,
|
||||
const char *stream,
|
||||
void *user_data,
|
||||
on_mk_media_source_find_cb cb) {
|
||||
assert(schema && vhost && app && stream && cb);
|
||||
auto src = MediaSource::find(schema, vhost, app, stream);
|
||||
cb(user_data, src.get());
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb){
|
||||
assert(cb);
|
||||
MediaSource::for_each_media([&](const MediaSource::Ptr &src){
|
||||
cb(user_data,src.get());
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////HttpBody/////////////////////////////////////////////
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_string(const char *str,int len){
|
||||
assert(str);
|
||||
if(!len){
|
||||
len = strlen(str);
|
||||
}
|
||||
return new HttpBody::Ptr(new HttpStringBody(string(str,len)));
|
||||
}
|
||||
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_file(const char *file_path){
|
||||
assert(file_path);
|
||||
return new HttpBody::Ptr(new HttpFileBody(file_path));
|
||||
}
|
||||
|
||||
template <typename C = StrCaseMap>
|
||||
static C get_http_header( const char *response_header[]){
|
||||
C header;
|
||||
for (int i = 0; response_header[i] != NULL;) {
|
||||
auto key = response_header[i];
|
||||
auto value = response_header[i + 1];
|
||||
if (key && value) {
|
||||
i += 2;
|
||||
header.emplace(key,value);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::move(header);
|
||||
}
|
||||
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_multi_form(const char *key_val[],const char *file_path){
|
||||
assert(key_val && file_path);
|
||||
return new HttpBody::Ptr(new HttpMultiFormBody(get_http_header<HttpArgs>(key_val),file_path));
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_body_release(mk_http_body ctx){
|
||||
assert(ctx);
|
||||
HttpBody::Ptr *ptr = (HttpBody::Ptr *)ctx;
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////HttpResponseInvoker/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_do_string(const mk_http_response_invoker ctx,
|
||||
const char *response_code,
|
||||
const char **response_header,
|
||||
const char *response_content){
|
||||
assert(ctx && response_code && response_header && response_content);
|
||||
auto header = get_http_header(response_header);
|
||||
HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx;
|
||||
(*invoker)(response_code,header,response_content);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_do_file(const mk_http_response_invoker ctx,
|
||||
const mk_parser request_parser,
|
||||
const char *response_header[],
|
||||
const char *response_file_path){
|
||||
assert(ctx && request_parser && response_header && response_file_path);
|
||||
auto header = get_http_header(response_header);
|
||||
HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx;
|
||||
(*invoker).responseFile(((Parser*)(request_parser))->getValues(),header,response_file_path);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_do(const mk_http_response_invoker ctx,
|
||||
const char *response_code,
|
||||
const char **response_header,
|
||||
const mk_http_body response_body){
|
||||
assert(ctx && response_code && response_header && response_body);
|
||||
auto header = get_http_header(response_header);
|
||||
HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx;
|
||||
HttpBody::Ptr *body = (HttpBody::Ptr*) response_body;
|
||||
(*invoker)(response_code,header,*body);
|
||||
}
|
||||
|
||||
API_EXPORT mk_http_response_invoker API_CALL mk_http_response_invoker_clone(const mk_http_response_invoker ctx){
|
||||
assert(ctx);
|
||||
HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx;
|
||||
return new HttpSession::HttpResponseInvoker (*invoker);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_response_invoker_clone_release(const mk_http_response_invoker ctx){
|
||||
assert(ctx);
|
||||
HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx;
|
||||
delete invoker;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////HttpAccessPathInvoker/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_http_access_path_invoker_do(const mk_http_access_path_invoker ctx,
|
||||
const char *err_msg,
|
||||
const char *access_path,
|
||||
int cookie_life_second){
|
||||
assert(ctx);
|
||||
HttpSession::HttpAccessPathInvoker *invoker = (HttpSession::HttpAccessPathInvoker *)ctx;
|
||||
(*invoker)(err_msg ? err_msg : "",
|
||||
access_path? access_path : "",
|
||||
cookie_life_second);
|
||||
}
|
||||
|
||||
API_EXPORT mk_http_access_path_invoker API_CALL mk_http_access_path_invoker_clone(const mk_http_access_path_invoker ctx){
|
||||
assert(ctx);
|
||||
HttpSession::HttpAccessPathInvoker *invoker = (HttpSession::HttpAccessPathInvoker *)ctx;
|
||||
return new HttpSession::HttpAccessPathInvoker(*invoker);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_access_path_invoker_clone_release(const mk_http_access_path_invoker ctx){
|
||||
assert(ctx);
|
||||
HttpSession::HttpAccessPathInvoker *invoker = (HttpSession::HttpAccessPathInvoker *)ctx;
|
||||
delete invoker;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////RtspSession::onGetRealm/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_do(const mk_rtsp_get_realm_invoker ctx,
|
||||
const char *realm){
|
||||
assert(ctx);
|
||||
RtspSession::onGetRealm *invoker = (RtspSession::onGetRealm *)ctx;
|
||||
(*invoker)(realm ? realm : "");
|
||||
}
|
||||
|
||||
API_EXPORT mk_rtsp_get_realm_invoker API_CALL mk_rtsp_get_realm_invoker_clone(const mk_rtsp_get_realm_invoker ctx){
|
||||
assert(ctx);
|
||||
RtspSession::onGetRealm *invoker = (RtspSession::onGetRealm *)ctx;
|
||||
return new RtspSession::onGetRealm (*invoker);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_clone_release(const mk_rtsp_get_realm_invoker ctx){
|
||||
assert(ctx);
|
||||
RtspSession::onGetRealm *invoker = (RtspSession::onGetRealm *)ctx;
|
||||
delete invoker;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////RtspSession::onAuth/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_rtsp_auth_invoker_do(const mk_rtsp_auth_invoker ctx,
|
||||
int encrypted,
|
||||
const char *pwd_or_md5){
|
||||
assert(ctx);
|
||||
RtspSession::onAuth *invoker = (RtspSession::onAuth *)ctx;
|
||||
(*invoker)(encrypted, pwd_or_md5 ? pwd_or_md5 : "");
|
||||
}
|
||||
|
||||
API_EXPORT mk_rtsp_auth_invoker API_CALL mk_rtsp_auth_invoker_clone(const mk_rtsp_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
RtspSession::onAuth *invoker = (RtspSession::onAuth *)ctx;
|
||||
return new RtspSession::onAuth(*invoker);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_rtsp_auth_invoker_clone_release(const mk_rtsp_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
RtspSession::onAuth *invoker = (RtspSession::onAuth *)ctx;
|
||||
delete invoker;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////Broadcast::PublishAuthInvoker/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
||||
const char *err_msg,
|
||||
int enable_rtxp,
|
||||
int enable_hls,
|
||||
int enable_mp4){
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
(*invoker)(err_msg ? err_msg : "", enable_rtxp, enable_hls, enable_mp4);
|
||||
}
|
||||
|
||||
API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
return new Broadcast::PublishAuthInvoker(*invoker);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_clone_release(const mk_publish_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
delete invoker;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////Broadcast::AuthInvoker/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_auth_invoker_do(const mk_auth_invoker ctx, const char *err_msg){
|
||||
assert(ctx);
|
||||
Broadcast::AuthInvoker *invoker = (Broadcast::AuthInvoker *)ctx;
|
||||
(*invoker)(err_msg ? err_msg : "");
|
||||
}
|
||||
|
||||
API_EXPORT mk_auth_invoker API_CALL mk_auth_invoker_clone(const mk_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
Broadcast::AuthInvoker *invoker = (Broadcast::AuthInvoker *)ctx;
|
||||
return new Broadcast::AuthInvoker(*invoker);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_auth_invoker_clone_release(const mk_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
Broadcast::AuthInvoker *invoker = (Broadcast::AuthInvoker *)ctx;
|
||||
delete invoker;
|
||||
}
|
159
api/source/mk_httpclient.cpp
Executable file
159
api/source/mk_httpclient.cpp
Executable file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include "mk_httpclient.h"
|
||||
|
||||
#include "Util/logger.h"
|
||||
#include "Http/HttpDownloader.h"
|
||||
#include "Http/HttpRequester.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_http_downloader API_CALL mk_http_downloader_create() {
|
||||
HttpDownloader::Ptr *obj(new HttpDownloader::Ptr(new HttpDownloader()));
|
||||
return (mk_http_downloader) obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_downloader_release(mk_http_downloader ctx) {
|
||||
assert(ctx);
|
||||
HttpDownloader::Ptr *obj = (HttpDownloader::Ptr *) ctx;
|
||||
delete obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_downloader_start(mk_http_downloader ctx, const char *url, const char *file, on_mk_download_complete cb, void *user_data) {
|
||||
assert(ctx && url && file);
|
||||
HttpDownloader::Ptr *obj = (HttpDownloader::Ptr *) ctx;
|
||||
(*obj)->setOnResult([cb, user_data](ErrCode code, const string &errMsg, const string &filePath) {
|
||||
if (cb) {
|
||||
cb(user_data, code, errMsg.data(), filePath.data());
|
||||
}
|
||||
});
|
||||
(*obj)->startDownload(url, file, false);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////HttpRequester/////////////////////////////////////////////
|
||||
API_EXPORT mk_http_requester API_CALL mk_http_requester_create(){
|
||||
HttpRequester::Ptr *ret = new HttpRequester::Ptr(new HttpRequester);
|
||||
return ret;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_clear(mk_http_requester ctx){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
(*obj)->clear();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_release(mk_http_requester ctx){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
delete obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_set_method(mk_http_requester ctx,const char *method){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
(*obj)->setMethod(method);
|
||||
}
|
||||
|
||||
template <typename C = StrCaseMap>
|
||||
static C get_http_header( const char *response_header[]){
|
||||
C header;
|
||||
for (int i = 0; response_header[i] != NULL;) {
|
||||
auto key = response_header[i];
|
||||
auto value = response_header[i + 1];
|
||||
if (key && value) {
|
||||
i += 2;
|
||||
header.emplace(key,value);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::move(header);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_set_body(mk_http_requester ctx, mk_http_body body){
|
||||
assert(ctx && body);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
HttpBody::Ptr *body_obj = (HttpBody::Ptr *)body;
|
||||
(*obj)->setBody(*body_obj);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_set_header(mk_http_requester ctx, const char *header[]){
|
||||
assert(ctx && header);
|
||||
auto header_obj = get_http_header(header);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
(*obj)->setHeader(header_obj);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,const char *key,const char *value,int force){
|
||||
assert(ctx && key && value);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
(*obj)->addHeader(key,value,force);
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
return (*obj)->responseStatus().c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
return (*obj)->response()[key].c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requester ctx, int *length){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
if(length){
|
||||
*length = (*obj)->response().Content().size();
|
||||
}
|
||||
return (*obj)->response().Content().c_str();
|
||||
}
|
||||
|
||||
API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
return (mk_parser)&((*obj)->response());
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_set_cb(mk_http_requester ctx,on_mk_http_requester_complete cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
(*obj)->setOnResult([cb,user_data](const SockException &ex,const string &status,const StrCaseMap &header,const string &strRecvBody){
|
||||
cb(user_data, ex.getErrCode(),ex.what());
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_start(mk_http_requester ctx,const char *url, float timeout_second){
|
||||
assert(ctx && url && url[0] && timeout_second > 0);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
(*obj)->sendRequest(url,timeout_second);
|
||||
}
|
||||
|
184
api/source/mk_media.cpp
Executable file
184
api/source/mk_media.cpp
Executable file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_media.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Common/Device.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
class MediaHelper : public MediaSourceEvent , public std::enable_shared_from_this<MediaHelper> {
|
||||
public:
|
||||
typedef std::shared_ptr<MediaHelper> Ptr;
|
||||
template<typename ...ArgsType>
|
||||
MediaHelper(ArgsType &&...args){
|
||||
_channel = std::make_shared<DevChannel>(std::forward<ArgsType>(args)...);
|
||||
}
|
||||
~MediaHelper(){}
|
||||
|
||||
void attachEvent(){
|
||||
_channel->setListener(shared_from_this());
|
||||
}
|
||||
|
||||
DevChannel::Ptr &getChannel(){
|
||||
return _channel;
|
||||
}
|
||||
|
||||
void setCallBack(on_mk_media_close cb, void *user_data){
|
||||
_cb = cb;
|
||||
_user_data = user_data;
|
||||
}
|
||||
protected:
|
||||
// 通知其停止推流
|
||||
bool close(MediaSource &sender,bool force) override{
|
||||
if(!force && _channel->totalReaderCount()){
|
||||
//非强制关闭且正有人在观看该视频
|
||||
return false;
|
||||
}
|
||||
if(!_cb){
|
||||
//未设置回调,没法关闭
|
||||
return false;
|
||||
}
|
||||
if(!_cb(_user_data)){
|
||||
//回调选择返回不关闭该视频
|
||||
return false;
|
||||
}
|
||||
|
||||
//回调中已经关闭该视频
|
||||
WarnL << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 通知无人观看
|
||||
void onNoneReader(MediaSource &sender) override{
|
||||
if(_channel->totalReaderCount()){
|
||||
//统计有误,还有人在看
|
||||
return;
|
||||
}
|
||||
MediaSourceEvent::onNoneReader(sender);
|
||||
}
|
||||
|
||||
// 观看总人数
|
||||
int totalReaderCount(MediaSource &sender) override{
|
||||
return _channel->totalReaderCount();
|
||||
}
|
||||
private:
|
||||
DevChannel::Ptr _channel;
|
||||
on_mk_media_close _cb;
|
||||
void *_user_data;
|
||||
};
|
||||
|
||||
API_EXPORT void API_CALL mk_media_set_on_close(mk_media ctx, on_mk_media_close cb, void *user_data){
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
(*obj)->setCallBack(cb,user_data);
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_total_reader_count(mk_media ctx){
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
return (*obj)->getChannel()->totalReaderCount();
|
||||
}
|
||||
|
||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration, int hls_enabled, int mp4_enabled) {
|
||||
assert(vhost && app && stream);
|
||||
MediaHelper::Ptr *obj(new MediaHelper::Ptr(new MediaHelper(vhost, app, stream, duration, true, true, hls_enabled, mp4_enabled)));
|
||||
(*obj)->attachEvent();
|
||||
return (mk_media) obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_release(mk_media ctx) {
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
delete obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_init_h264(mk_media ctx, int width, int height, int frameRate) {
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
VideoInfo info;
|
||||
info.iFrameRate = frameRate;
|
||||
info.iWidth = width;
|
||||
info.iHeight = height;
|
||||
(*obj)->getChannel()->initVideo(info);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_init_h265(mk_media ctx, int width, int height, int frameRate) {
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
VideoInfo info;
|
||||
info.iFrameRate = frameRate;
|
||||
info.iWidth = width;
|
||||
info.iHeight = height;
|
||||
(*obj)->getChannel()->initH265Video(info);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_init_aac(mk_media ctx, int channel, int sample_bit, int sample_rate, int profile) {
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
AudioInfo info;
|
||||
info.iSampleRate = sample_rate;
|
||||
info.iChannel = channel;
|
||||
info.iSampleBit = sample_bit;
|
||||
info.iProfile = profile;
|
||||
(*obj)->getChannel()->initAudio(info);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_init_complete(mk_media ctx){
|
||||
assert(ctx);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
(*obj)->getChannel()->addTrackCompleted();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts) {
|
||||
assert(ctx && data && len > 0);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
(*obj)->getChannel()->inputH264((char *) data, len, dts, pts);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_input_h265(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts) {
|
||||
assert(ctx && data && len > 0);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
(*obj)->getChannel()->inputH265((char *) data, len, dts, pts);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_input_aac(mk_media ctx, void *data, int len, uint32_t dts, int with_adts_header) {
|
||||
assert(ctx && data && len > 0);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
(*obj)->getChannel()->inputAAC((char *) data, len, dts, with_adts_header);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_input_aac1(mk_media ctx, void *data, int len, uint32_t dts, void *adts) {
|
||||
assert(ctx && data && len > 0 && adts);
|
||||
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
|
||||
(*obj)->getChannel()->inputAAC((char *) data, len, dts, (char *) adts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
178
api/source/mk_player.cpp
Executable file
178
api/source/mk_player.cpp
Executable file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_player.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Player/MediaPlayer.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_player API_CALL mk_player_create() {
|
||||
MediaPlayer::Ptr *obj = new MediaPlayer::Ptr(new MediaPlayer());
|
||||
return obj;
|
||||
}
|
||||
API_EXPORT void API_CALL mk_player_release(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr *obj = (MediaPlayer::Ptr *)ctx;
|
||||
delete obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_player_set_option(mk_player ctx,const char* key,const char *val){
|
||||
assert(ctx && key && val);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
string key_str(key), val_str(val);
|
||||
player->getPoller()->async([key_str,val_str,player](){
|
||||
//切换线程后再操作
|
||||
(*player)[key_str] = val_str;
|
||||
});
|
||||
}
|
||||
API_EXPORT void API_CALL mk_player_play(mk_player ctx, const char *url) {
|
||||
assert(ctx && url);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
string url_str(url);
|
||||
player->getPoller()->async([url_str,player](){
|
||||
//切换线程后再操作
|
||||
player->play(url_str);
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_player_pause(mk_player ctx, int pause) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
player->getPoller()->async([pause,player](){
|
||||
//切换线程后再操作
|
||||
player->pause(pause);
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
player->getPoller()->async([progress,player](){
|
||||
//切换线程后再操作
|
||||
player->seekTo(progress);
|
||||
});
|
||||
}
|
||||
|
||||
static void mk_player_set_on_event(mk_player ctx, on_mk_play_event cb, void *user_data, int type) {
|
||||
assert(ctx && cb);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
player->getPoller()->async([cb,user_data,type,player](){
|
||||
//切换线程后再操作
|
||||
if(type == 0){
|
||||
player->setOnPlayResult([cb,user_data](const SockException &ex){
|
||||
cb(user_data,ex.getErrCode(),ex.what());
|
||||
});
|
||||
}else{
|
||||
player->setOnShutdown([cb,user_data](const SockException &ex){
|
||||
cb(user_data,ex.getErrCode(),ex.what());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_player_set_on_result(mk_player ctx, on_mk_play_event cb, void *user_data) {
|
||||
mk_player_set_on_event(ctx,cb,user_data,0);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_event cb, void *user_data) {
|
||||
mk_player_set_on_event(ctx,cb,user_data,1);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data) {
|
||||
assert(ctx && cb);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
player->getPoller()->async([player,cb,user_data](){
|
||||
//切换线程后再操作
|
||||
auto delegate = std::make_shared<FrameWriterInterfaceHelper>([cb,user_data](const Frame::Ptr &frame){
|
||||
cb(user_data,frame->getTrackType(),frame->getCodecId(),frame->data(),frame->size(),frame->dts(),frame->pts());
|
||||
});
|
||||
for(auto &track : player->getTracks()){
|
||||
track->addDelegate(delegate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_player_video_width(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
||||
return track ? track->getVideoWidth() : 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_player_video_height(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
||||
return track ? track->getVideoHeight() : 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_player_video_fps(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
auto track = dynamic_pointer_cast<VideoTrack>(player->getTrack(TrackVideo));
|
||||
return track ? track->getVideoFps() : 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_player_audio_samplerate(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
||||
return track ? track->getAudioSampleRate() : 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_player_audio_bit(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
||||
return track ? track->getAudioSampleBit() : 0;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_player_audio_channel(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
auto track = dynamic_pointer_cast<AudioTrack>(player->getTrack(TrackAudio));
|
||||
return track ? track->getAudioChannel() : 0;
|
||||
}
|
||||
|
||||
API_EXPORT float API_CALL mk_player_duration(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
return player->getDuration();
|
||||
}
|
||||
|
||||
API_EXPORT float API_CALL mk_player_progress(mk_player ctx) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
return player->getProgress();
|
||||
}
|
||||
|
||||
API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type) {
|
||||
assert(ctx);
|
||||
MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx);
|
||||
return player->getPacketLossRate((TrackType)track_type);
|
||||
}
|
63
api/source/mk_proxyplayer.cpp
Normal file
63
api/source/mk_proxyplayer.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_proxyplayer.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_proxy_player API_CALL mk_proxy_player_create(const char *vhost, const char *app, const char *stream, int hls_enabled, int mp4_enabled) {
|
||||
assert(vhost && app && stream);
|
||||
PlayerProxy::Ptr *obj(new PlayerProxy::Ptr(new PlayerProxy(vhost, app, stream, true, true, hls_enabled, mp4_enabled)));
|
||||
return (mk_proxy_player) obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_proxy_player_release(mk_proxy_player ctx) {
|
||||
assert(ctx);
|
||||
PlayerProxy::Ptr *obj = (PlayerProxy::Ptr *) ctx;
|
||||
delete obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_proxy_player_set_option(mk_proxy_player ctx, const char *key, const char *val){
|
||||
assert(ctx && key && val);
|
||||
PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx);
|
||||
string key_str(key),val_str(val);
|
||||
obj->getPoller()->async([obj,key_str,val_str](){
|
||||
//切换线程再操作
|
||||
(*obj)[key_str] = val_str;
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_proxy_player_play(mk_proxy_player ctx, const char *url) {
|
||||
assert(ctx && url);
|
||||
PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx);
|
||||
string url_str(url);
|
||||
obj->getPoller()->async([obj,url_str](){
|
||||
//切换线程再操作
|
||||
obj->play(url_str);
|
||||
});
|
||||
}
|
84
api/source/mk_pusher.cpp
Normal file
84
api/source/mk_pusher.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include "mk_pusher.h"
|
||||
#include "Pusher/MediaPusher.h"
|
||||
using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_pusher API_CALL mk_pusher_create(const char *schema,const char *vhost,const char *app, const char *stream){
|
||||
assert(schema && vhost && app && schema);
|
||||
MediaPusher::Ptr *obj = new MediaPusher::Ptr(new MediaPusher(schema,vhost,app,stream));
|
||||
return obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_pusher_release(mk_pusher ctx){
|
||||
assert(ctx);
|
||||
MediaPusher::Ptr *obj = (MediaPusher::Ptr *)ctx;
|
||||
delete obj;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_pusher_set_option(mk_pusher ctx, const char *key, const char *val){
|
||||
assert(ctx && key && val);
|
||||
MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx);
|
||||
string key_str(key),val_str(val);
|
||||
obj->getPoller()->async([obj,key_str,val_str](){
|
||||
//切换线程再操作
|
||||
(*obj)[key_str] = val_str;
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_pusher_publish(mk_pusher ctx,const char *url){
|
||||
assert(ctx && url);
|
||||
MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx);
|
||||
string url_str(url);
|
||||
obj->getPoller()->async([obj,url_str](){
|
||||
//切换线程再操作
|
||||
obj->publish(url_str);
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_pusher_set_on_result(mk_pusher ctx, on_mk_push_event cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx);
|
||||
obj->getPoller()->async([obj,cb,user_data](){
|
||||
//切换线程再操作
|
||||
obj->setOnPublished([cb,user_data](const SockException &ex){
|
||||
cb(user_data,ex.getErrCode(),ex.what());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_pusher_set_on_shutdown(mk_pusher ctx, on_mk_push_event cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx);
|
||||
obj->getPoller()->async([obj,cb,user_data](){
|
||||
//切换线程再操作
|
||||
obj->setOnShutdown([cb,user_data](const SockException &ex){
|
||||
cb(user_data,ex.getErrCode(),ex.what());
|
||||
});
|
||||
});
|
||||
}
|
72
api/source/mk_recorder.cpp
Normal file
72
api/source/mk_recorder.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_recorder.h"
|
||||
#include "Rtmp/FlvMuxer.h"
|
||||
#include "Record/Recorder.h"
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_flv_recorder API_CALL mk_flv_recorder_create(){
|
||||
FlvRecorder::Ptr *ret = new FlvRecorder::Ptr(new FlvRecorder);
|
||||
return ret;
|
||||
}
|
||||
API_EXPORT void API_CALL mk_flv_recorder_release(mk_flv_recorder ctx){
|
||||
assert(ctx);
|
||||
FlvRecorder::Ptr *record = (FlvRecorder::Ptr *)(ctx);
|
||||
delete record;
|
||||
}
|
||||
API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *vhost, const char *app, const char *stream, const char *file_path){
|
||||
assert(ctx && vhost && app && stream && file_path);
|
||||
try {
|
||||
FlvRecorder::Ptr *record = (FlvRecorder::Ptr *)(ctx);
|
||||
(*record)->startRecord(EventPollerPool::Instance().getPoller(),vhost,app,stream,file_path);
|
||||
return 0;
|
||||
}catch (std::exception &ex){
|
||||
WarnL << ex.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////hls/mp4录制/////////////////////////////////////////////
|
||||
API_EXPORT int API_CALL mk_recorder_status(int type, const char *vhost, const char *app, const char *stream){
|
||||
assert(vhost && app && stream);
|
||||
return Recorder::getRecordStatus((Recorder::type)type,vhost,app,stream);
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const char *app, const char *stream,const char *customized_path,int wait_for_record, int continue_record){
|
||||
assert(vhost && app && stream);
|
||||
return Recorder::startRecord((Recorder::type)type,vhost,app,stream,customized_path ? customized_path : "",wait_for_record,continue_record);
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_recorder_stop(int type, const char *vhost, const char *app, const char *stream){
|
||||
assert(vhost && app && stream);
|
||||
return Recorder::stopRecord((Recorder::type)type,vhost,app,stream);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_recorder_stop_all(){
|
||||
Recorder::stopAll();
|
||||
}
|
271
api/source/mk_tcp.cpp
Normal file
271
api/source/mk_tcp.cpp
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_tcp.h"
|
||||
#include "mk_tcp_private.h"
|
||||
#include "Http/WebSocketClient.h"
|
||||
#include "Http/WebSocketSession.h"
|
||||
using namespace mediakit;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_tcp_session_shutdown(const mk_tcp_session ctx,int err,const char *err_msg){
|
||||
assert(ctx);
|
||||
TcpSession *session = (TcpSession *)ctx;
|
||||
session->safeShutdown(SockException((ErrCode)err,err_msg));
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_tcp_session_peer_ip(const mk_tcp_session ctx){
|
||||
assert(ctx);
|
||||
TcpSession *session = (TcpSession *)ctx;
|
||||
return session->get_peer_ip().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_tcp_session_local_ip(const mk_tcp_session ctx){
|
||||
assert(ctx);
|
||||
TcpSession *session = (TcpSession *)ctx;
|
||||
return session->get_local_ip().c_str();
|
||||
}
|
||||
API_EXPORT uint16_t API_CALL mk_tcp_session_peer_port(const mk_tcp_session ctx){
|
||||
assert(ctx);
|
||||
TcpSession *session = (TcpSession *)ctx;
|
||||
return session->get_peer_port();
|
||||
}
|
||||
API_EXPORT uint16_t API_CALL mk_tcp_session_local_port(const mk_tcp_session ctx){
|
||||
assert(ctx);
|
||||
TcpSession *session = (TcpSession *)ctx;
|
||||
return session->get_local_port();
|
||||
}
|
||||
API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx,const char *data,int len){
|
||||
assert(ctx && data);
|
||||
if(!len){
|
||||
len = strlen(data);
|
||||
}
|
||||
TcpSession *session = (TcpSession *)ctx;
|
||||
session->send(data,len);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx,const char *data,int len){
|
||||
assert(ctx && data);
|
||||
if(!len){
|
||||
len = strlen(data);
|
||||
}
|
||||
try {
|
||||
weak_ptr<TcpSession> weak_session = ((TcpSession *)ctx)->shared_from_this();
|
||||
string str = string(data,len);
|
||||
((TcpSession *)ctx)->async([weak_session,str](){
|
||||
auto session_session = weak_session.lock();
|
||||
if(session_session){
|
||||
session_session->send(str);
|
||||
}
|
||||
});
|
||||
}catch (std::exception &ex){
|
||||
WarnL << "can not got the strong pionter of this mk_tcp_session:" << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////TcpSessionForC////////////////////////////////////////////////
|
||||
static TcpServer::Ptr s_tcp_server[4];
|
||||
static mk_tcp_session_events s_events_server = {0};
|
||||
|
||||
TcpSessionForC::TcpSessionForC(const Socket::Ptr &pSock) : TcpSession(pSock) {
|
||||
_local_port = get_local_port();
|
||||
if (s_events_server.on_mk_tcp_session_create) {
|
||||
s_events_server.on_mk_tcp_session_create(_local_port,this);
|
||||
}
|
||||
}
|
||||
|
||||
void TcpSessionForC::onRecv(const Buffer::Ptr &buffer) {
|
||||
if (s_events_server.on_mk_tcp_session_data) {
|
||||
s_events_server.on_mk_tcp_session_data(_local_port,this, buffer->data(), buffer->size());
|
||||
}
|
||||
}
|
||||
|
||||
void TcpSessionForC::onError(const SockException &err) {
|
||||
if (s_events_server.on_mk_tcp_session_disconnect) {
|
||||
s_events_server.on_mk_tcp_session_disconnect(_local_port,this, err.getErrCode(), err.what());
|
||||
}
|
||||
}
|
||||
|
||||
void TcpSessionForC::onManager() {
|
||||
if (s_events_server.on_mk_tcp_session_manager) {
|
||||
s_events_server.on_mk_tcp_session_manager(_local_port,this);
|
||||
}
|
||||
}
|
||||
|
||||
void stopAllTcpServer(){
|
||||
CLEAR_ARR(s_tcp_server);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_session_set_user_data(mk_tcp_session session,void *user_data){
|
||||
assert(session);
|
||||
TcpSessionForC *obj = (TcpSessionForC *)session;
|
||||
obj->_user_data = user_data;
|
||||
}
|
||||
|
||||
API_EXPORT void* API_CALL mk_tcp_session_get_user_data(mk_tcp_session session){
|
||||
assert(session);
|
||||
TcpSessionForC *obj = (TcpSessionForC *)session;
|
||||
return obj->_user_data;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_server_events_listen(const mk_tcp_session_events *events){
|
||||
if (events) {
|
||||
memcpy(&s_events_server, events, sizeof(s_events_server));
|
||||
} else {
|
||||
memset(&s_events_server, 0, sizeof(s_events_server));
|
||||
}
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_tcp_server_start(uint16_t port, mk_tcp_type type){
|
||||
type = MAX(mk_type_tcp, MIN(type, mk_type_wss));
|
||||
try {
|
||||
s_tcp_server[type] = std::make_shared<TcpServer>();
|
||||
switch (type) {
|
||||
case mk_type_tcp:
|
||||
s_tcp_server[type]->start<TcpSessionForC>(port);
|
||||
break;
|
||||
case mk_type_ssl:
|
||||
s_tcp_server[type]->start<TcpSessionWithSSL<TcpSessionForC> >(port);
|
||||
break;
|
||||
case mk_type_ws:
|
||||
s_tcp_server[type]->start<WebSocketSession<TcpSessionForC, HttpSession>>(port);
|
||||
break;
|
||||
case mk_type_wss:
|
||||
s_tcp_server[type]->start<WebSocketSession<TcpSessionForC, HttpsSession>>(port);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return s_tcp_server[type]->getPort();
|
||||
} catch (std::exception &ex) {
|
||||
s_tcp_server[type].reset();
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////TcpClientForC/////////////////////////////////////////////////////////
|
||||
TcpClientForC::TcpClientForC(mk_tcp_client_events *events){
|
||||
_events = *events;
|
||||
}
|
||||
|
||||
|
||||
void TcpClientForC::onRecv(const Buffer::Ptr &pBuf) {
|
||||
if(_events.on_mk_tcp_client_data){
|
||||
_events.on_mk_tcp_client_data(_client,pBuf->data(),pBuf->size());
|
||||
}
|
||||
}
|
||||
|
||||
void TcpClientForC::onErr(const SockException &ex) {
|
||||
if(_events.on_mk_tcp_client_disconnect){
|
||||
_events.on_mk_tcp_client_disconnect(_client,ex.getErrCode(),ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void TcpClientForC::onManager() {
|
||||
if(_events.on_mk_tcp_client_manager){
|
||||
_events.on_mk_tcp_client_manager(_client);
|
||||
}
|
||||
}
|
||||
|
||||
void TcpClientForC::onConnect(const SockException &ex) {
|
||||
if(_events.on_mk_tcp_client_connect){
|
||||
_events.on_mk_tcp_client_connect(_client,ex.getErrCode(),ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
TcpClientForC::~TcpClientForC() {
|
||||
TraceL << "mk_tcp_client_release:" << _client;
|
||||
}
|
||||
|
||||
void TcpClientForC::setClient(mk_tcp_client client) {
|
||||
_client = client;
|
||||
TraceL << "mk_tcp_client_create:" << _client;
|
||||
}
|
||||
|
||||
TcpClientForC::Ptr *mk_tcp_client_create_l(mk_tcp_client_events *events, mk_tcp_type type){
|
||||
assert(events);
|
||||
type = MAX(mk_type_tcp, MIN(type, mk_type_wss));
|
||||
switch (type) {
|
||||
case mk_type_tcp:
|
||||
return new TcpClientForC::Ptr(new TcpClientForC(events));
|
||||
case mk_type_ssl:
|
||||
return (TcpClientForC::Ptr *)new shared_ptr<TcpSessionWithSSL<TcpClientForC> >(new TcpSessionWithSSL<TcpClientForC>(events));
|
||||
case mk_type_ws:
|
||||
return (TcpClientForC::Ptr *)new shared_ptr<WebSocketClient<TcpClientForC, WebSocketHeader::TEXT, false> >(new WebSocketClient<TcpClientForC, WebSocketHeader::TEXT, false>(events));
|
||||
case mk_type_wss:
|
||||
return (TcpClientForC::Ptr *)new shared_ptr<WebSocketClient<TcpClientForC, WebSocketHeader::TEXT, true> >(new WebSocketClient<TcpClientForC, WebSocketHeader::TEXT, true>(events));
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
API_EXPORT mk_tcp_client API_CALL mk_tcp_client_create(mk_tcp_client_events *events, mk_tcp_type type){
|
||||
auto ret = mk_tcp_client_create_l(events,type);
|
||||
(*ret)->setClient(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_client_release(mk_tcp_client ctx){
|
||||
assert(ctx);
|
||||
TcpClient::Ptr *client = (TcpClient::Ptr *)ctx;
|
||||
delete client;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_client_connect(mk_tcp_client ctx, const char *host, uint16_t port, float time_out_sec){
|
||||
assert(ctx);
|
||||
TcpClient::Ptr *client = (TcpClient::Ptr *)ctx;
|
||||
(*client)->startConnect(host,port);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_client_send(mk_tcp_client ctx, const char *data, int len){
|
||||
assert(ctx && data);
|
||||
TcpClient::Ptr *client = (TcpClient::Ptr *)ctx;
|
||||
(*client)->send(data,len);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_client_send_safe(mk_tcp_client ctx, const char *data, int len){
|
||||
assert(ctx && data);
|
||||
TcpClient::Ptr *client = (TcpClient::Ptr *)ctx;
|
||||
weak_ptr<TcpClient> weakClient = *client;
|
||||
Buffer::Ptr buf = (*client)->obtainBuffer(data,len);
|
||||
(*client)->async([weakClient,buf](){
|
||||
auto strongClient = weakClient.lock();
|
||||
if(strongClient){
|
||||
strongClient->send(buf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_tcp_client_set_user_data(mk_tcp_client ctx,void *user_data){
|
||||
assert(ctx);
|
||||
TcpClientForC::Ptr *client = (TcpClientForC::Ptr *)ctx;
|
||||
(*client)->_user_data = user_data;
|
||||
}
|
||||
|
||||
API_EXPORT void* API_CALL mk_tcp_client_get_user_data(mk_tcp_client ctx){
|
||||
assert(ctx);
|
||||
TcpClientForC::Ptr *client = (TcpClientForC::Ptr *)ctx;
|
||||
return (*client)->_user_data;
|
||||
}
|
62
api/source/mk_tcp_private.h
Normal file
62
api/source/mk_tcp_private.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MK_TCP_PRIVATE_H
|
||||
#define MK_TCP_PRIVATE_H
|
||||
|
||||
#include "mk_tcp.h"
|
||||
#include "Network/TcpClient.h"
|
||||
#include "Network/TcpSession.h"
|
||||
using namespace toolkit;
|
||||
|
||||
class TcpClientForC : public TcpClient {
|
||||
public:
|
||||
typedef std::shared_ptr<TcpClientForC> Ptr;
|
||||
TcpClientForC(mk_tcp_client_events *events) ;
|
||||
~TcpClientForC() override ;
|
||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||
void onErr(const SockException &ex) override;
|
||||
void onManager() override;
|
||||
void onConnect(const SockException &ex) override;
|
||||
void setClient(mk_tcp_client client);
|
||||
void *_user_data;
|
||||
private:
|
||||
mk_tcp_client_events _events;
|
||||
mk_tcp_client _client;
|
||||
};
|
||||
|
||||
class TcpSessionForC : public TcpSession {
|
||||
public:
|
||||
TcpSessionForC(const Socket::Ptr &pSock) ;
|
||||
~TcpSessionForC() override = default;
|
||||
void onRecv(const Buffer::Ptr &buffer) override ;
|
||||
void onError(const SockException &err) override;
|
||||
void onManager() override;
|
||||
void *_user_data;
|
||||
uint16_t _local_port;
|
||||
};
|
||||
|
||||
#endif //MK_TCP_PRIVATE_H
|
76
api/source/mk_thread.cpp
Normal file
76
api/source/mk_thread.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_thread.h"
|
||||
#include "mk_tcp_private.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
API_EXPORT mk_thread API_CALL mk_thread_from_tcp_session(mk_tcp_session ctx){
|
||||
assert(ctx);
|
||||
TcpSession *obj = (TcpSession *)ctx;
|
||||
return obj->getPoller().get();
|
||||
}
|
||||
|
||||
API_EXPORT mk_thread API_CALL mk_thread_from_tcp_client(mk_tcp_client ctx){
|
||||
assert(ctx);
|
||||
TcpClient::Ptr *client = (TcpClient::Ptr *)ctx;
|
||||
return (*client)->getPoller().get();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_async_do(mk_thread ctx,on_mk_async cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
EventPoller *poller = (EventPoller *)ctx;
|
||||
poller->async([cb,user_data](){
|
||||
cb(user_data);
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_sync_do(mk_thread ctx,on_mk_async cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
EventPoller *poller = (EventPoller *)ctx;
|
||||
poller->sync([cb,user_data](){
|
||||
cb(user_data);
|
||||
});
|
||||
}
|
||||
|
||||
API_EXPORT mk_timer API_CALL mk_timer_create(mk_thread ctx,uint64_t delay_ms,on_mk_timer cb, void *user_data){
|
||||
assert(ctx && cb);
|
||||
EventPoller *poller = (EventPoller *)ctx;
|
||||
auto ret = poller->doDelayTask(delay_ms,[cb,user_data](){
|
||||
return cb(user_data);
|
||||
});
|
||||
return new DelayTask::Ptr(ret);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_timer_release(mk_timer ctx){
|
||||
assert(ctx);
|
||||
DelayTask::Ptr *obj = (DelayTask::Ptr *)ctx;
|
||||
(*obj)->cancel();
|
||||
delete obj;
|
||||
}
|
70
api/source/mk_util.cpp
Normal file
70
api/source/mk_util.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mk_util.h"
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include "Util/logger.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
API_EXPORT char* API_CALL mk_util_get_exe_path(){
|
||||
return strdup(exePath().data());
|
||||
}
|
||||
|
||||
API_EXPORT char* API_CALL mk_util_get_exe_dir(const char *relative_path){
|
||||
if(relative_path){
|
||||
return strdup((exeDir() + relative_path).data());
|
||||
}
|
||||
return strdup(exeDir().data());
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_util_get_current_millisecond(){
|
||||
return getCurrentMillisecond();
|
||||
}
|
||||
|
||||
API_EXPORT char* API_CALL mk_util_get_current_time_string(const char *fmt){
|
||||
assert(fmt);
|
||||
return strdup(getTimeStr(fmt).data());
|
||||
}
|
||||
|
||||
API_EXPORT char* API_CALL mk_util_hex_dump(const void *buf, int len){
|
||||
assert(buf && len > 0);
|
||||
return strdup(hexdump(buf,len).data());
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_log_printf(int level, const char *file, const char *function, int line, const char *fmt, ...) {
|
||||
assert(file && function && fmt);
|
||||
LogContextCapturer info(Logger::Instance(), (LogLevel) level, file, function, line);
|
||||
va_list pArg;
|
||||
va_start(pArg, fmt);
|
||||
char buf[4096];
|
||||
int n = vsprintf(buf, fmt, pArg);
|
||||
buf[n] = '\0';
|
||||
va_end(pArg);
|
||||
info << buf;
|
||||
}
|
||||
|
25
api/tests/CMakeLists.txt
Normal file
25
api/tests/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
aux_source_directory(. TEST_SRC_LIST)
|
||||
foreach(TEST_SRC ${TEST_SRC_LIST})
|
||||
STRING(REGEX REPLACE "^\\./|\\.c[a-zA-Z0-9_]*$" "" TEST_EXE_NAME ${TEST_SRC})
|
||||
message(STATUS "add c api tester:${TEST_EXE_NAME}")
|
||||
set(exe_name api_tester_${TEST_EXE_NAME})
|
||||
add_executable(${exe_name} ${TEST_SRC})
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(${exe_name} PROPERTIES COMPILE_FLAGS ${VS_FALGS} )
|
||||
endif(WIN32)
|
||||
|
||||
target_link_libraries(${exe_name} mk_api)
|
||||
endforeach()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
449
api/tests/server.c
Normal file
449
api/tests/server.c
Normal file
@ -0,0 +1,449 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include "mk_mediakit.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "windows.h"
|
||||
#else
|
||||
|
||||
#include "unistd.h"
|
||||
|
||||
#endif
|
||||
|
||||
#define LOG_LEV 4
|
||||
|
||||
/**
|
||||
* 注册或反注册MediaSource事件广播
|
||||
* @param regist 注册为1,注销为0
|
||||
* @param sender 该MediaSource对象
|
||||
*/
|
||||
void API_CALL on_mk_media_changed(int regist,
|
||||
const mk_media_source sender) {
|
||||
log_printf(LOG_LEV,"%d %s/%s/%s/%s",(int)regist,
|
||||
mk_media_source_get_schema(sender),
|
||||
mk_media_source_get_vhost(sender),
|
||||
mk_media_source_get_app(sender),
|
||||
mk_media_source_get_stream(sender));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
||||
* @see mk_publish_auth_invoker_do
|
||||
* @param url_info 推流url相关信息
|
||||
* @param invoker 执行invoker返回鉴权结果
|
||||
* @param sender 该tcp客户端相关信息
|
||||
*/
|
||||
void API_CALL on_mk_media_publish(const mk_media_info url_info,
|
||||
const mk_publish_auth_invoker invoker,
|
||||
const mk_tcp_session sender) {
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"%s/%s/%s/%s, url params: %s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
mk_media_info_get_schema(url_info),
|
||||
mk_media_info_get_vhost(url_info),
|
||||
mk_media_info_get_app(url_info),
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info));
|
||||
|
||||
//允许推流,并且允许转rtxp/hls/mp4
|
||||
mk_publish_auth_invoker_do(invoker, NULL, 1, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放rtsp/rtmp/http-flv/hls事件广播,通过该事件控制播放鉴权
|
||||
* @see mk_auth_invoker_do
|
||||
* @param url_info 播放url相关信息
|
||||
* @param invoker 执行invoker返回鉴权结果
|
||||
* @param sender 播放客户端相关信息
|
||||
*/
|
||||
void API_CALL on_mk_media_play(const mk_media_info url_info,
|
||||
const mk_auth_invoker invoker,
|
||||
const mk_tcp_session sender) {
|
||||
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"%s/%s/%s/%s, url params: %s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
mk_media_info_get_schema(url_info),
|
||||
mk_media_info_get_vhost(url_info),
|
||||
mk_media_info_get_app(url_info),
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info));
|
||||
|
||||
//允许播放
|
||||
mk_auth_invoker_do(invoker, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了
|
||||
* @param url_info 播放url相关信息
|
||||
* @param sender 播放客户端相关信息
|
||||
*/
|
||||
void API_CALL on_mk_media_not_found(const mk_media_info url_info,
|
||||
const mk_tcp_session sender) {
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"%s/%s/%s/%s, url params: %s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
mk_media_info_get_schema(url_info),
|
||||
mk_media_info_get_vhost(url_info),
|
||||
mk_media_info_get_app(url_info),
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑
|
||||
* @param sender 该MediaSource对象
|
||||
*/
|
||||
void API_CALL on_mk_media_no_reader(const mk_media_source sender) {
|
||||
log_printf(LOG_LEV,
|
||||
"%s/%s/%s/%s",
|
||||
mk_media_source_get_schema(sender),
|
||||
mk_media_source_get_vhost(sender),
|
||||
mk_media_source_get_app(sender),
|
||||
mk_media_source_get_stream(sender));
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到http api请求广播(包括GET/POST)
|
||||
* @param parser http请求内容对象
|
||||
* @param invoker 执行该invoker返回http回复
|
||||
* @param consumed 置1则说明我们要处理该事件
|
||||
* @param sender http客户端相关信息
|
||||
*/
|
||||
//测试url : http://127.0.0.1/api/test
|
||||
void API_CALL on_mk_http_request(const mk_parser parser,
|
||||
const mk_http_response_invoker invoker,
|
||||
int *consumed,
|
||||
const mk_tcp_session sender) {
|
||||
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"%s %s?%s %s\n"
|
||||
"User-Agent: %s\n"
|
||||
"%s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
mk_parser_get_method(parser),
|
||||
mk_parser_get_url(parser),
|
||||
mk_parser_get_url_params(parser),
|
||||
mk_parser_get_tail(parser),
|
||||
mk_parser_get_header(parser, "User-Agent"),
|
||||
mk_parser_get_content(parser,NULL));
|
||||
|
||||
const char *url = mk_parser_get_url(parser);
|
||||
if(strcmp(url,"/api/test") != 0){
|
||||
*consumed = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//只拦截api: /api/test
|
||||
*consumed = 1;
|
||||
const char *response_header[] = {"Content-Type","text/html",NULL};
|
||||
const char *content =
|
||||
"<html>"
|
||||
"<head>"
|
||||
"<title>hello world</title>"
|
||||
"</head>"
|
||||
"<body bgcolor=\"white\">"
|
||||
"<center><h1>hello world</h1></center><hr>"
|
||||
"<center>""ZLMediaKit-4.0</center>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
mk_http_body body = mk_http_body_from_string(content,0);
|
||||
mk_http_response_invoker_do(invoker, "200 OK", response_header, body);
|
||||
mk_http_body_release(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
|
||||
* @param parser http请求内容对象
|
||||
* @param path 文件绝对路径
|
||||
* @param is_dir path是否为文件夹
|
||||
* @param invoker 执行invoker返回本次访问文件的结果
|
||||
* @param sender http客户端相关信息
|
||||
*/
|
||||
void API_CALL on_mk_http_access(const mk_parser parser,
|
||||
const char *path,
|
||||
int is_dir,
|
||||
const mk_http_access_path_invoker invoker,
|
||||
mk_tcp_session sender) {
|
||||
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d, path: %s ,is_dir: %d\n"
|
||||
"%s %s?%s %s\n"
|
||||
"User-Agent: %s\n"
|
||||
"%s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
path,(int)is_dir,
|
||||
mk_parser_get_method(parser),
|
||||
mk_parser_get_url(parser),
|
||||
mk_parser_get_url_params(parser),
|
||||
mk_parser_get_tail(parser),
|
||||
mk_parser_get_header(parser,"User-Agent"),
|
||||
mk_parser_get_content(parser,NULL));
|
||||
|
||||
//有访问权限,每次访问文件都需要鉴权
|
||||
mk_http_access_path_invoker_do(invoker, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
|
||||
* 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的
|
||||
* @param parser http请求内容对象
|
||||
* @param path 文件绝对路径,覆盖之可以重定向到其他文件
|
||||
* @param sender http客户端相关信息
|
||||
*/
|
||||
void API_CALL on_mk_http_before_access(const mk_parser parser,
|
||||
char *path,
|
||||
const mk_tcp_session sender) {
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d, path: %s\n"
|
||||
"%s %s?%s %s\n"
|
||||
"User-Agent: %s\n"
|
||||
"%s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
path,
|
||||
mk_parser_get_method(parser),
|
||||
mk_parser_get_url(parser),
|
||||
mk_parser_get_url_params(parser),
|
||||
mk_parser_get_tail(parser),
|
||||
mk_parser_get_header(parser, "User-Agent"),
|
||||
mk_parser_get_content(parser,NULL));
|
||||
//覆盖path的值可以重定向文件
|
||||
}
|
||||
|
||||
/**
|
||||
* 该rtsp流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm
|
||||
* @param url_info 请求rtsp url相关信息
|
||||
* @param invoker 执行invoker返回是否需要rtsp专属认证
|
||||
* @param sender rtsp客户端相关信息
|
||||
*/
|
||||
void API_CALL on_mk_rtsp_get_realm(const mk_media_info url_info,
|
||||
const mk_rtsp_get_realm_invoker invoker,
|
||||
const mk_tcp_session sender) {
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"%s/%s/%s/%s, url params: %s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
mk_media_info_get_schema(url_info),
|
||||
mk_media_info_get_vhost(url_info),
|
||||
mk_media_info_get_app(url_info),
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info));
|
||||
|
||||
//rtsp播放默认鉴权
|
||||
mk_rtsp_get_realm_invoker_do(invoker, "zlmediakit");
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为1,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
|
||||
* 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码
|
||||
* @param url_info 请求rtsp url相关信息
|
||||
* @param realm rtsp认证realm
|
||||
* @param user_name rtsp认证用户名
|
||||
* @param must_no_encrypt 如果为1,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
|
||||
* @param invoker 执行invoker返回rtsp专属认证的密码
|
||||
* @param sender rtsp客户端信息
|
||||
*/
|
||||
void API_CALL on_mk_rtsp_auth(const mk_media_info url_info,
|
||||
const char *realm,
|
||||
const char *user_name,
|
||||
int must_no_encrypt,
|
||||
const mk_rtsp_auth_invoker invoker,
|
||||
const mk_tcp_session sender) {
|
||||
|
||||
log_printf(LOG_LEV,
|
||||
"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"%s/%s/%s/%s, url params: %s\n"
|
||||
"realm: %s, user_name: %s, must_no_encrypt: %d",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
mk_media_info_get_schema(url_info),
|
||||
mk_media_info_get_vhost(url_info),
|
||||
mk_media_info_get_app(url_info),
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info),
|
||||
realm,user_name,(int)must_no_encrypt);
|
||||
|
||||
//rtsp播放用户名跟密码一致
|
||||
mk_rtsp_auth_invoker_do(invoker,0,user_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 录制mp4分片文件成功后广播
|
||||
*/
|
||||
void API_CALL on_mk_record_mp4(const mk_mp4_info mp4) {
|
||||
log_printf(LOG_LEV,
|
||||
"\nstart_time: %d\n"
|
||||
"time_len: %d\n"
|
||||
"file_size: %d\n"
|
||||
"file_path: %s\n"
|
||||
"file_name: %s\n"
|
||||
"folder: %s\n"
|
||||
"url: %s\n"
|
||||
"vhost: %s\n"
|
||||
"app: %s\n"
|
||||
"stream: %s\n",
|
||||
mk_mp4_info_get_start_time(mp4),
|
||||
mk_mp4_info_get_time_len(mp4),
|
||||
mk_mp4_info_get_file_size(mp4),
|
||||
mk_mp4_info_get_file_path(mp4),
|
||||
mk_mp4_info_get_file_name(mp4),
|
||||
mk_mp4_info_get_folder(mp4),
|
||||
mk_mp4_info_get_url(mp4),
|
||||
mk_mp4_info_get_vhost(mp4),
|
||||
mk_mp4_info_get_app(mp4),
|
||||
mk_mp4_info_get_stream(mp4));
|
||||
}
|
||||
|
||||
/**
|
||||
* shell登录鉴权
|
||||
*/
|
||||
void API_CALL on_mk_shell_login(const char *user_name,
|
||||
const char *passwd,
|
||||
const mk_auth_invoker invoker,
|
||||
const mk_tcp_session sender) {
|
||||
log_printf(LOG_LEV,"client info, local: %s:%d, peer: %s:%d\n"
|
||||
"user_name: %s, passwd: %s",
|
||||
mk_tcp_session_local_ip(sender),
|
||||
mk_tcp_session_local_port(sender),
|
||||
mk_tcp_session_peer_ip(sender),
|
||||
mk_tcp_session_peer_port(sender),
|
||||
user_name, passwd);
|
||||
//允许登录shell
|
||||
mk_auth_invoker_do(invoker, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止rtsp/rtmp/http-flv会话后流量汇报事件广播
|
||||
* @param url_info 播放url相关信息
|
||||
* @param total_bytes 耗费上下行总流量,单位字节数
|
||||
* @param total_seconds 本次tcp会话时长,单位秒
|
||||
* @param is_player 客户端是否为播放器
|
||||
* @param peer_ip 客户端ip
|
||||
* @param peer_port 客户端端口号
|
||||
*/
|
||||
void API_CALL on_mk_flow_report(const mk_media_info url_info,
|
||||
uint64_t total_bytes,
|
||||
uint64_t total_seconds,
|
||||
int is_player,
|
||||
const char *peer_ip,
|
||||
uint16_t peer_port) {
|
||||
log_printf(LOG_LEV,"%s/%s/%s/%s, url params: %s,"
|
||||
"total_bytes: %d, total_seconds: %d, is_player: %d, peer_ip:%s, peer_port:%d",
|
||||
mk_media_info_get_schema(url_info),
|
||||
mk_media_info_get_vhost(url_info),
|
||||
mk_media_info_get_app(url_info),
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info),
|
||||
(int)total_bytes, (int)total_seconds, (int)is_player,peer_ip, (int)peer_port);
|
||||
}
|
||||
|
||||
static int flag = 1;
|
||||
static void s_on_exit(int sig){
|
||||
flag = 0;
|
||||
}
|
||||
int main(int argc, char *argv[]) {
|
||||
char *ini_path = mk_util_get_exe_dir("c_api.ini");
|
||||
char *ssl_path = mk_util_get_exe_dir("ssl.p12");
|
||||
|
||||
mk_config config = {
|
||||
.ini = ini_path,
|
||||
.ini_is_path = 1,
|
||||
.log_level = 0,
|
||||
.ssl = ssl_path,
|
||||
.ssl_is_path = 1,
|
||||
.ssl_pwd = NULL,
|
||||
.thread_num = 0
|
||||
};
|
||||
mk_env_init(&config);
|
||||
free(ini_path);
|
||||
free(ssl_path);
|
||||
|
||||
mk_http_server_start(80, 0);
|
||||
mk_http_server_start(443, 1);
|
||||
mk_rtsp_server_start(554, 0);
|
||||
mk_rtmp_server_start(1935, 0);
|
||||
mk_shell_server_start(9000);
|
||||
mk_rtp_server_start(10000);
|
||||
|
||||
mk_events events = {
|
||||
.on_mk_media_changed = on_mk_media_changed,
|
||||
.on_mk_media_publish = on_mk_media_publish,
|
||||
.on_mk_media_play = on_mk_media_play,
|
||||
.on_mk_media_not_found = on_mk_media_not_found,
|
||||
.on_mk_media_no_reader = on_mk_media_no_reader,
|
||||
.on_mk_http_request = on_mk_http_request,
|
||||
.on_mk_http_access = on_mk_http_access,
|
||||
.on_mk_http_before_access = on_mk_http_before_access,
|
||||
.on_mk_rtsp_get_realm = on_mk_rtsp_get_realm,
|
||||
.on_mk_rtsp_auth = on_mk_rtsp_auth,
|
||||
.on_mk_record_mp4 = on_mk_record_mp4,
|
||||
.on_mk_shell_login = on_mk_shell_login,
|
||||
.on_mk_flow_report = on_mk_flow_report
|
||||
};
|
||||
mk_events_listen(&events);
|
||||
|
||||
signal(SIGINT, s_on_exit );// 设置退出信号
|
||||
while (flag) {
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
}
|
||||
mk_stop_all_server();
|
||||
return 0;
|
||||
}
|
227
api/tests/websocket.c
Normal file
227
api/tests/websocket.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "mk_mediakit.h"
|
||||
#ifdef _WIN32
|
||||
#include "windows.h"
|
||||
#else
|
||||
#include "unistd.h"
|
||||
#endif
|
||||
|
||||
#define LOG_LEV 4
|
||||
//修改此宏,可以选择协议类型
|
||||
#define TCP_TYPE mk_type_ws
|
||||
|
||||
static int flag = 1;
|
||||
static void s_on_exit(int sig){
|
||||
flag = 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
typedef struct {
|
||||
mk_tcp_session _session;
|
||||
//下面你可以夹杂你的私货数据
|
||||
char your_some_useful_data[1024];
|
||||
} tcp_session_user_data;
|
||||
/**
|
||||
* 当tcp客户端连接服务器时触发
|
||||
* @param session 会话处理对象
|
||||
*/
|
||||
void API_CALL on_mk_tcp_session_create(uint16_t server_port,mk_tcp_session session){
|
||||
log_printf(LOG_LEV,"%s %d",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session));
|
||||
tcp_session_user_data *user_data = malloc(sizeof(tcp_session_user_data));
|
||||
user_data->_session = session;
|
||||
mk_tcp_session_set_user_data(session,user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到tcp客户端发过来的数据
|
||||
* @param session 会话处理对象
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
*/
|
||||
void API_CALL on_mk_tcp_session_data(uint16_t server_port,mk_tcp_session session,const char *data,int len){
|
||||
log_printf(LOG_LEV,"from %s %d, data[%d]: %s",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session), len,data);
|
||||
mk_tcp_session_send(session,"echo:",0);
|
||||
mk_tcp_session_send(session,data,len);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每隔2秒的定时器,用于管理超时等任务
|
||||
* @param session 会话处理对象
|
||||
*/
|
||||
void API_CALL on_mk_tcp_session_manager(uint16_t server_port,mk_tcp_session session){
|
||||
log_printf(LOG_LEV,"%s %d",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session));
|
||||
}
|
||||
|
||||
/**
|
||||
* 一般由于客户端断开tcp触发
|
||||
* 本事件中可以调用mk_tcp_session_send_safe函数
|
||||
* @param session 会话处理对象
|
||||
* @param code 错误代码
|
||||
* @param msg 错误提示
|
||||
*/
|
||||
void API_CALL on_mk_tcp_session_disconnect(uint16_t server_port,mk_tcp_session session,int code,const char *msg){
|
||||
log_printf(LOG_LEV,"%s %d: %d %s",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session),code,msg);
|
||||
tcp_session_user_data *user_data = (tcp_session_user_data *)mk_tcp_session_get_user_data(session);
|
||||
free(user_data);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
typedef struct {
|
||||
mk_tcp_client client;
|
||||
//下面你可以夹杂你的私货数据
|
||||
char your_some_useful_data[1024];
|
||||
int count;
|
||||
} tcp_client_user_data;
|
||||
|
||||
/**
|
||||
* tcp客户端连接服务器成功或失败回调
|
||||
* @param client tcp客户端
|
||||
* @param code 0为连接成功,否则为失败原因
|
||||
* @param msg 连接失败错误提示
|
||||
*/
|
||||
void API_CALL on_mk_tcp_client_connect(mk_tcp_client client,int code,const char *msg){
|
||||
log_printf(LOG_LEV,"connect result:%d %s",code,msg);
|
||||
if(code == 0){
|
||||
//连接上后我们发送一个hello world测试数据
|
||||
mk_tcp_client_send(client,"hello world",0);
|
||||
}else{
|
||||
tcp_client_user_data *user_data = mk_tcp_client_get_user_data(client);
|
||||
mk_tcp_client_release(client);
|
||||
free(user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tcp客户端与tcp服务器之间断开回调
|
||||
* 一般是eof事件导致
|
||||
* @param client tcp客户端
|
||||
* @param code 错误代码
|
||||
* @param msg 错误提示
|
||||
*/
|
||||
void API_CALL on_mk_tcp_client_disconnect(mk_tcp_client client,int code,const char *msg){
|
||||
log_printf(LOG_LEV,"disconnect:%d %s",code,msg);
|
||||
//服务器主动断开了,我们销毁对象
|
||||
tcp_client_user_data *user_data = mk_tcp_client_get_user_data(client);
|
||||
mk_tcp_client_release(client);
|
||||
free(user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到tcp服务器发来的数据
|
||||
* @param client tcp客户端
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
*/
|
||||
void API_CALL on_mk_tcp_client_data(mk_tcp_client client,const char *data,int len){
|
||||
log_printf(LOG_LEV,"data[%d]:%s",len,data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每隔2秒的定时器,用于管理超时等任务
|
||||
* @param client tcp客户端
|
||||
*/
|
||||
void API_CALL on_mk_tcp_client_manager(mk_tcp_client client){
|
||||
tcp_client_user_data *user_data = mk_tcp_client_get_user_data(client);
|
||||
char buf[1024];
|
||||
sprintf(buf,"on_mk_tcp_client_manager:%d",user_data->count);
|
||||
mk_tcp_client_send(client,buf,0);
|
||||
|
||||
if(++user_data->count == 5){
|
||||
//发送5遍后主动释放对象
|
||||
mk_tcp_client_release(client);
|
||||
free(user_data);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void test_server(){
|
||||
mk_tcp_session_events events_server = {
|
||||
.on_mk_tcp_session_create = on_mk_tcp_session_create,
|
||||
.on_mk_tcp_session_data = on_mk_tcp_session_data,
|
||||
.on_mk_tcp_session_manager = on_mk_tcp_session_manager,
|
||||
.on_mk_tcp_session_disconnect = on_mk_tcp_session_disconnect
|
||||
};
|
||||
|
||||
mk_tcp_server_events_listen(&events_server);
|
||||
mk_tcp_server_start(80, TCP_TYPE);
|
||||
}
|
||||
|
||||
void test_client(){
|
||||
mk_tcp_client_events events_clent = {
|
||||
.on_mk_tcp_client_connect = on_mk_tcp_client_connect,
|
||||
.on_mk_tcp_client_data = on_mk_tcp_client_data,
|
||||
.on_mk_tcp_client_disconnect = on_mk_tcp_client_disconnect,
|
||||
.on_mk_tcp_client_manager = on_mk_tcp_client_manager,
|
||||
};
|
||||
mk_tcp_client client = mk_tcp_client_create(&events_clent, TCP_TYPE);
|
||||
|
||||
tcp_client_user_data *user_data = (tcp_client_user_data *)malloc(sizeof(tcp_client_user_data));
|
||||
user_data->client = client;
|
||||
user_data->count = 0;
|
||||
mk_tcp_client_set_user_data(client,user_data);
|
||||
|
||||
mk_tcp_client_connect(client, "121.40.165.18", 8800, 3);
|
||||
//你可以连接127.0.0.1 80测试
|
||||
// mk_tcp_client_connect(client, "127.0.0.1", 80, 3);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char *ini_path = mk_util_get_exe_dir("c_api.ini");
|
||||
char *ssl_path = mk_util_get_exe_dir("ssl.p12");
|
||||
|
||||
mk_config config = {
|
||||
.ini = ini_path,
|
||||
.ini_is_path = 1,
|
||||
.log_level = 0,
|
||||
.ssl = ssl_path,
|
||||
.ssl_is_path = 1,
|
||||
.ssl_pwd = NULL,
|
||||
.thread_num = 0
|
||||
};
|
||||
mk_env_init(&config);
|
||||
free(ini_path);
|
||||
free(ssl_path);
|
||||
|
||||
test_server();
|
||||
test_client();
|
||||
|
||||
signal(SIGINT, s_on_exit );// 设置退出信号
|
||||
while (flag) {
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
}
|
||||
mk_stop_all_server();
|
||||
return 0;
|
||||
}
|
3
build_docker_images.sh
Normal file
3
build_docker_images.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
docker build -t gemfield/zlmediakit:20.01-runtime-ubuntu18.04 -f docker/ubuntu18.04/Dockerfile.runtime .
|
||||
#docker build -t gemfield/zlmediakit:20.01-devel-ubuntu18.04 -f docker/ubuntu18.04/Dockerfile.devel .
|
@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
cd ..
|
||||
git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit.git
|
||||
cd ZLMediaKit
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
mkdir -p ios_build
|
||||
rm -rf ./build
|
||||
ln -s ./ios_build build
|
||||
cd ios_build
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=OS
|
||||
make -j4
|
@ -1,18 +0,0 @@
|
||||
find_path(ZLTOOLKIT_INCLUDE_DIR
|
||||
NAMES Network/Socket.h
|
||||
PATHS
|
||||
${PROJECT_SOURCE_DIR}/../ZLToolKit/src
|
||||
$ENV{HOME}/ZLToolKit/include)
|
||||
|
||||
find_library(ZLTOOLKIT_LIBRARY
|
||||
NAMES ZLToolKit
|
||||
PATHS
|
||||
${PROJECT_SOURCE_DIR}/../ZLToolKit/build/lib
|
||||
$ENV{HOME}/ZLToolKit/lib)
|
||||
|
||||
set(ZLTOOLKIT_LIBRARIES ${ZLTOOLKIT_LIBRARY})
|
||||
set(ZLTOOLKIT_INCLUDE_DIRS ${ZLTOOLKIT_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(ZLTOOLKIT DEFAULT_MSG ZLTOOLKIT_LIBRARY ZLTOOLKIT_INCLUDE_DIR)
|
209
cmake/iOS.cmake
209
cmake/iOS.cmake
@ -1,209 +0,0 @@
|
||||
# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake
|
||||
# files which are included with CMake 2.8.4
|
||||
# It has been altered for iOS development
|
||||
|
||||
# Options:
|
||||
#
|
||||
# IOS_PLATFORM = OS (default) or SIMULATOR or SIMULATOR64
|
||||
# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders
|
||||
# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch.
|
||||
# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch.
|
||||
#
|
||||
# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder
|
||||
# By default this location is automatcially chosen based on the IOS_PLATFORM value above.
|
||||
# If set manually, it will override the default location and force the user of a particular Developer Platform
|
||||
#
|
||||
# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder
|
||||
# By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value.
|
||||
# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path.
|
||||
# If set manually, this will force the use of a specific SDK version
|
||||
|
||||
# Macros:
|
||||
#
|
||||
# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE)
|
||||
# A convenience macro for setting xcode specific properties on targets
|
||||
# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1")
|
||||
#
|
||||
# find_host_package (PROGRAM ARGS)
|
||||
# A macro used to find executable programs on the host system, not within the iOS environment.
|
||||
# Thanks to the android-cmake project for providing the command
|
||||
|
||||
# Standard settings
|
||||
set (CMAKE_SYSTEM_NAME Darwin)
|
||||
set (CMAKE_SYSTEM_VERSION 1)
|
||||
set (UNIX True)
|
||||
set (APPLE True)
|
||||
set (IOS True)
|
||||
|
||||
# Required as of cmake 2.8.10
|
||||
set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE)
|
||||
|
||||
# Determine the cmake host system version so we know where to find the iOS SDKs
|
||||
find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin)
|
||||
if (CMAKE_UNAME)
|
||||
exec_program(uname ARGS -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION)
|
||||
string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}")
|
||||
endif (CMAKE_UNAME)
|
||||
|
||||
# Force the compilers to gcc for iOS
|
||||
include (CMakeForceCompiler)
|
||||
#CMAKE_FORCE_C_COMPILER (/usr/bin/gcc Apple)
|
||||
set(CMAKE_C_COMPILER /usr/bin/clang)
|
||||
#CMAKE_FORCE_CXX_COMPILER (/usr/bin/g++ Apple)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
|
||||
set(CMAKE_AR ar CACHE FILEPATH "" FORCE)
|
||||
|
||||
# Skip the platform compiler checks for cross compiling
|
||||
set (CMAKE_CXX_COMPILER_WORKS TRUE)
|
||||
set (CMAKE_C_COMPILER_WORKS TRUE)
|
||||
|
||||
# All iOS/Darwin specific settings - some may be redundant
|
||||
set (CMAKE_SHARED_LIBRARY_PREFIX "lib")
|
||||
set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib")
|
||||
set (CMAKE_SHARED_MODULE_PREFIX "lib")
|
||||
set (CMAKE_SHARED_MODULE_SUFFIX ".so")
|
||||
set (CMAKE_MODULE_EXISTS 1)
|
||||
set (CMAKE_DL_LIBS "")
|
||||
|
||||
set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ")
|
||||
set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ")
|
||||
set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}")
|
||||
set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}")
|
||||
|
||||
# Hidden visibilty is required for cxx on iOS
|
||||
set (CMAKE_C_FLAGS_INIT "")
|
||||
set (CMAKE_CXX_FLAGS_INIT "-fvisibility=hidden -fvisibility-inlines-hidden")
|
||||
|
||||
set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}")
|
||||
set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}")
|
||||
|
||||
set (CMAKE_PLATFORM_HAS_INSTALLNAME 1)
|
||||
set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names")
|
||||
set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names")
|
||||
set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,")
|
||||
set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,")
|
||||
set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a")
|
||||
|
||||
# hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree
|
||||
# (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache
|
||||
# and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun)
|
||||
# hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex
|
||||
if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL)
|
||||
find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool)
|
||||
endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL)
|
||||
|
||||
# Setup iOS platform unless specified manually with IOS_PLATFORM
|
||||
if (NOT DEFINED IOS_PLATFORM)
|
||||
set (IOS_PLATFORM "OS")
|
||||
endif (NOT DEFINED IOS_PLATFORM)
|
||||
set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform")
|
||||
|
||||
# Setup building for arm64 or not
|
||||
if (NOT DEFINED BUILD_ARM64)
|
||||
set (BUILD_ARM64 true)
|
||||
endif (NOT DEFINED BUILD_ARM64)
|
||||
set (BUILD_ARM64 ${BUILD_ARM64} CACHE STRING "Build arm64 arch or not")
|
||||
|
||||
# Check the platform selection and setup for developer root
|
||||
if (${IOS_PLATFORM} STREQUAL "OS")
|
||||
set (IOS_PLATFORM_LOCATION "iPhoneOS.platform")
|
||||
|
||||
# This causes the installers to properly locate the output libraries
|
||||
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos")
|
||||
elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR")
|
||||
set (SIMULATOR true)
|
||||
set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
|
||||
|
||||
# This causes the installers to properly locate the output libraries
|
||||
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
|
||||
elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR64")
|
||||
set (SIMULATOR true)
|
||||
set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
|
||||
|
||||
# This causes the installers to properly locate the output libraries
|
||||
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
|
||||
else (${IOS_PLATFORM} STREQUAL "OS")
|
||||
message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR")
|
||||
endif (${IOS_PLATFORM} STREQUAL "OS")
|
||||
|
||||
# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT
|
||||
# Note Xcode 4.3 changed the installation location, choose the most recent one available
|
||||
exec_program(/usr/bin/xcode-select ARGS -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR)
|
||||
set (XCODE_POST_43_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
|
||||
set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
|
||||
if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
|
||||
if (EXISTS ${XCODE_POST_43_ROOT})
|
||||
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT})
|
||||
elseif(EXISTS ${XCODE_PRE_43_ROOT})
|
||||
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT})
|
||||
endif (EXISTS ${XCODE_POST_43_ROOT})
|
||||
endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
|
||||
set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform")
|
||||
|
||||
# Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT
|
||||
if (NOT DEFINED CMAKE_IOS_SDK_ROOT)
|
||||
file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*")
|
||||
if (_CMAKE_IOS_SDKS)
|
||||
list (SORT _CMAKE_IOS_SDKS)
|
||||
list (REVERSE _CMAKE_IOS_SDKS)
|
||||
list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT)
|
||||
else (_CMAKE_IOS_SDKS)
|
||||
message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.")
|
||||
endif (_CMAKE_IOS_SDKS)
|
||||
message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}")
|
||||
endif (NOT DEFINED CMAKE_IOS_SDK_ROOT)
|
||||
set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK")
|
||||
|
||||
# Set the sysroot default to the most recent SDK
|
||||
set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support")
|
||||
|
||||
# set the architecture for iOS
|
||||
if (${IOS_PLATFORM} STREQUAL "OS")
|
||||
set (IOS_ARCH armv7 armv7s arm64)
|
||||
elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR")
|
||||
set (IOS_ARCH i386)
|
||||
elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR64")
|
||||
set (IOS_ARCH x86_64)
|
||||
endif (${IOS_PLATFORM} STREQUAL "OS")
|
||||
|
||||
set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE string "Build architecture for iOS")
|
||||
|
||||
# Set the find root to the iOS developer roots and to user defined paths
|
||||
set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE string "iOS find search path root")
|
||||
|
||||
# default to searching for frameworks first
|
||||
set (CMAKE_FIND_FRAMEWORK FIRST)
|
||||
|
||||
# set up the default search directories for frameworks
|
||||
set (CMAKE_SYSTEM_FRAMEWORK_PATH
|
||||
${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks
|
||||
${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks
|
||||
${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks
|
||||
)
|
||||
|
||||
# only search the iOS sdks, not the remainder of the host filesystem
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
|
||||
# This little macro lets you set any XCode specific property
|
||||
macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE)
|
||||
set_property (TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE})
|
||||
endmacro (set_xcode_property)
|
||||
|
||||
|
||||
# This macro lets you find executable programs on the host system
|
||||
macro (find_host_package)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
|
||||
set (IOS FALSE)
|
||||
|
||||
find_package(${ARGN})
|
||||
|
||||
set (IOS TRUE)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endmacro (find_host_package)
|
674
cmake/ios.toolchain.cmake
Normal file
674
cmake/ios.toolchain.cmake
Normal file
@ -0,0 +1,674 @@
|
||||
# This file is part of the ios-cmake project. It was retrieved from
|
||||
# https://github.com/cristeab/ios-cmake.git, which is a fork of
|
||||
# https://code.google.com/p/ios-cmake/. Which in turn is based off of
|
||||
# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which
|
||||
# are included with CMake 2.8.4
|
||||
#
|
||||
# The ios-cmake project is licensed under the new BSD license.
|
||||
#
|
||||
# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software,
|
||||
# Kitware, Inc., Insight Software Consortium. All rights reserved.
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# This file is based off of the Platform/Darwin.cmake and
|
||||
# Platform/UnixPaths.cmake files which are included with CMake 2.8.4
|
||||
# It has been altered for iOS development.
|
||||
#
|
||||
# Updated by Alex Stewart (alexs.mac@gmail.com)
|
||||
#
|
||||
# *****************************************************************************
|
||||
# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com)
|
||||
# under the BSD-3-Clause license
|
||||
# https://github.com/leetal/ios-cmake
|
||||
# *****************************************************************************
|
||||
#
|
||||
# INFORMATION / HELP
|
||||
#
|
||||
# The following arguments control the behaviour of this toolchain:
|
||||
#
|
||||
# PLATFORM: (default "OS")
|
||||
# OS = Build for iPhoneOS.
|
||||
# OS64 = Build for arm64 iphoneOS.
|
||||
# OS64COMBINED = Build for arm64 x86_64 iphoneOS. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY)
|
||||
# SIMULATOR = Build for x86 i386 iphoneOS Simulator.
|
||||
# SIMULATOR64 = Build for x86_64 iphoneOS Simulator.
|
||||
# TVOS = Build for arm64 tvOS.
|
||||
# TVOSCOMBINED = Build for arm64 x86_64 tvOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY)
|
||||
# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator.
|
||||
# WATCHOS = Build for armv7k arm64_32 for watchOS.
|
||||
# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY)
|
||||
# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator.
|
||||
#
|
||||
# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is
|
||||
# automatically determined from PLATFORM and xcodebuild, but
|
||||
# can also be manually specified (although this should not be required).
|
||||
#
|
||||
# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform
|
||||
# being compiled for. By default this is automatically determined from
|
||||
# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should
|
||||
# not be required).
|
||||
#
|
||||
# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 2.0 on watchOS and 9.0 on tvOS+iOS
|
||||
#
|
||||
# ENABLE_BITCODE: (1|0) Enables or disables bitcode support. Default 1 (true)
|
||||
#
|
||||
# ENABLE_ARC: (1|0) Enables or disables ARC support. Default 1 (true, ARC enabled by default)
|
||||
#
|
||||
# ENABLE_VISIBILITY: (1|0) Enables or disables symbol visibility support. Default 0 (false, visibility hidden by default)
|
||||
#
|
||||
# ENABLE_STRICT_TRY_COMPILE: (1|0) Enables or disables strict try_compile() on all Check* directives (will run linker
|
||||
# to actually check if linking is possible). Default 0 (false, will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY)
|
||||
#
|
||||
# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM
|
||||
# OS = armv7 armv7s arm64 (if applicable)
|
||||
# OS64 = arm64 (if applicable)
|
||||
# SIMULATOR = i386
|
||||
# SIMULATOR64 = x86_64
|
||||
# TVOS = arm64
|
||||
# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated)
|
||||
# WATCHOS = armv7k arm64_32 (if applicable)
|
||||
# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated)
|
||||
#
|
||||
# This toolchain defines the following variables for use externally:
|
||||
#
|
||||
# XCODE_VERSION: Version number (not including Build version) of Xcode detected.
|
||||
# SDK_VERSION: Version of SDK being used.
|
||||
# CMAKE_OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM).
|
||||
#
|
||||
# This toolchain defines the following macros for use externally:
|
||||
#
|
||||
# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT)
|
||||
# A convenience macro for setting xcode specific properties on targets.
|
||||
# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel
|
||||
# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all").
|
||||
#
|
||||
# find_host_package (PROGRAM ARGS)
|
||||
# A macro used to find executable programs on the host system, not within the
|
||||
# environment. Thanks to the android-cmake project for providing the
|
||||
# command.
|
||||
#
|
||||
# ******************************** DEPRECATIONS *******************************
|
||||
#
|
||||
# IOS_DEPLOYMENT_TARGET: (Deprecated) Alias to DEPLOYMENT_TARGET
|
||||
# CMAKE_IOS_DEVELOPER_ROOT: (Deprecated) Alias to CMAKE_DEVELOPER_ROOT
|
||||
# IOS_PLATFORM: (Deprecated) Alias to PLATFORM
|
||||
# IOS_ARCH: (Deprecated) Alias to ARCHS
|
||||
#
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
# Fix for PThread library not in path
|
||||
set(CMAKE_THREAD_LIBS_INIT "-lpthread")
|
||||
set(CMAKE_HAVE_THREADS_LIBRARY 1)
|
||||
set(CMAKE_USE_WIN32_THREADS_INIT 0)
|
||||
set(CMAKE_USE_PTHREADS_INIT 1)
|
||||
|
||||
# Cache what generator is used
|
||||
set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}" CACHE STRING "Expose CMAKE_GENERATOR" FORCE)
|
||||
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14")
|
||||
set(MODERN_CMAKE YES)
|
||||
message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!")
|
||||
endif()
|
||||
|
||||
# Get the Xcode version being used.
|
||||
execute_process(COMMAND xcodebuild -version
|
||||
OUTPUT_VARIABLE XCODE_VERSION
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION "${XCODE_VERSION}")
|
||||
string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION "${XCODE_VERSION}")
|
||||
message(STATUS "Building with Xcode version: ${XCODE_VERSION}")
|
||||
|
||||
######## ALIASES (DEPRECATION WARNINGS)
|
||||
|
||||
if(DEFINED IOS_PLATFORM)
|
||||
set(PLATFORM ${IOS_PLATFORM})
|
||||
message(DEPRECATION "IOS_PLATFORM argument is DEPRECATED. Consider using the new PLATFORM argument instead.")
|
||||
endif()
|
||||
|
||||
if(DEFINED IOS_DEPLOYMENT_TARGET)
|
||||
set(DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET})
|
||||
message(DEPRECATION "IOS_DEPLOYMENT_TARGET argument is DEPRECATED. Consider using the new DEPLOYMENT_TARGET argument instead.")
|
||||
endif()
|
||||
|
||||
if(DEFINED CMAKE_IOS_DEVELOPER_ROOT)
|
||||
set(CMAKE_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT})
|
||||
message(DEPRECATION "CMAKE_IOS_DEVELOPER_ROOT argument is DEPRECATED. Consider using the new CMAKE_DEVELOPER_ROOT argument instead.")
|
||||
endif()
|
||||
|
||||
if(DEFINED IOS_ARCH)
|
||||
set(ARCHS ${IOS_ARCH})
|
||||
message(DEPRECATION "IOS_ARCH argument is DEPRECATED. Consider using the new ARCHS argument instead.")
|
||||
endif()
|
||||
|
||||
######## END ALIASES
|
||||
|
||||
# Unset the FORCE on cache variables if in try_compile()
|
||||
set(FORCE_CACHE FORCE)
|
||||
get_property(_CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE)
|
||||
if(_CMAKE_IN_TRY_COMPILE)
|
||||
unset(FORCE_CACHE)
|
||||
endif()
|
||||
|
||||
# Default to building for iPhoneOS if not specified otherwise, and we cannot
|
||||
# determine the platform from the CMAKE_OSX_ARCHITECTURES variable. The use
|
||||
# of CMAKE_OSX_ARCHITECTURES is such that try_compile() projects can correctly
|
||||
# determine the value of PLATFORM from the root project, as
|
||||
# CMAKE_OSX_ARCHITECTURES is propagated to them by CMake.
|
||||
if(NOT DEFINED PLATFORM)
|
||||
if (CMAKE_OSX_ARCHITECTURES)
|
||||
if(CMAKE_OSX_ARCHITECTURES MATCHES ".*arm.*" AND CMAKE_OSX_SYSROOT MATCHES ".*iphoneos.*")
|
||||
set(PLATFORM "OS")
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*")
|
||||
set(PLATFORM "SIMULATOR")
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*")
|
||||
set(PLATFORM "SIMULATOR64")
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvos.*")
|
||||
set(PLATFORM "TVOS")
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvsimulator.*")
|
||||
set(PLATFORM "SIMULATOR_TVOS")
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES ".*armv7k.*" AND CMAKE_OSX_SYSROOT MATCHES ".*watchos.*")
|
||||
set(PLATFORM "WATCHOS")
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*watchsimulator.*")
|
||||
set(PLATFORM "SIMULATOR_WATCHOS")
|
||||
endif()
|
||||
endif()
|
||||
if (NOT PLATFORM)
|
||||
set(PLATFORM "OS")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(PLATFORM_INT "${PLATFORM}" CACHE STRING "Type of platform for which the build targets.")
|
||||
|
||||
# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially)
|
||||
if(PLATFORM_INT STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4)
|
||||
set(PLATFORM_INT "OS64")
|
||||
message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.")
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4)
|
||||
set(PLATFORM_INT "SIMULATOR64")
|
||||
message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.")
|
||||
endif()
|
||||
|
||||
# Determine the platform name and architectures for use in xcodebuild commands
|
||||
# from the specified PLATFORM name.
|
||||
if(PLATFORM_INT STREQUAL "OS")
|
||||
set(SDK_NAME iphoneos)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS armv7 armv7s arm64)
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "OS64")
|
||||
set(SDK_NAME iphoneos)
|
||||
if(NOT ARCHS)
|
||||
if (XCODE_VERSION VERSION_GREATER 10.0)
|
||||
set(ARCHS arm64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example
|
||||
else()
|
||||
set(ARCHS arm64)
|
||||
endif()
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "OS64COMBINED")
|
||||
set(SDK_NAME iphoneos)
|
||||
if(MODERN_CMAKE)
|
||||
if(NOT ARCHS)
|
||||
if (XCODE_VERSION VERSION_GREATER 10.0)
|
||||
set(ARCHS arm64 x86_64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example
|
||||
else()
|
||||
set(ARCHS arm64 x86_64)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work")
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR")
|
||||
set(SDK_NAME iphonesimulator)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS i386)
|
||||
endif()
|
||||
message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.")
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR64")
|
||||
set(SDK_NAME iphonesimulator)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS x86_64)
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "TVOS")
|
||||
set(SDK_NAME appletvos)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS arm64)
|
||||
endif()
|
||||
elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED")
|
||||
set(SDK_NAME appletvos)
|
||||
if(MODERN_CMAKE)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS arm64 x86_64)
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work")
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS")
|
||||
set(SDK_NAME appletvsimulator)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS x86_64)
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "WATCHOS")
|
||||
set(SDK_NAME watchos)
|
||||
if(NOT ARCHS)
|
||||
if (XCODE_VERSION VERSION_GREATER 10.0)
|
||||
set(ARCHS armv7k arm64_32)
|
||||
else()
|
||||
set(ARCHS armv7k)
|
||||
endif()
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED")
|
||||
set(SDK_NAME watchos)
|
||||
if(MODERN_CMAKE)
|
||||
if(NOT ARCHS)
|
||||
if (XCODE_VERSION VERSION_GREATER 10.0)
|
||||
set(ARCHS armv7k arm64_32 i386)
|
||||
else()
|
||||
set(ARCHS armv7k i386)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work")
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS")
|
||||
set(SDK_NAME watchsimulator)
|
||||
if(NOT ARCHS)
|
||||
set(ARCHS i386)
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}")
|
||||
endif()
|
||||
message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}")
|
||||
|
||||
if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode")
|
||||
message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode")
|
||||
endif()
|
||||
|
||||
# If user did not specify the SDK root to use, then query xcodebuild for it.
|
||||
execute_process(COMMAND xcodebuild -version -sdk ${SDK_NAME} Path
|
||||
OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT)
|
||||
message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain"
|
||||
"is pointing to the correct path. Please run:"
|
||||
"sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
|
||||
"and see if that fixes the problem for you.")
|
||||
message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} "
|
||||
"does not exist.")
|
||||
elseif(DEFINED CMAKE_OSX_SYSROOT)
|
||||
message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT} for platform: ${PLATFORM_INT} when checking compatibility")
|
||||
elseif(DEFINED CMAKE_OSX_SYSROOT_INT)
|
||||
message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT} for platform: ${PLATFORM_INT}")
|
||||
set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "")
|
||||
endif()
|
||||
|
||||
# Set Xcode property for SDKROOT as well if Xcode generator is used
|
||||
if(USED_CMAKE_GENERATOR MATCHES "Xcode")
|
||||
set(CMAKE_OSX_SYSROOT "${SDK_NAME}" CACHE INTERNAL "")
|
||||
if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM 123456789A CACHE INTERNAL "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Specify minimum version of deployment target.
|
||||
if(NOT DEFINED DEPLOYMENT_TARGET)
|
||||
if (PLATFORM_INT STREQUAL "WATCHOS" OR PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS")
|
||||
# Unless specified, SDK version 2.0 is used by default as minimum target version (watchOS).
|
||||
set(DEPLOYMENT_TARGET "2.0"
|
||||
CACHE STRING "Minimum SDK version to build for." )
|
||||
else()
|
||||
# Unless specified, SDK version 9.0 is used by default as minimum target version (iOS, tvOS).
|
||||
set(DEPLOYMENT_TARGET "9.0"
|
||||
CACHE STRING "Minimum SDK version to build for." )
|
||||
endif()
|
||||
message(STATUS "Using the default min-version since DEPLOYMENT_TARGET not provided!")
|
||||
endif()
|
||||
# Use bitcode or not
|
||||
if(NOT DEFINED ENABLE_BITCODE AND NOT ARCHS MATCHES "((^|;|, )(i386|x86_64))+")
|
||||
# Unless specified, enable bitcode support by default
|
||||
message(STATUS "Enabling bitcode support by default. ENABLE_BITCODE not provided!")
|
||||
set(ENABLE_BITCODE TRUE)
|
||||
elseif(NOT DEFINED ENABLE_BITCODE)
|
||||
message(STATUS "Disabling bitcode support by default on simulators. ENABLE_BITCODE not provided for override!")
|
||||
set(ENABLE_BITCODE FALSE)
|
||||
endif()
|
||||
set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL "Whether or not to enable bitcode" ${FORCE_CACHE})
|
||||
# Use ARC or not
|
||||
if(NOT DEFINED ENABLE_ARC)
|
||||
# Unless specified, enable ARC support by default
|
||||
set(ENABLE_ARC TRUE)
|
||||
message(STATUS "Enabling ARC support by default. ENABLE_ARC not provided!")
|
||||
endif()
|
||||
set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" ${FORCE_CACHE})
|
||||
# Use hidden visibility or not
|
||||
if(NOT DEFINED ENABLE_VISIBILITY)
|
||||
# Unless specified, disable symbols visibility by default
|
||||
set(ENABLE_VISIBILITY FALSE)
|
||||
message(STATUS "Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!")
|
||||
endif()
|
||||
set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols (-fvisibility=hidden)" ${FORCE_CACHE})
|
||||
# Set strict compiler checks or not
|
||||
if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE)
|
||||
# Unless specified, disable strict try_compile()
|
||||
set(ENABLE_STRICT_TRY_COMPILE FALSE)
|
||||
message(STATUS "Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!")
|
||||
endif()
|
||||
set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL "Whether or not to use strict compiler checks" ${FORCE_CACHE})
|
||||
# Get the SDK version information.
|
||||
execute_process(COMMAND xcodebuild -sdk ${CMAKE_OSX_SYSROOT} -version SDKVersion
|
||||
OUTPUT_VARIABLE SDK_VERSION
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# Find the Developer root for the specific iOS platform being compiled for
|
||||
# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in
|
||||
# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain
|
||||
# this information from xcrun or xcodebuild.
|
||||
if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode")
|
||||
get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT} PATH)
|
||||
get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH)
|
||||
|
||||
if (NOT DEFINED CMAKE_DEVELOPER_ROOT)
|
||||
message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: "
|
||||
"${CMAKE_DEVELOPER_ROOT} does not exist.")
|
||||
endif()
|
||||
endif()
|
||||
# Find the C & C++ compilers for the specified SDK.
|
||||
if(NOT CMAKE_C_COMPILER)
|
||||
execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang
|
||||
OUTPUT_VARIABLE CMAKE_C_COMPILER
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}")
|
||||
endif()
|
||||
if(NOT CMAKE_CXX_COMPILER)
|
||||
execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang++
|
||||
OUTPUT_VARIABLE CMAKE_CXX_COMPILER
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}")
|
||||
endif()
|
||||
# Find (Apple's) libtool.
|
||||
execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find libtool
|
||||
OUTPUT_VARIABLE BUILD_LIBTOOL
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
message(STATUS "Using libtool: ${BUILD_LIBTOOL}")
|
||||
# Configure libtool to be used instead of ar + ranlib to build static libraries.
|
||||
# This is required on Xcode 7+, but should also work on previous versions of
|
||||
# Xcode.
|
||||
set(CMAKE_C_CREATE_STATIC_LIBRARY
|
||||
"${BUILD_LIBTOOL} -static -o <TARGET> <LINK_FLAGS> <OBJECTS> ")
|
||||
set(CMAKE_CXX_CREATE_STATIC_LIBRARY
|
||||
"${BUILD_LIBTOOL} -static -o <TARGET> <LINK_FLAGS> <OBJECTS> ")
|
||||
# Find the toolchain's provided install_name_tool if none is found on the host
|
||||
if(NOT CMAKE_INSTALL_NAME_TOOL)
|
||||
execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find install_name_tool
|
||||
OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE STRING "" ${FORCE_CACHE})
|
||||
message(STATUS "Using install_name_tool: ${CMAKE_INSTALL_NAME_TOOL}")
|
||||
endif()
|
||||
# Get the version of Darwin (OS X) of the host.
|
||||
execute_process(COMMAND uname -r
|
||||
OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
# CMake 3.14+ support building for iOS, watchOS and tvOS out of the box.
|
||||
if(MODERN_CMAKE)
|
||||
if(SDK_NAME MATCHES "iphone")
|
||||
set(CMAKE_SYSTEM_NAME iOS CACHE INTERNAL "" ${FORCE_CACHE})
|
||||
elseif(SDK_NAME MATCHES "appletv")
|
||||
set(CMAKE_SYSTEM_NAME tvOS CACHE INTERNAL "" ${FORCE_CACHE})
|
||||
elseif(SDK_NAME MATCHES "watch")
|
||||
set(CMAKE_SYSTEM_NAME watchOS CACHE INTERNAL "" ${FORCE_CACHE})
|
||||
endif()
|
||||
|
||||
# Provide flags for a combined FAT library build on newer CMake versions
|
||||
if(PLATFORM_INT MATCHES ".*COMBINED")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO CACHE INTERNAL "" ${FORCE_CACHE})
|
||||
set(CMAKE_IOS_INSTALL_COMBINED YES CACHE INTERNAL "" ${FORCE_CACHE})
|
||||
message(STATUS "Will combine built (static) artifacts into FAT lib...")
|
||||
endif()
|
||||
else()
|
||||
# Legacy code path prior to CMake 3.14
|
||||
set(CMAKE_SYSTEM_NAME Darwin CACHE INTERNAL "" ${FORCE_CACHE})
|
||||
endif()
|
||||
# Standard settings.
|
||||
set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "")
|
||||
set(UNIX TRUE CACHE BOOL "")
|
||||
set(APPLE TRUE CACHE BOOL "")
|
||||
set(IOS TRUE CACHE BOOL "")
|
||||
set(CMAKE_AR ar CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_STRIP strip CACHE FILEPATH "" FORCE)
|
||||
# Set the architectures for which to build.
|
||||
set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE STRING "Build architecture for iOS")
|
||||
# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks
|
||||
if(ENABLE_STRICT_TRY_COMPILE_INT)
|
||||
message(STATUS "Using strict compiler checks (default in CMake).")
|
||||
else()
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
endif()
|
||||
# All iOS/Darwin specific settings - some may be redundant.
|
||||
set(CMAKE_MACOSX_BUNDLE YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
|
||||
set(CMAKE_SHARED_LIBRARY_PREFIX "lib")
|
||||
set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib")
|
||||
set(CMAKE_SHARED_MODULE_PREFIX "lib")
|
||||
set(CMAKE_SHARED_MODULE_SUFFIX ".so")
|
||||
set(CMAKE_C_COMPILER_ABI ELF)
|
||||
set(CMAKE_CXX_COMPILER_ABI ELF)
|
||||
set(CMAKE_C_HAS_ISYSROOT 1)
|
||||
set(CMAKE_CXX_HAS_ISYSROOT 1)
|
||||
set(CMAKE_MODULE_EXISTS 1)
|
||||
set(CMAKE_DL_LIBS "")
|
||||
set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ")
|
||||
set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ")
|
||||
set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}")
|
||||
set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}")
|
||||
|
||||
if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+")
|
||||
set(CMAKE_C_SIZEOF_DATA_PTR 8)
|
||||
set(CMAKE_CXX_SIZEOF_DATA_PTR 8)
|
||||
if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+")
|
||||
set(CMAKE_SYSTEM_PROCESSOR "aarch64")
|
||||
else()
|
||||
set(CMAKE_SYSTEM_PROCESSOR "x86_64")
|
||||
endif()
|
||||
message(STATUS "Using a data_ptr size of 8")
|
||||
else()
|
||||
set(CMAKE_C_SIZEOF_DATA_PTR 4)
|
||||
set(CMAKE_CXX_SIZEOF_DATA_PTR 4)
|
||||
set(CMAKE_SYSTEM_PROCESSOR "arm")
|
||||
message(STATUS "Using a data_ptr size of 4")
|
||||
endif()
|
||||
|
||||
message(STATUS "Building for minimum ${SDK_NAME} version: ${DEPLOYMENT_TARGET}"
|
||||
" (SDK version: ${SDK_VERSION})")
|
||||
# Note that only Xcode 7+ supports the newer more specific:
|
||||
# -m${SDK_NAME}-version-min flags, older versions of Xcode use:
|
||||
# -m(ios/ios-simulator)-version-min instead.
|
||||
if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64")
|
||||
if(XCODE_VERSION VERSION_LESS 7.0)
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-mios-version-min=${DEPLOYMENT_TARGET}")
|
||||
else()
|
||||
# Xcode 7.0+ uses flags we can build directly from SDK_NAME.
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}")
|
||||
endif()
|
||||
elseif(PLATFORM_INT STREQUAL "TVOS")
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-mtvos-version-min=${DEPLOYMENT_TARGET}")
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS")
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}")
|
||||
elseif(PLATFORM_INT STREQUAL "WATCHOS")
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-mwatchos-version-min=${DEPLOYMENT_TARGET}")
|
||||
elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS")
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}")
|
||||
else()
|
||||
# SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min.
|
||||
set(SDK_NAME_VERSION_FLAGS
|
||||
"-mios-simulator-version-min=${DEPLOYMENT_TARGET}")
|
||||
endif()
|
||||
message(STATUS "Version flags set to: ${SDK_NAME_VERSION_FLAGS}")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE STRING
|
||||
"Set CMake deployment target" ${FORCE_CACHE})
|
||||
|
||||
if(ENABLE_BITCODE_INT)
|
||||
set(BITCODE "-fembed-bitcode")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE bitcode CACHE INTERNAL "")
|
||||
message(STATUS "Enabling bitcode support.")
|
||||
else()
|
||||
set(BITCODE "")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE NO CACHE INTERNAL "")
|
||||
message(STATUS "Disabling bitcode support.")
|
||||
endif()
|
||||
|
||||
if(ENABLE_ARC_INT)
|
||||
set(FOBJC_ARC "-fobjc-arc")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES CACHE INTERNAL "")
|
||||
message(STATUS "Enabling ARC support.")
|
||||
else()
|
||||
set(FOBJC_ARC "-fno-objc-arc")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC NO CACHE INTERNAL "")
|
||||
message(STATUS "Disabling ARC support.")
|
||||
endif()
|
||||
|
||||
if(NOT ENABLE_VISIBILITY_INT)
|
||||
set(VISIBILITY "-fvisibility=hidden")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN YES CACHE INTERNAL "")
|
||||
message(STATUS "Hiding symbols (-fvisibility=hidden).")
|
||||
else()
|
||||
set(VISIBILITY "")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN NO CACHE INTERNAL "")
|
||||
endif()
|
||||
|
||||
#Check if Xcode generator is used, since that will handle these flags automagically
|
||||
if(USED_CMAKE_GENERATOR MATCHES "Xcode")
|
||||
message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as generator.")
|
||||
else()
|
||||
set(CMAKE_C_FLAGS
|
||||
"${SDK_NAME_VERSION_FLAGS} ${BITCODE} -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_C_FLAGS}")
|
||||
# Hidden visibilty is required for C++ on iOS.
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} -fvisibility-inlines-hidden -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g ${CMAKE_CXX_FLAGS_DEBUG}")
|
||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DNDEBUG -Os -ffast-math ${CMAKE_CXX_FLAGS_MINSIZEREL}")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -DNDEBUG -O2 -g -ffast-math ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -ffast-math ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}")
|
||||
set(CMAKE_CXX_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}")
|
||||
|
||||
# In order to ensure that the updated compiler flags are used in try_compile()
|
||||
# tests, we have to forcibly set them in the CMake cache, not merely set them
|
||||
# in the local scope.
|
||||
list(APPEND VARS_TO_FORCE_IN_CACHE
|
||||
CMAKE_C_FLAGS
|
||||
CMAKE_CXX_FLAGS
|
||||
CMAKE_CXX_FLAGS_DEBUG
|
||||
CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL
|
||||
CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_C_LINK_FLAGS
|
||||
CMAKE_CXX_LINK_FLAGS)
|
||||
foreach(VAR_TO_FORCE ${VARS_TO_FORCE_IN_CACHE})
|
||||
set(${VAR_TO_FORCE} "${${VAR_TO_FORCE}}" CACHE STRING "")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
set(CMAKE_PLATFORM_HAS_INSTALLNAME 1)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks")
|
||||
set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names")
|
||||
set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names")
|
||||
set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,")
|
||||
set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,")
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a")
|
||||
set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name")
|
||||
|
||||
# Set the find root to the iOS developer roots and to user defined paths.
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_OSX_SYSROOT_INT} ${CMAKE_PREFIX_PATH} CACHE STRING "Root path that will be prepended
|
||||
to all search paths")
|
||||
# Default to searching for frameworks first.
|
||||
set(CMAKE_FIND_FRAMEWORK FIRST)
|
||||
# Set up the default search directories for frameworks.
|
||||
set(CMAKE_FRAMEWORK_PATH
|
||||
${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks
|
||||
${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks
|
||||
${CMAKE_FRAMEWORK_PATH} CACHE STRING "Frameworks search paths" ${FORCE_CACHE})
|
||||
|
||||
# By default, search both the specified iOS SDK and the remainder of the host filesystem.
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE STRING "" ${FORCE_CACHE})
|
||||
endif()
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY CACHE STRING "" ${FORCE_CACHE})
|
||||
endif()
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE STRING "" ${FORCE_CACHE})
|
||||
endif()
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY CACHE STRING "" ${FORCE_CACHE})
|
||||
endif()
|
||||
|
||||
#
|
||||
# Some helper-macros below to simplify and beautify the CMakeFile
|
||||
#
|
||||
|
||||
# This little macro lets you set any Xcode specific property.
|
||||
macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION)
|
||||
set(XCODE_RELVERSION_I "${XCODE_RELVERSION}")
|
||||
if(XCODE_RELVERSION_I STREQUAL "All")
|
||||
set_property(TARGET ${TARGET} PROPERTY
|
||||
XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}")
|
||||
else()
|
||||
set_property(TARGET ${TARGET} PROPERTY
|
||||
XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}")
|
||||
endif()
|
||||
endmacro(set_xcode_property)
|
||||
# This macro lets you find executable programs on the host system.
|
||||
macro(find_host_package)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)
|
||||
set(IOS FALSE)
|
||||
find_package(${ARGN})
|
||||
set(IOS TRUE)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
||||
endmacro(find_host_package)
|
@ -9,14 +9,14 @@ secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||
#FFmpeg可执行程序绝对路径
|
||||
bin=/usr/local/bin/ffmpeg
|
||||
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
||||
cmd=%s -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日志的路径,如果置空则不生成FFmpeg日志
|
||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||
log=./ffmpeg/ffmpeg.log
|
||||
|
||||
[general]
|
||||
#是否启用虚拟主机
|
||||
enableVhost=1
|
||||
enableVhost=0
|
||||
#播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件),
|
||||
#flowThreshold参数控制触发hook.on_flow_report事件阈值,使用流量超过该阈值后才触发,单位KB
|
||||
flowThreshold=1024
|
||||
@ -25,10 +25,10 @@ flowThreshold=1024
|
||||
#ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
|
||||
#如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功
|
||||
#否则返回播放器未找到该流,该机制的目的是可以先播放再推流
|
||||
maxStreamWaitMS=5000
|
||||
maxStreamWaitMS=15000
|
||||
#某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
|
||||
#在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
|
||||
streamNoneReaderDelayMS=5000
|
||||
streamNoneReaderDelayMS=20000
|
||||
#是否开启低延时模式,该模式下禁用MSG_MORE,启用TCP_NODEALY,延时将降低,但数据发送性能将降低
|
||||
ultraLowDelay=1
|
||||
#拉流代理是否添加静音音频(直接拉流模式本协议无效)
|
||||
@ -36,18 +36,26 @@ addMuteAudio=1
|
||||
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
resetWhenRePlay=1
|
||||
#是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
||||
publishToRtxp=1
|
||||
#是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||
publishToHls=1
|
||||
#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||
publishToMP4=0
|
||||
|
||||
[hls]
|
||||
#hls写文件的buf大小,调整参数可以提高文件io性能
|
||||
fileBufSize=65536
|
||||
#hls保存文件路径
|
||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||
filePath=./httpRoot
|
||||
filePath=./www
|
||||
#hls最大切片时间
|
||||
segDur=3
|
||||
segDur=2
|
||||
#m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
|
||||
#如果设置为0,则不删除切片,而是保存为点播
|
||||
segNum=3
|
||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||
segRetain=5
|
||||
|
||||
[hook]
|
||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||
@ -78,6 +86,8 @@ on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed
|
||||
on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
|
||||
#播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流
|
||||
on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found
|
||||
#服务器启动报告,可以用于服务器的崩溃重启事件监听
|
||||
on_server_started=https://127.0.0.1/index/hook/on_server_started
|
||||
#hook api最大等待回复时间,单位秒
|
||||
timeoutSec=10
|
||||
|
||||
@ -85,9 +95,7 @@ timeoutSec=10
|
||||
#http服务器字符编码,windows上默认gb2312
|
||||
charSet=utf-8
|
||||
#http链接超时时间
|
||||
keepAliveSecond=10
|
||||
#keep-alive类型的链接最多复用次数
|
||||
maxReqCount=100
|
||||
keepAliveSecond=30
|
||||
#http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
|
||||
maxReqSize=4096
|
||||
#404网页内容,用户可以自定义404网页
|
||||
@ -96,7 +104,7 @@ notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><c
|
||||
port=80
|
||||
#http文件服务器根目录
|
||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||
rootPath=./httpRoot
|
||||
rootPath=./www
|
||||
#http文件服务器读文件缓存大小,单位BYTE,调整该参数可以优化文件io性能
|
||||
sendBufSize=65536
|
||||
#https服务器监听端口
|
||||
@ -118,7 +126,7 @@ appName=record
|
||||
fileBufSize=65536
|
||||
#mp4录制保存、mp4点播根路径
|
||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||
filePath=./httpRoot
|
||||
filePath=./www
|
||||
#mp4录制切片时间,单位秒
|
||||
fileSecond=3600
|
||||
#mp4点播每次流化数据量,单位毫秒,
|
||||
@ -136,7 +144,7 @@ handshakeSecond=15
|
||||
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
||||
keepAliveSecond=15
|
||||
#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
|
||||
modifyStamp=1
|
||||
modifyStamp=0
|
||||
#rtmp服务器监听端口
|
||||
port=1935
|
||||
|
||||
@ -153,6 +161,16 @@ maxRtpCount=50
|
||||
#视频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
|
||||
videoMtuSize=1400
|
||||
|
||||
[rtp_proxy]
|
||||
#udp类型的代理服务器是否检查rtp源地址,地址不配备将丢弃数据
|
||||
checkSource=1
|
||||
#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
|
||||
dumpDir=
|
||||
#udp和tcp代理服务器,支持rtp(必须是ts或ps类型)代理
|
||||
port=10000
|
||||
#rtp超时时间,单位秒
|
||||
timeoutSec=15
|
||||
|
||||
[rtsp]
|
||||
#rtsp专有鉴权方式是采用base64还是md5方式
|
||||
authBasic=0
|
||||
@ -171,8 +189,6 @@ keepAliveSecond=15
|
||||
port=554
|
||||
#rtsps服务器监听地址
|
||||
sslport=322
|
||||
#在接收rtsp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
|
||||
modifyStamp=1
|
||||
|
||||
[shell]
|
||||
#调试telnet服务器接受最大bufffer大小
|
||||
|
43
docker/ubuntu16.04/Dockerfile.devel
Normal file
43
docker/ubuntu16.04/Dockerfile.devel
Normal file
@ -0,0 +1,43 @@
|
||||
FROM ubuntu:16.04
|
||||
#shell,rtmp,rtsp,rtsps,http,https,rtp
|
||||
EXPOSE 9000/tcp
|
||||
EXPOSE 1935/tcp
|
||||
EXPOSE 554/tcp
|
||||
EXPOSE 322/tcp
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 443/tcp
|
||||
EXPOSE 10000/udp
|
||||
EXPOSE 10000/tcp
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
curl \
|
||||
vim \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libssl-dev \
|
||||
libmysqlclient-dev \
|
||||
libx264-dev \
|
||||
libfaac-dev \
|
||||
libmp4v2-dev && \
|
||||
apt autoremove -y && \
|
||||
apt clean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /opt/media
|
||||
|
||||
WORKDIR /opt/media
|
||||
RUN git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit && \
|
||||
cd ZLMediaKit && git submodule update --init --recursive && \
|
||||
mkdir -p build release/linux/Release/
|
||||
|
||||
WORKDIR /opt/media/ZLMediaKit/build
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make -j4
|
||||
|
||||
ENV PATH /opt/media/ZLMediaKit/release/linux/Release/:$PATH
|
||||
CMD MediaServer
|
62
docker/ubuntu16.04/Dockerfile.runtime
Normal file
62
docker/ubuntu16.04/Dockerfile.runtime
Normal file
@ -0,0 +1,62 @@
|
||||
FROM ubuntu:16.04 AS build
|
||||
#shell,rtmp,rtsp,rtsps,http,https,rtp
|
||||
EXPOSE 9000/tcp
|
||||
EXPOSE 1935/tcp
|
||||
EXPOSE 554/tcp
|
||||
EXPOSE 322/tcp
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 443/tcp
|
||||
EXPOSE 10000/udp
|
||||
EXPOSE 10000/tcp
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
curl \
|
||||
vim \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libssl-dev \
|
||||
libmysqlclient-dev \
|
||||
libx264-dev \
|
||||
libfaac-dev \
|
||||
libmp4v2-dev && \
|
||||
apt autoremove -y && \
|
||||
apt clean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /opt/media
|
||||
|
||||
WORKDIR /opt/media
|
||||
RUN git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit && \
|
||||
cd ZLMediaKit && git submodule update --init --recursive && \
|
||||
mkdir -p build release/linux/Release/
|
||||
|
||||
WORKDIR /opt/media/ZLMediaKit/build
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make -j4
|
||||
|
||||
FROM ubuntu:16.04
|
||||
LABEL maintainer "Gemfield <gemfield@civilnet.cn>"
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y --no-install-recommends \
|
||||
vim \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libssl-dev \
|
||||
libx264-dev \
|
||||
libfaac-dev \
|
||||
libmp4v2-dev && \
|
||||
apt autoremove -y && \
|
||||
apt clean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/media/bin/
|
||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
||||
ENV PATH /opt/media/bin:$PATH
|
||||
CMD MediaServer
|
44
docker/ubuntu18.04/Dockerfile.devel
Normal file
44
docker/ubuntu18.04/Dockerfile.devel
Normal file
@ -0,0 +1,44 @@
|
||||
FROM ubuntu:18.04
|
||||
LABEL maintainer "Gemfield <gemfield@civilnet.cn>"
|
||||
#shell,rtmp,rtsp,rtsps,http,https,rtp
|
||||
EXPOSE 9000/tcp
|
||||
EXPOSE 1935/tcp
|
||||
EXPOSE 554/tcp
|
||||
EXPOSE 322/tcp
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 443/tcp
|
||||
EXPOSE 10000/udp
|
||||
EXPOSE 10000/tcp
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
curl \
|
||||
vim \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libssl-dev \
|
||||
libmysqlclient-dev \
|
||||
libx264-dev \
|
||||
libfaac-dev \
|
||||
libmp4v2-dev && \
|
||||
apt autoremove -y && \
|
||||
apt clean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /opt/media
|
||||
|
||||
WORKDIR /opt/media
|
||||
RUN git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit && \
|
||||
cd ZLMediaKit && git submodule update --init --recursive && \
|
||||
mkdir -p build release/linux/Release/
|
||||
|
||||
WORKDIR /opt/media/ZLMediaKit/build
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make -j4
|
||||
|
||||
ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH
|
||||
CMD MediaServer
|
62
docker/ubuntu18.04/Dockerfile.runtime
Normal file
62
docker/ubuntu18.04/Dockerfile.runtime
Normal file
@ -0,0 +1,62 @@
|
||||
FROM ubuntu:18.04 AS build
|
||||
#shell,rtmp,rtsp,rtsps,http,https,rtp
|
||||
EXPOSE 9000/tcp
|
||||
EXPOSE 1935/tcp
|
||||
EXPOSE 554/tcp
|
||||
EXPOSE 322/tcp
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 443/tcp
|
||||
EXPOSE 10000/udp
|
||||
EXPOSE 10000/tcp
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
curl \
|
||||
vim \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libssl-dev \
|
||||
libmysqlclient-dev \
|
||||
libx264-dev \
|
||||
libfaac-dev \
|
||||
libmp4v2-dev && \
|
||||
apt autoremove -y && \
|
||||
apt clean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /opt/media
|
||||
|
||||
WORKDIR /opt/media
|
||||
RUN git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit && \
|
||||
cd ZLMediaKit && git submodule update --init --recursive && \
|
||||
mkdir -p build release/linux/Release/
|
||||
|
||||
WORKDIR /opt/media/ZLMediaKit/build
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make -j4
|
||||
|
||||
FROM ubuntu:18.04
|
||||
LABEL maintainer "Gemfield <gemfield@civilnet.cn>"
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y --no-install-recommends \
|
||||
vim \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libssl-dev \
|
||||
libx264-dev \
|
||||
libfaac-dev \
|
||||
libmp4v2-dev && \
|
||||
apt autoremove -y && \
|
||||
apt clean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/media/bin/
|
||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
||||
ENV PATH /opt/media/bin:$PATH
|
||||
CMD MediaServer
|
@ -3,12 +3,7 @@ file(GLOB jsoncpp_src_list ../3rdpart/jsoncpp/*.cpp ../3rdpart/jsoncpp/*.h )
|
||||
add_library(jsoncpp STATIC ${jsoncpp_src_list})
|
||||
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
set(MediaServer_src_list ./WebApi.cpp ./WebHook.cpp main.cpp)
|
||||
else()
|
||||
file(GLOB MediaServer_src_list ./*.cpp ./*.h)
|
||||
endif()
|
||||
|
||||
#message(STATUS ${MediaServer_src_list})
|
||||
|
||||
add_executable(MediaServer ${MediaServer_src_list})
|
||||
|
@ -32,13 +32,22 @@
|
||||
|
||||
namespace FFmpeg {
|
||||
#define FFmpeg_FIELD "ffmpeg."
|
||||
const char kBin[] = FFmpeg_FIELD"bin";
|
||||
const char kCmd[] = FFmpeg_FIELD"cmd";
|
||||
const char kLog[] = FFmpeg_FIELD"log";
|
||||
const string kBin = FFmpeg_FIELD"bin";
|
||||
const string kCmd = FFmpeg_FIELD"cmd";
|
||||
const string kLog = FFmpeg_FIELD"log";
|
||||
|
||||
onceToken token([]() {
|
||||
mINI::Instance()[kBin] = trim(System::execute("which ffmpeg"));
|
||||
mINI::Instance()[kCmd] = "%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s";
|
||||
#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 ";
|
||||
#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 ";
|
||||
#endif
|
||||
//默认ffmpeg命令路径为环境变量中路径
|
||||
mINI::Instance()[kBin] = ffmpeg_bin.empty() ? "ffmpeg" : ffmpeg_bin;
|
||||
//ffmpeg日志保存路径
|
||||
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
|
||||
});
|
||||
}
|
||||
@ -48,7 +57,6 @@ FFmpegSource::FFmpegSource() {
|
||||
}
|
||||
|
||||
FFmpegSource::~FFmpegSource() {
|
||||
NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastStreamNoneReader);
|
||||
DebugL;
|
||||
}
|
||||
|
||||
@ -64,7 +72,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_
|
||||
|
||||
char cmd[1024] = {0};
|
||||
snprintf(cmd, sizeof(cmd),ffmpeg_cmd.data(),ffmpeg_bin.data(),src_url.data(),dst_url.data());
|
||||
_process.run(cmd,File::absolutePath("",ffmpeg_log));
|
||||
_process.run(cmd,ffmpeg_log.empty() ? "" : File::absolutePath("",ffmpeg_log));
|
||||
InfoL << cmd;
|
||||
|
||||
if(_media_info._host == "127.0.0.1"){
|
||||
@ -83,6 +91,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_
|
||||
if(src){
|
||||
//推流给自己成功
|
||||
cb(SockException());
|
||||
strongSelf->onGetMediaSource(src);
|
||||
strongSelf->startTimer(timeout_ms);
|
||||
return;
|
||||
}
|
||||
@ -148,10 +157,10 @@ void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSourc
|
||||
}
|
||||
|
||||
if (!bRegist ||
|
||||
schema != strongSelf->_media_info._schema ||
|
||||
vhost != strongSelf->_media_info._vhost ||
|
||||
app != strongSelf->_media_info._app ||
|
||||
stream != strongSelf->_media_info._streamid){
|
||||
sender.getSchema() != strongSelf->_media_info._schema ||
|
||||
sender.getVhost() != strongSelf->_media_info._vhost ||
|
||||
sender.getApp() != strongSelf->_media_info._app ||
|
||||
sender.getId() != strongSelf->_media_info._streamid) {
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
return;
|
||||
}
|
||||
@ -192,8 +201,11 @@ void FFmpegSource::startTimer(int timeout_ms) {
|
||||
//同步查找流
|
||||
if (!src) {
|
||||
//流不在线,重新拉流
|
||||
strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms,
|
||||
[](const SockException &) {});
|
||||
if(strongSelf->_replay_ticker.elapsedTime() > 10 * 1000){
|
||||
//上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||
strongSelf->_replay_ticker.resetTime();
|
||||
strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -205,29 +217,43 @@ void FFmpegSource::startTimer(int timeout_ms) {
|
||||
}
|
||||
return true;
|
||||
}, _poller);
|
||||
|
||||
NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastStreamNoneReader);
|
||||
NoticeCenter::Instance().addListener(this, Broadcast::kBroadcastStreamNoneReader,[weakSelf](BroadcastStreamNoneReaderArgs) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//自身已经销毁
|
||||
return;
|
||||
}
|
||||
|
||||
if(sender.getVhost() != strongSelf->_media_info._vhost ||
|
||||
sender.getApp() != strongSelf->_media_info._app ||
|
||||
sender.getId() != strongSelf->_media_info._streamid){
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
return;
|
||||
}
|
||||
|
||||
//该流无人观看,我们停止吧
|
||||
if(strongSelf->_onClose){
|
||||
strongSelf->_onClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FFmpegSource::setOnClose(const function<void()> &cb){
|
||||
_onClose = cb;
|
||||
}
|
||||
|
||||
bool FFmpegSource::close(MediaSource &sender, bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if(listener && !listener->close(sender,force)){
|
||||
//关闭失败
|
||||
return false;
|
||||
}
|
||||
//该流无人观看,我们停止吧
|
||||
if(_onClose){
|
||||
_onClose();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FFmpegSource::onNoneReader(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if(listener){
|
||||
listener->onNoneReader(sender);
|
||||
}else{
|
||||
MediaSourceEvent::onNoneReader(sender);
|
||||
}
|
||||
}
|
||||
|
||||
int FFmpegSource::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if(listener){
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
||||
_listener = src->getListener();
|
||||
src->setListener(shared_from_this());
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource>{
|
||||
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource> , public MediaSourceEvent{
|
||||
public:
|
||||
typedef shared_ptr<FFmpegSource> Ptr;
|
||||
typedef function<void(const SockException &ex)> onPlay;
|
||||
@ -55,6 +55,12 @@ public:
|
||||
private:
|
||||
void findAsync(int maxWaitMS ,const function<void(const MediaSource::Ptr &src)> &cb);
|
||||
void startTimer(int timeout_ms);
|
||||
void onGetMediaSource(const MediaSource::Ptr &src);
|
||||
|
||||
//MediaSourceEvent override
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
void onNoneReader(MediaSource &sender) override ;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
private:
|
||||
Process _process;
|
||||
Timer::Ptr _timer;
|
||||
@ -63,6 +69,8 @@ private:
|
||||
string _src_url;
|
||||
string _dst_url;
|
||||
function<void()> _onClose;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
Ticker _replay_ticker;
|
||||
};
|
||||
|
||||
|
||||
|
@ -26,8 +26,15 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
//#include <TlHelp32.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
#include <signal.h>
|
||||
#include "Util/util.h"
|
||||
@ -40,6 +47,25 @@ using namespace toolkit;
|
||||
|
||||
void Process::run(const string &cmd, const string &log_file_tmp) {
|
||||
kill(2000);
|
||||
#ifdef _WIN32
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&si, sizeof(si)); //结构体初始化;
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
LPTSTR lpDir = const_cast<char*>(cmd.data());
|
||||
|
||||
if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){
|
||||
//下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
_pid = pi.dwProcessId;
|
||||
InfoL << "start child proces " << _pid;
|
||||
} else {
|
||||
WarnL << "start child proces fail: " << GetLastError();
|
||||
}
|
||||
#else
|
||||
_pid = fork();
|
||||
if (_pid < 0) {
|
||||
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
|
||||
@ -57,7 +83,8 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
|
||||
string log_file;
|
||||
if (log_file_tmp.empty()) {
|
||||
log_file = "/dev/null";
|
||||
}else{
|
||||
}
|
||||
else {
|
||||
log_file = StrPrinter << log_file_tmp << "." << getpid();
|
||||
}
|
||||
|
||||
@ -67,7 +94,8 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
|
||||
File::createfile_path(log_file.data(), mode);
|
||||
if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) {
|
||||
fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// dup to stdout and stderr.
|
||||
if (dup2(log_fd, STDOUT_FILENO) < 0) {
|
||||
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||
@ -103,11 +131,10 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
|
||||
}
|
||||
exit(ret);
|
||||
}
|
||||
|
||||
InfoL << "start child proces " << _pid;
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取进程是否存活状态
|
||||
* @param pid 进程号
|
||||
@ -120,6 +147,15 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
|
||||
return false;
|
||||
}
|
||||
int status = 0;
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = NULL;
|
||||
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
|
||||
if (hProcess == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(hProcess);
|
||||
#else
|
||||
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
|
||||
int exit_code = (status & 0xFF00) >> 8;
|
||||
if (exit_code_ptr) {
|
||||
@ -133,7 +169,8 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
|
||||
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
|
||||
return false;
|
||||
}
|
||||
//WarnL << "process is running, pid=" << _pid;
|
||||
#endif // _WIN32
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -142,12 +179,25 @@ static void s_kill(pid_t pid,int max_delay,bool force){
|
||||
//pid无效
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = NULL;
|
||||
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
|
||||
if (hProcess == NULL) {
|
||||
WarnL << "\nOpen Process fAiled: " << GetLastError();
|
||||
return;
|
||||
}
|
||||
DWORD ret = TerminateProcess(hProcess, 0); //结束目标进程
|
||||
if (ret == 0) {
|
||||
WarnL << GetLastError;
|
||||
}
|
||||
#else
|
||||
if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
|
||||
//进程可能已经退出了
|
||||
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
|
||||
return;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
if(force){
|
||||
//发送SIGKILL信号后,阻塞等待退出
|
||||
|
@ -23,10 +23,15 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#ifndef IPTV_PROCESS_H
|
||||
#define IPTV_PROCESS_H
|
||||
#ifndef ZLMEDIAKIT_PROCESS_H
|
||||
#define ZLMEDIAKIT_PROCESS_H
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef int pid_t;
|
||||
#else
|
||||
#include <sys/wait.h>
|
||||
#endif // _WIN32
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
@ -45,4 +50,4 @@ private:
|
||||
};
|
||||
|
||||
|
||||
#endif //IPTV_PROCESS_H
|
||||
#endif //ZLMEDIAKIT_PROCESS_H
|
||||
|
@ -24,239 +24,36 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "System.h"
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <arpa/inet.h>
|
||||
#if !defined(_WIN32)
|
||||
#include <limits.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#ifndef ANDROID
|
||||
#if !defined(ANDROID)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "Util/mini.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#endif//!defined(ANDROID)
|
||||
#endif//!defined(_WIN32)
|
||||
|
||||
#include "System.h"
|
||||
#include <signal.h>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include "Util/logger.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/uv_errno.h"
|
||||
#include "Util/CMD.h"
|
||||
#include "Util/MD5.h"
|
||||
using namespace toolkit;
|
||||
|
||||
const int MAX_STACK_FRAMES = 128;
|
||||
#define BroadcastOnCrashDumpArgs int &sig,const vector<vector<string> > &stack
|
||||
const char kBroadcastOnCrashDump[] = "kBroadcastOnCrashDump";
|
||||
|
||||
//#if defined(__MACH__) || defined(__APPLE__)
|
||||
//#define TEST_LINUX
|
||||
//#endif
|
||||
|
||||
vector<string> splitWithEmptyLine(const string &s, const char *delim) {
|
||||
vector<string> ret;
|
||||
int last = 0;
|
||||
int index = s.find(delim, last);
|
||||
while (index != string::npos) {
|
||||
ret.push_back(s.substr(last, index - last));
|
||||
last = index + strlen(delim);
|
||||
index = s.find(delim, last);
|
||||
}
|
||||
if (s.size() - last > 0) {
|
||||
ret.push_back(s.substr(last));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
map<string, mINI> splitTopStr(const string &cmd_str) {
|
||||
map<string, mINI> ret;
|
||||
auto lines = splitWithEmptyLine(cmd_str, "\n");
|
||||
int i = 0;
|
||||
for (auto &line : lines) {
|
||||
if(i++ < 1 && line.empty()){
|
||||
continue;
|
||||
}
|
||||
if (line.empty()) {
|
||||
break;
|
||||
}
|
||||
auto line_vec = split(line, ":");
|
||||
|
||||
if (line_vec.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
trim(line_vec[0], " \r\n\t");
|
||||
auto args_vec = split(line_vec[1], ",");
|
||||
for (auto &arg : args_vec) {
|
||||
auto arg_vec = split(trim(arg, " \r\n\t."), " ");
|
||||
if (arg_vec.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
ret[line_vec[0]].emplace(arg_vec[1], arg_vec[0]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool System::getSystemUsage(SystemUsage &usage) {
|
||||
try {
|
||||
#if defined(__linux) || defined(__linux__) || defined(TEST_LINUX)
|
||||
string cmd_str;
|
||||
#if !defined(TEST_LINUX)
|
||||
cmd_str = System::execute("top -b -n 1");
|
||||
#else
|
||||
cmd_str = "top - 07:21:31 up 5:48, 2 users, load average: 0.03, 0.62, 0.54\n"
|
||||
"Tasks: 80 total, 1 running, 78 sleeping, 0 stopped, 1 zombie\n"
|
||||
"%Cpu(s): 0.8 us, 0.4 sy, 0.0 ni, 98.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\n"
|
||||
"KiB Mem: 2058500 total, 249100 used, 1809400 free, 19816 buffers\n"
|
||||
"KiB Swap: 1046524 total, 0 used, 1046524 free. 153012 cached Mem\n"
|
||||
"\n";
|
||||
#ifdef _WIN32
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
#endif
|
||||
if (cmd_str.empty()) {
|
||||
WarnL << "System::execute(\"top -b -n 1\") return empty";
|
||||
return false;
|
||||
}
|
||||
auto topMap = splitTopStr(cmd_str);
|
||||
|
||||
usage.task_total = topMap["Tasks"]["total"];
|
||||
usage.task_running = topMap["Tasks"]["running"];
|
||||
usage.task_sleeping = topMap["Tasks"]["sleeping"];
|
||||
usage.task_stopped = topMap["Tasks"]["stopped"];
|
||||
|
||||
usage.cpu_user = topMap["%Cpu(s)"]["us"];
|
||||
usage.cpu_sys = topMap["%Cpu(s)"]["sy"];
|
||||
usage.cpu_idle = topMap["%Cpu(s)"]["id"];
|
||||
|
||||
usage.mem_total = topMap["KiB Mem"]["total"];
|
||||
usage.mem_free = topMap["KiB Mem"]["free"];
|
||||
usage.mem_used = topMap["KiB Mem"]["used"];
|
||||
return true;
|
||||
|
||||
#elif defined(__MACH__) || defined(__APPLE__)
|
||||
/*
|
||||
"Processes: 275 total, 2 running, 1 stuck, 272 sleeping, 1258 threads \n"
|
||||
"2018/09/12 10:41:32\n"
|
||||
"Load Avg: 2.06, 2.88, 2.86 \n"
|
||||
"CPU usage: 14.54% user, 25.45% sys, 60.0% idle \n"
|
||||
"SharedLibs: 117M resident, 37M data, 15M linkedit.\n"
|
||||
"MemRegions: 46648 total, 3654M resident, 62M private, 714M shared.\n"
|
||||
"PhysMem: 7809M used (1906M wired), 381M unused.\n"
|
||||
"VM: 751G vsize, 614M framework vsize, 0(0) swapins, 0(0) swapouts.\n"
|
||||
"Networks: packets: 502366/248M in, 408957/87M out.\n"
|
||||
"Disks: 349435/6037M read, 78622/2577M written.";
|
||||
*/
|
||||
|
||||
string cmd_str = System::execute("top -l 1");
|
||||
if(cmd_str.empty()){
|
||||
WarnL << "System::execute(\"top -n 1\") return empty";
|
||||
return false;
|
||||
}
|
||||
auto topMap = splitTopStr(cmd_str);
|
||||
usage.task_total = topMap["Processes"]["total"];
|
||||
usage.task_running = topMap["Processes"]["running"];
|
||||
usage.task_sleeping = topMap["Processes"]["sleeping"];
|
||||
usage.task_stopped = topMap["Processes"]["stuck"];
|
||||
|
||||
usage.cpu_user = topMap["CPU usage"]["user"];
|
||||
usage.cpu_sys = topMap["CPU usage"]["sys"];
|
||||
usage.cpu_idle = topMap["CPU usage"]["idle"];
|
||||
|
||||
usage.mem_free = topMap["PhysMem"]["unused"].as<uint32_t>() * 1024 * 1024;
|
||||
usage.mem_used = topMap["PhysMem"]["used"].as<uint32_t>() * 1024 * 1024;
|
||||
usage.mem_total = usage.mem_free + usage.mem_used;
|
||||
return true;
|
||||
#else
|
||||
WarnL << "System not supported";
|
||||
return false;
|
||||
#endif
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool System::getNetworkUsage(vector<NetworkUsage> &usage) {
|
||||
try {
|
||||
#if defined(__linux) || defined(__linux__) || defined(TEST_LINUX)
|
||||
string cmd_str;
|
||||
#if !defined(TEST_LINUX)
|
||||
cmd_str = System::execute("cat /proc/net/dev");
|
||||
#else
|
||||
cmd_str =
|
||||
"Inter-| Receive | Transmit\n"
|
||||
" face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n"
|
||||
" lo: 475978 7546 0 0 0 0 0 0 475978 7546 0 0 0 0 0 0\n"
|
||||
"enp3s0: 151747818 315136 0 0 0 0 0 145 1590783447 1124616 0 0 0 0 0 0";
|
||||
#endif
|
||||
if (cmd_str.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto lines = split(cmd_str, "\n");
|
||||
int i = 0;
|
||||
vector<string> column_name_vec;
|
||||
vector<string> category_prefix_vec;
|
||||
for (auto &line : lines) {
|
||||
switch (i++) {
|
||||
case 0: {
|
||||
category_prefix_vec = split(line, "|");
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
auto category_suffix_vec = split(line, "|");
|
||||
int j = 0;
|
||||
for (auto &category_suffix : category_suffix_vec) {
|
||||
auto column_suffix_vec = split(category_suffix, " ");
|
||||
for (auto &column_suffix : column_suffix_vec) {
|
||||
column_name_vec.emplace_back(trim(category_prefix_vec[j]) + "-" + trim(column_suffix));
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
mINI valMap;
|
||||
auto vals = split(line, " ");
|
||||
int j = 0;
|
||||
for (auto &val : vals) {
|
||||
valMap[column_name_vec[j++]] = trim(val, " \r\n\t:");
|
||||
}
|
||||
usage.emplace_back(NetworkUsage());
|
||||
auto &ifrUsage = usage.back();
|
||||
ifrUsage.interface = valMap["Inter--face"];
|
||||
ifrUsage.recv_bytes = valMap["Receive-bytes"];
|
||||
ifrUsage.recv_packets = valMap["Receive-packets"];
|
||||
ifrUsage.snd_bytes = valMap["Transmit-bytes"];
|
||||
ifrUsage.snd_packets = valMap["Transmit-packets"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
WarnL << "System not supported";
|
||||
return false;
|
||||
#endif
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool System::getTcpUsage(System::TcpUsage &usage) {
|
||||
usage.established = atoi(trim(System::execute("netstat -na|grep ESTABLISHED|wc -l")).data());
|
||||
usage.syn_recv = atoi(trim(System::execute("netstat -na|grep SYN_RECV|wc -l")).data());
|
||||
usage.time_wait = atoi(trim(System::execute("netstat -na|grep TIME_WAIT|wc -l")).data());
|
||||
usage.close_wait = atoi(trim(System::execute("netstat -na|grep CLOSE_WAIT|wc -l")).data());
|
||||
return true;
|
||||
}
|
||||
|
||||
string System::execute(const string &cmd) {
|
||||
// DebugL << cmd;
|
||||
FILE *fPipe = popen(cmd.data(), "r");
|
||||
FILE *fPipe = NULL;
|
||||
fPipe = popen(cmd.data(), "r");
|
||||
if(!fPipe){
|
||||
return "";
|
||||
}
|
||||
@ -269,12 +66,12 @@ string System::execute(const string &cmd) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID) && !defined(_WIN32)
|
||||
static string addr2line(const string &address) {
|
||||
string cmd = StrPrinter << "addr2line -e " << exePath() << " " << address;
|
||||
return System::execute(cmd);
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
static void sig_crash(int sig) {
|
||||
signal(sig, SIG_DFL);
|
||||
void *array[MAX_STACK_FRAMES];
|
||||
@ -294,14 +91,13 @@ static void sig_crash(int sig) {
|
||||
#endif//__linux
|
||||
}
|
||||
free(strings);
|
||||
|
||||
NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack);
|
||||
}
|
||||
|
||||
#endif//#ifndef ANDROID
|
||||
#endif // !defined(ANDROID) && !defined(_WIN32)
|
||||
|
||||
|
||||
void System::startDaemon() {
|
||||
#ifndef _WIN32
|
||||
static pid_t pid;
|
||||
do{
|
||||
pid = fork();
|
||||
@ -336,21 +132,11 @@ void System::startDaemon() {
|
||||
DebugL << "waitpid被中断:" << get_uv_errmsg();
|
||||
}while (true);
|
||||
}while (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string currentDateTime(){
|
||||
time_t ts = time(NULL);
|
||||
std::tm tm_snapshot;
|
||||
localtime_r(&ts, &tm_snapshot);
|
||||
|
||||
char buffer[1024] = {0};
|
||||
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_snapshot);
|
||||
return buffer;
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
void System::systemSetup(){
|
||||
#if !defined(_WIN32)
|
||||
struct rlimit rlim,rlim_new;
|
||||
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
|
||||
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
|
||||
@ -373,11 +159,9 @@ void System::systemSetup(){
|
||||
#ifndef ANDROID
|
||||
signal(SIGSEGV, sig_crash);
|
||||
signal(SIGABRT, sig_crash);
|
||||
#endif//#ifndef ANDROID
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,kBroadcastOnCrashDump,[](BroadcastOnCrashDumpArgs){
|
||||
stringstream ss;
|
||||
ss << "## crash date:" << currentDateTime() << endl;
|
||||
ss << "## crash date:" << getTimeStr("%Y-%m-%d %H:%M:%S") << endl;
|
||||
ss << "## exe: " << exeName() << endl;
|
||||
ss << "## signal: " << sig << endl;
|
||||
ss << "## stack: " << endl;
|
||||
@ -391,8 +175,9 @@ void System::systemSetup(){
|
||||
ofstream out(StrPrinter << exeDir() << "/crash." << getpid(), ios::out | ios::binary | ios::trunc);
|
||||
out << stack_info;
|
||||
out.flush();
|
||||
|
||||
cerr << stack_info << endl;
|
||||
});
|
||||
#endif// ANDROID
|
||||
#endif//!defined(_WIN32)
|
||||
}
|
||||
|
||||
|
@ -24,67 +24,17 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef IPTV_BASH_H
|
||||
#define IPTV_BASH_H
|
||||
#ifndef ZLMEDIAKIT_SYSTEM_H
|
||||
#define ZLMEDIAKIT_SYSTEM_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Util/NoticeCenter.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
class System {
|
||||
public:
|
||||
typedef struct {
|
||||
uint32_t task_total;
|
||||
uint32_t task_running;
|
||||
uint32_t task_sleeping;
|
||||
uint32_t task_stopped;
|
||||
|
||||
uint64_t mem_total;
|
||||
uint64_t mem_free;
|
||||
uint64_t mem_used;
|
||||
|
||||
float cpu_user;
|
||||
float cpu_sys;
|
||||
float cpu_idle;
|
||||
} SystemUsage;
|
||||
|
||||
typedef struct {
|
||||
uint64_t recv_bytes;
|
||||
uint64_t recv_packets;
|
||||
uint64_t snd_bytes;
|
||||
uint64_t snd_packets;
|
||||
string interface;
|
||||
} NetworkUsage;
|
||||
|
||||
typedef struct {
|
||||
uint64_t available;
|
||||
uint64_t used;
|
||||
float used_per;
|
||||
string mounted_on;
|
||||
string filesystem;
|
||||
bool mounted;
|
||||
} DiskUsage;
|
||||
|
||||
typedef struct {
|
||||
uint32_t established;
|
||||
uint32_t syn_recv;
|
||||
uint32_t time_wait;
|
||||
uint32_t close_wait;
|
||||
} TcpUsage;
|
||||
|
||||
static bool getSystemUsage(SystemUsage &usage);
|
||||
static bool getNetworkUsage(vector<NetworkUsage> &usage);
|
||||
static bool getTcpUsage(TcpUsage &usage);
|
||||
static string execute(const string &cmd);
|
||||
static void startDaemon();
|
||||
static void systemSetup();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //IPTV_BASH_H
|
||||
#endif //ZLMEDIAKIT_SYSTEM_H
|
||||
|
@ -46,40 +46,12 @@
|
||||
#include "WebApi.h"
|
||||
#include "WebHook.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include "Rtp/RtpSelector.h"
|
||||
#include "FFmpegSource.h"
|
||||
#endif//!defined(_WIN32)
|
||||
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
|
||||
typedef map<string,variant,StrCaseCompare> ApiArgsType;
|
||||
|
||||
|
||||
#define API_ARGS TcpSession &sender, \
|
||||
HttpSession::KeyValue &headerIn, \
|
||||
HttpSession::KeyValue &headerOut, \
|
||||
ApiArgsType &allArgs, \
|
||||
Json::Value &val
|
||||
|
||||
#define API_REGIST(field, name, ...) \
|
||||
s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ \
|
||||
static auto lam = [&](API_ARGS) __VA_ARGS__ ; \
|
||||
lam(sender,headerIn, headerOut, allArgs, val); \
|
||||
invoker("200 OK", headerOut, val.toStyledString()); \
|
||||
});
|
||||
|
||||
#define API_REGIST_INVOKER(field, name, ...) \
|
||||
s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__);
|
||||
|
||||
//异步http api lambad定义
|
||||
typedef std::function<void(API_ARGS,const HttpSession::HttpResponseInvoker &invoker)> AsyncHttpApi;
|
||||
//api列表
|
||||
static map<string, AsyncHttpApi> s_map_api;
|
||||
|
||||
namespace API {
|
||||
typedef enum {
|
||||
InvalidArgs = -300,
|
||||
@ -99,6 +71,7 @@ static onceToken token([]() {
|
||||
});
|
||||
}//namespace API
|
||||
|
||||
|
||||
class ApiRetException: public std::runtime_error {
|
||||
public:
|
||||
ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
|
||||
@ -128,6 +101,27 @@ public:
|
||||
~SuccessException() = default;
|
||||
};
|
||||
|
||||
#define API_ARGS1 TcpSession &sender,HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, ApiArgsType &allArgs, Json::Value &val
|
||||
#define API_ARGS2 API_ARGS1, const HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_VALUE1 sender,headerIn,headerOut,allArgs,val
|
||||
#define API_ARGS_VALUE2 API_ARGS_VALUE1, invoker
|
||||
|
||||
typedef map<string, variant, StrCaseCompare> ApiArgsType;
|
||||
//http api列表
|
||||
static map<string, std::function<void(API_ARGS2)> > s_map_api;
|
||||
|
||||
template<typename FUNC>
|
||||
static void api_regist1(const string &api_path, FUNC &&func) {
|
||||
s_map_api.emplace(api_path, [func](API_ARGS2) {
|
||||
func(API_ARGS_VALUE1);
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
});
|
||||
}
|
||||
|
||||
template<typename FUNC>
|
||||
static void api_regist2(const string &api_path, FUNC &&func) {
|
||||
s_map_api.emplace(api_path, std::forward<FUNC>(func));
|
||||
}
|
||||
|
||||
//获取HTTP请求中url参数、content参数
|
||||
static ApiArgsType getAllArgs(const Parser &parser) {
|
||||
@ -153,9 +147,8 @@ static ApiArgsType getAllArgs(const Parser &parser) {
|
||||
WarnL << "invalid Content-Type:" << parser["Content-Type"];
|
||||
}
|
||||
|
||||
auto &urlArgs = parser.getUrlArgs();
|
||||
for (auto &pr : urlArgs) {
|
||||
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
||||
for (auto &pr : parser.getUrlArgs()) {
|
||||
allArgs[pr.first] = pr.second;
|
||||
}
|
||||
return std::move(allArgs);
|
||||
}
|
||||
@ -266,10 +259,8 @@ static inline string getProxyKey(const string &vhost,const string &app,const str
|
||||
return vhost + "/" + app + "/" + stream;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
|
||||
static recursive_mutex s_ffmpegMapMtx;
|
||||
#endif//#if !defined(_WIN32)
|
||||
|
||||
/**
|
||||
* 安装api接口
|
||||
@ -278,12 +269,11 @@ static recursive_mutex s_ffmpegMapMtx;
|
||||
*/
|
||||
void installWebApi() {
|
||||
addHttpListener();
|
||||
|
||||
GET_CONFIG(string,api_secret,API::kSecret);
|
||||
|
||||
//获取线程负载
|
||||
//测试url http://127.0.0.1/index/api/getThreadsLoad
|
||||
API_REGIST_INVOKER(api, getThreadsLoad, {
|
||||
api_regist2("/index/api/getThreadsLoad",[](API_ARGS2){
|
||||
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||
Value val;
|
||||
auto vec = EventPollerPool::Instance().getExecutorLoad();
|
||||
@ -294,13 +284,14 @@ void installWebApi() {
|
||||
obj["delay"] = vecDelay[i++];
|
||||
val["data"].append(obj);
|
||||
}
|
||||
val["code"] = API::Success;
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
//获取后台工作线程负载
|
||||
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
|
||||
API_REGIST_INVOKER(api, getWorkThreadsLoad, {
|
||||
api_regist2("/index/api/getWorkThreadsLoad", [](API_ARGS2){
|
||||
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||
Value val;
|
||||
auto vec = WorkThreadPool::Instance().getExecutorLoad();
|
||||
@ -311,13 +302,14 @@ void installWebApi() {
|
||||
obj["delay"] = vecDelay[i++];
|
||||
val["data"].append(obj);
|
||||
}
|
||||
val["code"] = API::Success;
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
//获取服务器配置
|
||||
//测试url http://127.0.0.1/index/api/getServerConfig
|
||||
API_REGIST(api, getServerConfig, {
|
||||
api_regist1("/index/api/getServerConfig",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
Value obj;
|
||||
for (auto &pr : mINI::Instance()) {
|
||||
@ -329,7 +321,7 @@ void installWebApi() {
|
||||
//设置服务器配置
|
||||
//测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
|
||||
//你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参
|
||||
API_REGIST(api, setServerConfig, {
|
||||
api_regist1("/index/api/setServerConfig",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
auto &ini = mINI::Instance();
|
||||
int changed = API::Success;
|
||||
@ -347,25 +339,35 @@ void installWebApi() {
|
||||
}
|
||||
if (changed > 0) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||
ini.dumpFile();
|
||||
ini.dumpFile(g_ini_file);
|
||||
}
|
||||
val["changed"] = changed;
|
||||
});
|
||||
|
||||
|
||||
//获取服务器api列表
|
||||
//测试url http://127.0.0.1/index/api/getApiList
|
||||
API_REGIST(api,getApiList,{
|
||||
static auto s_get_api_list = [](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
for(auto &pr : s_map_api){
|
||||
val["data"].append(pr.first);
|
||||
}
|
||||
};
|
||||
|
||||
//获取服务器api列表
|
||||
//测试url http://127.0.0.1/index/api/getApiList
|
||||
api_regist1("/index/api/getApiList",[](API_ARGS1){
|
||||
s_get_api_list(API_ARGS_VALUE1);
|
||||
});
|
||||
|
||||
//获取服务器api列表
|
||||
//测试url http://127.0.0.1/index/
|
||||
api_regist1("/index/",[](API_ARGS1){
|
||||
s_get_api_list(API_ARGS_VALUE1);
|
||||
});
|
||||
|
||||
#if !defined(_WIN32)
|
||||
//重启服务器,只有Daemon方式才能重启,否则是直接关闭!
|
||||
//测试url http://127.0.0.1/index/api/restartServer
|
||||
API_REGIST(api,restartServer,{
|
||||
api_regist1("/index/api/restartServer",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
|
||||
//尝试正常退出
|
||||
@ -388,37 +390,68 @@ void installWebApi() {
|
||||
//测试url0(获取所有流) http://127.0.0.1/index/api/getMediaList
|
||||
//测试url1(获取虚拟主机为"__defaultVost__"的流) http://127.0.0.1/index/api/getMediaList?vhost=__defaultVost__
|
||||
//测试url2(获取rtsp类型的流) http://127.0.0.1/index/api/getMediaList?schema=rtsp
|
||||
API_REGIST(api,getMediaList,{
|
||||
api_regist1("/index/api/getMediaList",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
//获取所有MediaSource列表
|
||||
val["code"] = API::Success;
|
||||
val["msg"] = "success";
|
||||
MediaSource::for_each_media([&](const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const MediaSource::Ptr &media){
|
||||
if(!allArgs["schema"].empty() && allArgs["schema"] != schema){
|
||||
MediaSource::for_each_media([&](const MediaSource::Ptr &media){
|
||||
if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["vhost"].empty() && allArgs["vhost"] != vhost){
|
||||
if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["app"].empty() && allArgs["app"] != app){
|
||||
if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
|
||||
return;
|
||||
}
|
||||
Value item;
|
||||
item["schema"] = schema;
|
||||
item["vhost"] = vhost;
|
||||
item["app"] = app;
|
||||
item["stream"] = stream;
|
||||
item["schema"] = media->getSchema();
|
||||
item["vhost"] = media->getVhost();
|
||||
item["app"] = media->getApp();
|
||||
item["stream"] = media->getId();
|
||||
item["readerCount"] = media->readerCount();
|
||||
item["totalReaderCount"] = media->totalReaderCount();
|
||||
for(auto &track : media->getTracks()){
|
||||
Value obj;
|
||||
obj["codec_id"] = track->getCodecId();
|
||||
obj["codec_type"] = track->getTrackType();
|
||||
obj["ready"] = track->ready();
|
||||
item["tracks"].append(obj);
|
||||
}
|
||||
val["data"].append(item);
|
||||
});
|
||||
});
|
||||
|
||||
//测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
|
||||
api_regist1("/index/api/isMediaOnline",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("schema","vhost","app","stream");
|
||||
val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false));
|
||||
});
|
||||
|
||||
//测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
|
||||
api_regist1("/index/api/getMediaInfo",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("schema","vhost","app","stream");
|
||||
auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false);
|
||||
if(!src){
|
||||
val["online"] = false;
|
||||
return;
|
||||
}
|
||||
val["online"] = true;
|
||||
val["readerCount"] = src->readerCount();
|
||||
val["totalReaderCount"] = src->totalReaderCount();
|
||||
for(auto &track : src->getTracks()){
|
||||
Value obj;
|
||||
obj["codec_id"] = track->getCodecId();
|
||||
obj["codec_type"] = track->getTrackType();
|
||||
obj["ready"] = track->ready();
|
||||
val["tracks"].append(obj);
|
||||
}
|
||||
});
|
||||
|
||||
//主动关断流,包括关断拉流、推流
|
||||
//测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
|
||||
API_REGIST(api,close_stream,{
|
||||
api_regist1("/index/api/close_stream",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("schema","vhost","app","stream");
|
||||
//踢掉推流器
|
||||
@ -428,25 +461,60 @@ void installWebApi() {
|
||||
allArgs["stream"]);
|
||||
if(src){
|
||||
bool flag = src->close(allArgs["force"].as<bool>());
|
||||
val["code"] = flag ? 0 : -1;
|
||||
val["result"] = flag ? 0 : -1;
|
||||
val["msg"] = flag ? "success" : "close failed";
|
||||
}else{
|
||||
val["code"] = -2;
|
||||
val["result"] = -2;
|
||||
val["msg"] = "can not find the stream";
|
||||
}
|
||||
});
|
||||
|
||||
//批量主动关断流,包括关断拉流、推流
|
||||
//测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
|
||||
api_regist1("/index/api/close_streams",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
//筛选命中个数
|
||||
int count_hit = 0;
|
||||
int count_closed = 0;
|
||||
list<MediaSource::Ptr> media_list;
|
||||
MediaSource::for_each_media([&](const MediaSource::Ptr &media){
|
||||
if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
|
||||
return;
|
||||
}
|
||||
++count_hit;
|
||||
media_list.emplace_back(media);
|
||||
});
|
||||
|
||||
bool force = allArgs["force"].as<bool>();
|
||||
for(auto &media : media_list){
|
||||
if(media->close(force)){
|
||||
++count_closed;
|
||||
}
|
||||
}
|
||||
val["count_hit"] = count_hit;
|
||||
val["count_closed"] = count_closed;
|
||||
});
|
||||
|
||||
//获取所有TcpSession列表信息
|
||||
//可以根据本地端口和远端ip来筛选
|
||||
//测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
|
||||
API_REGIST(api,getAllSession,{
|
||||
api_regist1("/index/api/getAllSession",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
Value jsession;
|
||||
uint16_t local_port = allArgs["local_port"].as<uint16_t>();
|
||||
string &peer_ip = allArgs["peer_ip"];
|
||||
|
||||
SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){
|
||||
if(local_port != API::Success && local_port != session->get_local_port()){
|
||||
if(local_port != 0 && local_port != session->get_local_port()){
|
||||
return;
|
||||
}
|
||||
if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){
|
||||
@ -464,19 +532,42 @@ void installWebApi() {
|
||||
|
||||
//断开tcp连接,比如说可以断开rtsp、rtmp播放器等
|
||||
//测试url http://127.0.0.1/index/api/kick_session?id=123456
|
||||
API_REGIST(api,kick_session,{
|
||||
api_regist1("/index/api/kick_session",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("id");
|
||||
//踢掉tcp会话
|
||||
auto session = SessionMap::Instance().get(allArgs["id"]);
|
||||
if(!session){
|
||||
val["code"] = API::OtherFailed;
|
||||
val["msg"] = "can not find the target";
|
||||
return;
|
||||
throw ApiRetException("can not find the target",API::OtherFailed);
|
||||
}
|
||||
session->safeShutdown();
|
||||
val["code"] = API::Success;
|
||||
val["msg"] = "success";
|
||||
});
|
||||
|
||||
|
||||
//批量断开tcp连接,比如说可以断开rtsp、rtmp播放器等
|
||||
//测试url http://127.0.0.1/index/api/kick_sessions?local_port=1935
|
||||
api_regist1("/index/api/kick_sessions",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
uint16_t local_port = allArgs["local_port"].as<uint16_t>();
|
||||
string &peer_ip = allArgs["peer_ip"];
|
||||
uint64_t count_hit = 0;
|
||||
|
||||
list<TcpSession::Ptr> session_list;
|
||||
SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){
|
||||
if(local_port != 0 && local_port != session->get_local_port()){
|
||||
return;
|
||||
}
|
||||
if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){
|
||||
return;
|
||||
}
|
||||
session_list.emplace_back(session);
|
||||
++count_hit;
|
||||
});
|
||||
|
||||
for(auto &session : session_list){
|
||||
session->safeShutdown();
|
||||
}
|
||||
val["count_hit"] = (Json::UInt64)count_hit;
|
||||
});
|
||||
|
||||
static auto addStreamProxy = [](const string &vhost,
|
||||
@ -521,7 +612,7 @@ void installWebApi() {
|
||||
|
||||
//动态添加rtsp/rtmp拉流代理
|
||||
//测试url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs
|
||||
API_REGIST_INVOKER(api,addStreamProxy,{
|
||||
api_regist2("/index/api/addStreamProxy",[](API_ARGS2){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
|
||||
addStreamProxy(allArgs["vhost"],
|
||||
@ -546,15 +637,14 @@ void installWebApi() {
|
||||
|
||||
//关闭拉流代理
|
||||
//测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
|
||||
API_REGIST(api,delStreamProxy,{
|
||||
api_regist1("/index/api/delStreamProxy",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("key");
|
||||
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||
val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
|
||||
});
|
||||
|
||||
#if !defined(_WIN32)
|
||||
static auto addFFmepgSource = [](const string &src_url,
|
||||
static auto addFFmpegSource = [](const string &src_url,
|
||||
const string &dst_url,
|
||||
int timeout_ms,
|
||||
const function<void(const SockException &ex,const string &key)> &cb){
|
||||
@ -584,14 +674,14 @@ void installWebApi() {
|
||||
|
||||
//动态添加rtsp/rtmp拉流代理
|
||||
//测试url http://127.0.0.1/index/api/addFFmpegSource?src_url=http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8&dst_url=rtmp://127.0.0.1/live/hks2&timeout_ms=10000
|
||||
API_REGIST_INVOKER(api,addFFmpegSource,{
|
||||
api_regist2("/index/api/addFFmpegSource",[](API_ARGS2){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("src_url","dst_url","timeout_ms");
|
||||
auto src_url = allArgs["src_url"];
|
||||
auto dst_url = allArgs["dst_url"];
|
||||
int timeout_ms = allArgs["timeout_ms"];
|
||||
|
||||
addFFmepgSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
|
||||
addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
|
||||
if(ex){
|
||||
const_cast<Value &>(val)["code"] = API::OtherFailed;
|
||||
const_cast<Value &>(val)["msg"] = ex.what();
|
||||
@ -602,25 +692,127 @@ void installWebApi() {
|
||||
});
|
||||
});
|
||||
|
||||
//关闭拉流代理
|
||||
//测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
|
||||
API_REGIST(api,delFFmepgSource,{
|
||||
|
||||
static auto api_delFFmpegSource = [](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("key");
|
||||
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
||||
val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
|
||||
};
|
||||
|
||||
//关闭拉流代理
|
||||
//测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
|
||||
api_regist1("/index/api/delFFmpegSource",[](API_ARGS1){
|
||||
api_delFFmpegSource(API_ARGS_VALUE1);
|
||||
});
|
||||
|
||||
//此处为了兼容之前的拼写错误
|
||||
api_regist1("/index/api/delFFmepgSource",[](API_ARGS1){
|
||||
api_delFFmpegSource(API_ARGS_VALUE1);
|
||||
});
|
||||
#endif
|
||||
|
||||
//新增http api下载可执行程序文件接口
|
||||
//测试url http://127.0.0.1/index/api/downloadBin
|
||||
API_REGIST_INVOKER(api,downloadBin,{
|
||||
api_regist2("/index/api/downloadBin",[](API_ARGS2){
|
||||
CHECK_SECRET();
|
||||
invoker.responseFile(headerIn,StrCaseMap(),exePath());
|
||||
});
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
api_regist1("/index/api/getSsrcInfo",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("ssrc");
|
||||
uint32_t ssrc = 0;
|
||||
stringstream ss(allArgs["ssrc"]);
|
||||
ss >> std::hex >> ssrc;
|
||||
|
||||
auto process = RtpSelector::Instance().getProcess(ssrc,false);
|
||||
if(!process){
|
||||
val["exist"] = false;
|
||||
return;
|
||||
}
|
||||
val["exist"] = true;
|
||||
val["peer_ip"] = process->get_peer_ip();
|
||||
val["peer_port"] = process->get_peer_port();
|
||||
});
|
||||
#endif//ENABLE_RTPPROXY
|
||||
|
||||
// 开始录制hls或MP4
|
||||
api_regist1("/index/api/startRecord",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("type","vhost","app","stream","wait_for_record","continue_record");
|
||||
|
||||
int result = Recorder::startRecord((Recorder::type)allArgs["type"].as<int>(),
|
||||
allArgs["vhost"],
|
||||
allArgs["app"],
|
||||
allArgs["stream"],
|
||||
allArgs["customized_path"],
|
||||
allArgs["wait_for_record"],
|
||||
allArgs["continue_record"]);
|
||||
val["result"] = result;
|
||||
});
|
||||
|
||||
// 停止录制hls或MP4
|
||||
api_regist1("/index/api/stopRecord",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("type","vhost","app","stream");
|
||||
int result = Recorder::stopRecord((Recorder::type)allArgs["type"].as<int>(),
|
||||
allArgs["vhost"],
|
||||
allArgs["app"],
|
||||
allArgs["stream"]);
|
||||
val["result"] = result;
|
||||
});
|
||||
|
||||
// 获取hls或MP4录制状态
|
||||
api_regist1("/index/api/getRecordStatus",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("type","vhost","app","stream");
|
||||
auto status = Recorder::getRecordStatus((Recorder::type)allArgs["type"].as<int>(),
|
||||
allArgs["vhost"],
|
||||
allArgs["app"],
|
||||
allArgs["stream"]);
|
||||
val["status"] = (int)status;
|
||||
});
|
||||
|
||||
//获取录像文件夹列表或mp4文件列表
|
||||
//http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
|
||||
api_regist1("/index/api/getMp4RecordFile", [](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream");
|
||||
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"],allArgs["stream"]);
|
||||
auto period = allArgs["period"];
|
||||
|
||||
//判断是获取mp4文件列表还是获取文件夹列表
|
||||
bool search_mp4 = period.size() == sizeof("2020-02-01") - 1;
|
||||
if (search_mp4) {
|
||||
record_path = record_path + period + "/";
|
||||
}
|
||||
|
||||
Json::Value paths(arrayValue);
|
||||
//这是筛选日期,获取文件夹列表
|
||||
File::scanDir(record_path, [&](const string &path, bool isDir) {
|
||||
int pos = path.rfind('/');
|
||||
if (pos != string::npos) {
|
||||
string relative_path = path.substr(pos + 1);
|
||||
if (search_mp4) {
|
||||
if (!isDir) {
|
||||
//我们只收集mp4文件,对文件夹不感兴趣
|
||||
paths.append(relative_path);
|
||||
}
|
||||
} else if (isDir && relative_path.find(period) == 0) {
|
||||
//匹配到对应日期的文件夹
|
||||
paths.append(relative_path);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, false);
|
||||
|
||||
val["data"]["rootPath"] = record_path;
|
||||
val["data"]["paths"] = paths;
|
||||
});
|
||||
|
||||
////////////以下是注册的Hook API////////////
|
||||
API_REGIST(hook,on_publish,{
|
||||
api_regist1("/index/hook/on_publish",[](API_ARGS1){
|
||||
//开始推流事件
|
||||
//转换成rtsp或rtmp
|
||||
val["enableRtxp"] = true;
|
||||
@ -630,23 +822,23 @@ void installWebApi() {
|
||||
val["enableMP4"] = false;
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_play,{
|
||||
api_regist1("/index/hook/on_play",[](API_ARGS1){
|
||||
//开始播放事件
|
||||
throw SuccessException();
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_flow_report,{
|
||||
api_regist1("/index/hook/on_flow_report",[](API_ARGS1){
|
||||
//流量统计hook api
|
||||
throw SuccessException();
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_rtsp_realm,{
|
||||
api_regist1("/index/hook/on_rtsp_realm",[](API_ARGS1){
|
||||
//rtsp是否需要鉴权,默认需要鉴权
|
||||
val["code"] = API::Success;
|
||||
val["realm"] = "zlmediakit_reaml";
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_rtsp_auth,{
|
||||
api_regist1("/index/hook/on_rtsp_auth",[](API_ARGS1){
|
||||
//rtsp鉴权密码,密码等于用户名
|
||||
//rtsp可以有双重鉴权!后面还会触发on_play事件
|
||||
CHECK_ARGS("user_name");
|
||||
@ -655,14 +847,14 @@ void installWebApi() {
|
||||
val["passwd"] = allArgs["user_name"].data();
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_stream_changed,{
|
||||
api_regist1("/index/hook/on_stream_changed",[](API_ARGS1){
|
||||
//媒体注册或反注册事件
|
||||
throw SuccessException();
|
||||
});
|
||||
|
||||
|
||||
#if !defined(_WIN32)
|
||||
API_REGIST_INVOKER(hook,on_stream_not_found_ffmpeg,{
|
||||
api_regist2("/index/hook/on_stream_not_found_ffmpeg",[](API_ARGS2){
|
||||
//媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost","app","stream");
|
||||
@ -677,7 +869,7 @@ void installWebApi() {
|
||||
<< allArgs["stream"] << "?vhost="
|
||||
<< allArgs["vhost"];
|
||||
|
||||
addFFmepgSource("http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
|
||||
addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
|
||||
dst_url,
|
||||
(1000 * timeout_sec) - 500,
|
||||
[invoker,val,headerOut](const SockException &ex,const string &key){
|
||||
@ -692,7 +884,7 @@ void installWebApi() {
|
||||
});
|
||||
#endif//!defined(_WIN32)
|
||||
|
||||
API_REGIST_INVOKER(hook,on_stream_not_found,{
|
||||
api_regist2("/index/hook/on_stream_not_found",[](API_ARGS2){
|
||||
//媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost","app","stream");
|
||||
@ -718,17 +910,17 @@ void installWebApi() {
|
||||
});
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_record_mp4,{
|
||||
api_regist1("/index/hook/on_record_mp4",[](API_ARGS1){
|
||||
//录制mp4分片完毕事件
|
||||
throw SuccessException();
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_shell_login,{
|
||||
api_regist1("/index/hook/on_shell_login",[](API_ARGS1){
|
||||
//shell登录调试事件
|
||||
throw SuccessException();
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_stream_none_reader,{
|
||||
api_regist1("/index/hook/on_stream_none_reader",[](API_ARGS1){
|
||||
//无人观看流默认关闭
|
||||
val["close"] = true;
|
||||
});
|
||||
@ -738,7 +930,7 @@ void installWebApi() {
|
||||
return true;
|
||||
};
|
||||
|
||||
API_REGIST(hook,on_http_access,{
|
||||
api_regist1("/index/hook/on_http_access",[](API_ARGS1){
|
||||
//在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
|
||||
if(!checkAccess(allArgs["params"])){
|
||||
//无访问权限
|
||||
@ -759,6 +951,12 @@ void installWebApi() {
|
||||
});
|
||||
|
||||
|
||||
api_regist1("/index/hook/on_server_started",[](API_ARGS1){
|
||||
//服务器重启报告
|
||||
throw SuccessException();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
void unInstallWebApi(){
|
||||
@ -767,10 +965,8 @@ void unInstallWebApi(){
|
||||
s_proxyMap.clear();
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
|
||||
s_ffmpegMap.clear();
|
||||
}
|
||||
#endif
|
||||
}
|
@ -47,5 +47,7 @@ extern const string kPort;
|
||||
|
||||
void installWebApi();
|
||||
void unInstallWebApi();
|
||||
//配置文件路径
|
||||
extern string g_ini_file;
|
||||
|
||||
#endif //ZLMEDIAKIT_WEBAPI_H
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "Rtsp/RtspSession.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "WebHook.h"
|
||||
#include "Record/MP4Recorder.h"
|
||||
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
@ -71,6 +72,7 @@ const string kOnRecordMp4 = HOOK_FIELD"on_record_mp4";
|
||||
const string kOnShellLogin = HOOK_FIELD"on_shell_login";
|
||||
const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader";
|
||||
const string kOnHttpAccess = HOOK_FIELD"on_http_access";
|
||||
const string kOnServerStarted = HOOK_FIELD"on_server_started";
|
||||
const string kAdminParams = HOOK_FIELD"admin_params";
|
||||
|
||||
onceToken token([](){
|
||||
@ -87,6 +89,7 @@ onceToken token([](){
|
||||
mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login";
|
||||
mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader";
|
||||
mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access";
|
||||
mINI::Instance()[kOnServerStarted] = "https://127.0.0.1/index/hook/on_server_started";
|
||||
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
||||
},nullptr);
|
||||
}//namespace Hook
|
||||
@ -177,6 +180,20 @@ static ArgsType make_json(const MediaInfo &args){
|
||||
return std::move(body);
|
||||
}
|
||||
|
||||
static void reportServerStarted(){
|
||||
GET_CONFIG(bool,hook_enable,Hook::kEnable);
|
||||
GET_CONFIG(string,hook_server_started,Hook::kOnServerStarted);
|
||||
if(!hook_enable || hook_server_started.empty()){
|
||||
return;
|
||||
}
|
||||
|
||||
ArgsType body;
|
||||
for (auto &pr : mINI::Instance()) {
|
||||
body[pr.first] = (string &) pr.second;
|
||||
}
|
||||
//执行hook
|
||||
do_http_hook(hook_server_started,body, nullptr);
|
||||
}
|
||||
|
||||
void installWebHook(){
|
||||
GET_CONFIG(bool,hook_enable,Hook::kEnable);
|
||||
@ -194,8 +211,11 @@ void installWebHook(){
|
||||
GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
|
||||
GET_CONFIG(bool,toRtxp,General::kPublishToRtxp);
|
||||
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
||||
GET_CONFIG(bool,toMP4,General::kPublishToMP4);
|
||||
if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
||||
invoker("",true, true,false);
|
||||
invoker("",toRtxp,toHls,toMP4);
|
||||
return;
|
||||
}
|
||||
//异步执行该hook api,防止阻塞NoticeCenter
|
||||
@ -207,9 +227,9 @@ void installWebHook(){
|
||||
do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
|
||||
if(err.empty()){
|
||||
//推流鉴权成功
|
||||
bool enableRtxp = true;
|
||||
bool enableHls = true;
|
||||
bool enableMP4 = false;
|
||||
bool enableRtxp = toRtxp;
|
||||
bool enableHls = toHls;
|
||||
bool enableMP4 = toMP4;
|
||||
|
||||
//兼容用户不传递enableRtxp、enableHls、enableMP4参数
|
||||
if(obj.isMember("enableRtxp")){
|
||||
@ -249,16 +269,16 @@ void installWebHook(){
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){
|
||||
if(!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
||||
if(!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || peerIP == "127.0.0.1"){
|
||||
return;
|
||||
}
|
||||
auto body = make_json(args);
|
||||
body["ip"] = sender.get_peer_ip();
|
||||
body["port"] = sender.get_peer_port();
|
||||
body["id"] = sender.getIdentifier();
|
||||
body["totalBytes"] = (Json::UInt64)totalBytes;
|
||||
body["duration"] = (Json::UInt64)totalDuration;
|
||||
body["player"] = isPlayer;
|
||||
body["ip"] = peerIP;
|
||||
body["port"] = peerPort;
|
||||
body["id"] = sessionIdentifier;
|
||||
//执行hook
|
||||
do_http_hook(hook_flowreport,body, nullptr);
|
||||
});
|
||||
@ -321,10 +341,10 @@ void installWebHook(){
|
||||
}
|
||||
ArgsType body;
|
||||
body["regist"] = bRegist;
|
||||
body["schema"] = schema;
|
||||
body["vhost"] = vhost;
|
||||
body["app"] = app;
|
||||
body["stream"] = stream;
|
||||
body["schema"] = sender.getSchema();
|
||||
body["vhost"] = sender.getVhost();
|
||||
body["app"] = sender.getApp();
|
||||
body["stream"] = sender.getId();
|
||||
//执行hook
|
||||
do_http_hook(hook_stream_chaned,body, nullptr);
|
||||
});
|
||||
@ -408,7 +428,7 @@ void installWebHook(){
|
||||
/**
|
||||
* kBroadcastHttpAccess事件触发机制
|
||||
* 1、根据http请求头查找cookie,找到进入步骤3
|
||||
* 2、根据http url参数(如果没有根据ip+端口号)查找cookie,如果还是未找到cookie则进入步骤5
|
||||
* 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5
|
||||
* 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
|
||||
* 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
|
||||
* 5、触发kBroadcastHttpAccess事件
|
||||
@ -421,7 +441,7 @@ void installWebHook(){
|
||||
//如果没有url参数,客户端又不支持cookie,那么会根据ip和端口追踪用户
|
||||
//追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
|
||||
if(sender.get_peer_ip() == "127.0.0.1" && args._param_strs == hook_adminparams){
|
||||
if(sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams){
|
||||
//如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时
|
||||
invoker("","",60 * 60);
|
||||
return;
|
||||
@ -456,6 +476,9 @@ void installWebHook(){
|
||||
invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt());
|
||||
});
|
||||
});
|
||||
|
||||
//汇报服务器重新启动
|
||||
reportServerStarted();
|
||||
}
|
||||
|
||||
void unInstallWebHook(){
|
||||
|
111
server/main.cpp
111
server/main.cpp
@ -38,11 +38,11 @@
|
||||
#include "Common/config.h"
|
||||
#include "Rtsp/UDPServer.h"
|
||||
#include "Rtsp/RtspSession.h"
|
||||
#include "Rtp/RtpSession.h"
|
||||
#include "Rtmp/RtmpSession.h"
|
||||
#include "Shell/ShellSession.h"
|
||||
#include "Rtmp/FlvMuxer.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "Http/WebSocketSession.h"
|
||||
#include "Rtp/UdpRecver.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebHook.h"
|
||||
|
||||
@ -58,36 +58,31 @@ namespace mediakit {
|
||||
////////////HTTP配置///////////
|
||||
namespace Http {
|
||||
#define HTTP_FIELD "http."
|
||||
#define HTTP_PORT 80
|
||||
const string kPort = HTTP_FIELD"port";
|
||||
#define HTTPS_PORT 443
|
||||
const string kSSLPort = HTTP_FIELD"sslport";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = HTTP_PORT;
|
||||
mINI::Instance()[kSSLPort] = HTTPS_PORT;
|
||||
mINI::Instance()[kPort] = 80;
|
||||
mINI::Instance()[kSSLPort] = 443;
|
||||
},nullptr);
|
||||
}//namespace Http
|
||||
|
||||
////////////SHELL配置///////////
|
||||
namespace Shell {
|
||||
#define SHELL_FIELD "shell."
|
||||
#define SHELL_PORT 9000
|
||||
const string kPort = SHELL_FIELD"port";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = SHELL_PORT;
|
||||
mINI::Instance()[kPort] = 9000;
|
||||
},nullptr);
|
||||
} //namespace Shell
|
||||
|
||||
////////////RTSP服务器配置///////////
|
||||
namespace Rtsp {
|
||||
#define RTSP_FIELD "rtsp."
|
||||
#define RTSP_PORT 554
|
||||
#define RTSPS_PORT 322
|
||||
const string kPort = RTSP_FIELD"port";
|
||||
const string kSSLPort = RTSP_FIELD"sslport";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = RTSP_PORT;
|
||||
mINI::Instance()[kSSLPort] = RTSPS_PORT;
|
||||
mINI::Instance()[kPort] = 554;
|
||||
mINI::Instance()[kSSLPort] = 332;
|
||||
},nullptr);
|
||||
|
||||
} //namespace Rtsp
|
||||
@ -95,12 +90,21 @@ onceToken token1([](){
|
||||
////////////RTMP服务器配置///////////
|
||||
namespace Rtmp {
|
||||
#define RTMP_FIELD "rtmp."
|
||||
#define RTMP_PORT 1935
|
||||
const string kPort = RTMP_FIELD"port";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = RTMP_PORT;
|
||||
mINI::Instance()[kPort] = 1935;
|
||||
},nullptr);
|
||||
} //namespace RTMP
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
namespace RtpProxy {
|
||||
#define RTP_PROXY_FIELD "rtp_proxy."
|
||||
const string kPort = RTP_PROXY_FIELD"port";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = 10000;
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
@ -148,7 +152,7 @@ public:
|
||||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
(exeDir() + "ssl.p12").data(),/*该选项默认值*/
|
||||
false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"ssl证书路径,支持p12/pem类型",/*该选项说明文字*/
|
||||
"ssl证书文件或文件夹,支持p12/pem类型",/*该选项说明文字*/
|
||||
nullptr);
|
||||
|
||||
(*_parser) << Option('t',/*该选项简称,如果是\x00则说明无简称*/
|
||||
@ -205,6 +209,8 @@ static void inline listen_shell_input(){
|
||||
}
|
||||
#endif//!defined(_WIN32)
|
||||
|
||||
//全局变量,在WebApi中用于保存配置文件用
|
||||
string g_ini_file;
|
||||
|
||||
int start_main(int argc,char *argv[]) {
|
||||
{
|
||||
@ -219,7 +225,7 @@ int start_main(int argc,char *argv[]) {
|
||||
bool bDaemon = cmd_main.hasKey("daemon");
|
||||
LogLevel logLevel = (LogLevel) cmd_main["level"].as<int>();
|
||||
logLevel = MIN(MAX(logLevel, LTrace), LError);
|
||||
static string ini_file = cmd_main["config"];
|
||||
g_ini_file = cmd_main["config"];
|
||||
string ssl_file = cmd_main["ssl"];
|
||||
int threads = cmd_main["threads"];
|
||||
|
||||
@ -233,6 +239,7 @@ int start_main(int argc,char *argv[]) {
|
||||
#endif//
|
||||
|
||||
#if !defined(_WIN32)
|
||||
pid_t pid = getpid();
|
||||
if (bDaemon) {
|
||||
//启动守护进程
|
||||
System::startDaemon();
|
||||
@ -244,14 +251,21 @@ int start_main(int argc,char *argv[]) {
|
||||
//启动异步日志线程
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
//加载配置文件,如果配置文件不存在就创建一个
|
||||
loadIniConfig(ini_file.data());
|
||||
loadIniConfig(g_ini_file.data());
|
||||
|
||||
//加载证书,证书包含公钥和私钥
|
||||
if(!File::is_dir(ssl_file.data())){
|
||||
//不是文件夹,加载证书,证书包含公钥和私钥
|
||||
SSL_Initor::Instance().loadCertificate(ssl_file.data());
|
||||
//信任某个自签名证书
|
||||
SSL_Initor::Instance().trustCertificate(ssl_file.data());
|
||||
//不忽略无效证书证书(例如自签名或过期证书)
|
||||
SSL_Initor::Instance().ignoreInvalidCertificate(true);
|
||||
}else{
|
||||
//加载文件夹下的所有证书
|
||||
File::scanDir(ssl_file,[](const string &path, bool isDir){
|
||||
if(!isDir){
|
||||
//最后的一个证书会当做默认证书(客户端ssl握手时未指定主机)
|
||||
SSL_Initor::Instance().loadCertificate(path.data());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
uint16_t shellPort = mINI::Instance()[Shell::kPort];
|
||||
uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
|
||||
@ -259,6 +273,7 @@ int start_main(int argc,char *argv[]) {
|
||||
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
|
||||
uint16_t httpPort = mINI::Instance()[Http::kPort];
|
||||
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
|
||||
uint16_t rtp_proxy = mINI::Instance()[RtpProxy::kPort];
|
||||
|
||||
//设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效
|
||||
EventPollerPool::setPoolSize(threads);
|
||||
@ -269,21 +284,48 @@ int start_main(int argc,char *argv[]) {
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpSrv(new TcpServer());
|
||||
|
||||
shellSrv->start<ShellSession>(shellPort);
|
||||
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
||||
rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
|
||||
//http服务器
|
||||
httpSrv->start<HttpSession>(httpPort);//默认80
|
||||
|
||||
//如果支持ssl,还可以开启https服务器
|
||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||
//https服务器,支持websocket
|
||||
httpsSrv->start<HttpsSession>(httpsPort);//默认443
|
||||
|
||||
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
||||
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
UdpRecver recver;
|
||||
TcpServer::Ptr tcpRtpServer(new TcpServer());
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
|
||||
try {
|
||||
//rtsp服务器,端口默认554
|
||||
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
||||
//rtsps服务器,端口默认322
|
||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);
|
||||
//rtmp服务器,端口默认1935
|
||||
rtmpSrv->start<RtmpSession>(rtmpPort);
|
||||
//http服务器,端口默认80
|
||||
httpSrv->start<HttpSession>(httpPort);
|
||||
//https服务器,端口默认443
|
||||
httpsSrv->start<HttpsSession>(httpsPort);
|
||||
//telnet远程调试服务器
|
||||
shellSrv->start<ShellSession>(shellPort);
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
//创建rtp udp服务器
|
||||
recver.initSock(rtp_proxy);
|
||||
//创建rtp tcp服务器
|
||||
tcpRtpServer->start<RtpSession>(rtp_proxy);
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
|
||||
}catch (std::exception &ex){
|
||||
WarnL << "端口占用或无权限:" << ex.what() << endl;
|
||||
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl;
|
||||
sleep(1);
|
||||
#if !defined(_WIN32)
|
||||
if(pid != getpid()){
|
||||
kill(pid,SIGINT);
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
installWebApi();
|
||||
InfoL << "已启动http api 接口";
|
||||
@ -306,12 +348,13 @@ int start_main(int argc,char *argv[]) {
|
||||
});// 设置退出信号
|
||||
|
||||
#if !defined(_WIN32)
|
||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(ini_file.data()); });
|
||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
|
||||
#endif
|
||||
sem.wait();
|
||||
}
|
||||
unInstallWebApi();
|
||||
unInstallWebHook();
|
||||
Recorder::stopAll();
|
||||
//休眠1秒再退出,防止资源释放顺序错误
|
||||
InfoL << "程序退出中,请等待...";
|
||||
sleep(1);
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Extension/H264.h"
|
||||
#include "Extension/H265.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
@ -103,7 +104,39 @@ void DevChannel::inputH264(const char* pcData, int iDataLen, uint32_t dts,uint32
|
||||
} else {
|
||||
prefixeSize = 0;
|
||||
}
|
||||
inputFrame(std::make_shared<H264FrameNoCacheAble>((char *)pcData,iDataLen,dts,pts,prefixeSize));
|
||||
|
||||
H264Frame::Ptr frame = std::make_shared<H264Frame>();
|
||||
frame->_dts = dts;
|
||||
frame->_pts = pts;
|
||||
frame->_buffer.assign("\x00\x00\x00\x01",4);
|
||||
frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize);
|
||||
frame->_prefix_size = 4;
|
||||
inputFrame(frame);
|
||||
}
|
||||
|
||||
void DevChannel::inputH265(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) {
|
||||
if(dts == 0){
|
||||
dts = (uint32_t)_aTicker[0].elapsedTime();
|
||||
}
|
||||
if(pts == 0){
|
||||
pts = dts;
|
||||
}
|
||||
int prefixeSize;
|
||||
if (memcmp("\x00\x00\x00\x01", pcData, 4) == 0) {
|
||||
prefixeSize = 4;
|
||||
} else if (memcmp("\x00\x00\x01", pcData, 3) == 0) {
|
||||
prefixeSize = 3;
|
||||
} else {
|
||||
prefixeSize = 0;
|
||||
}
|
||||
|
||||
H265Frame::Ptr frame = std::make_shared<H265Frame>();
|
||||
frame->_dts = dts;
|
||||
frame->_pts = pts;
|
||||
frame->_buffer.assign("\x00\x00\x00\x01",4);
|
||||
frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize);
|
||||
frame->_prefix_size = 4;
|
||||
inputFrame(frame);
|
||||
}
|
||||
|
||||
void DevChannel::inputAAC(const char* pcData, int iDataLen, uint32_t uiStamp,bool withAdtsHeader) {
|
||||
@ -135,6 +168,11 @@ void DevChannel::initVideo(const VideoInfo& info) {
|
||||
addTrack(std::make_shared<H264Track>());
|
||||
}
|
||||
|
||||
void DevChannel::initH265Video(const VideoInfo &info){
|
||||
_video = std::make_shared<VideoInfo>(info);
|
||||
addTrack(std::make_shared<H265Track>());
|
||||
}
|
||||
|
||||
void DevChannel::initAudio(const AudioInfo& info) {
|
||||
_audio = std::make_shared<AudioInfo>(info);
|
||||
addTrack(std::make_shared<AACTrack>());
|
||||
|
@ -88,6 +88,12 @@ public:
|
||||
*/
|
||||
void initVideo(const VideoInfo &info);
|
||||
|
||||
/**
|
||||
* 初始化h265视频Track
|
||||
* @param info
|
||||
*/
|
||||
void initH265Video(const VideoInfo &info);
|
||||
|
||||
/**
|
||||
* 初始化aac音频Track
|
||||
* 相当于MultiMediaSourceMuxer::addTrack(AACTrack::Ptr );
|
||||
@ -104,6 +110,15 @@ public:
|
||||
*/
|
||||
void inputH264(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
|
||||
|
||||
/**
|
||||
* 输入265帧
|
||||
* @param pcData 265单帧数据指针
|
||||
* @param iDataLen 数据指针长度
|
||||
* @param dts 解码时间戳,单位毫秒;等于0时内部会自动生成时间戳
|
||||
* @param pts 播放时间戳,单位毫秒;等于0时内部会赋值为dts
|
||||
*/
|
||||
void inputH265(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
|
||||
|
||||
/**
|
||||
* 输入可能带adts头的aac帧
|
||||
* @param pcDataWithAdts 可能带adts头的aac帧
|
||||
|
@ -26,7 +26,11 @@
|
||||
#include "MediaSink.h"
|
||||
|
||||
//最多等待未初始化的Track 10秒,超时之后会忽略未初始化的Track
|
||||
#define MAX_WAIT_MS 10000
|
||||
#define MAX_WAIT_MS_READY 10000
|
||||
|
||||
//如果添加Track,最多等待3秒
|
||||
#define MAX_WAIT_MS_ADD_TRACK 3000
|
||||
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
@ -34,63 +38,99 @@ void MediaSink::addTrack(const Track::Ptr &track_in) {
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
//克隆Track,只拷贝其数据,不拷贝其数据转发关系
|
||||
auto track = track_in->clone();
|
||||
|
||||
auto codec_id = track->getCodecId();
|
||||
_track_map[codec_id] = track;
|
||||
auto lam = [this,track](){
|
||||
_allTrackReady = false;
|
||||
_trackReadyCallback[codec_id] = [this, track]() {
|
||||
onTrackReady(track);
|
||||
};
|
||||
if(track->ready()){
|
||||
lam();
|
||||
}else{
|
||||
_anyTrackUnReady = true;
|
||||
_allTrackReady = false;
|
||||
_trackReadyCallback[codec_id] = lam;
|
||||
_ticker.resetTime();
|
||||
}
|
||||
|
||||
weak_ptr<MediaSink> weakSelf = shared_from_this();
|
||||
track->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([weakSelf](const Frame::Ptr &frame){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
return;
|
||||
}
|
||||
if(!strongSelf->_anyTrackUnReady){
|
||||
strongSelf->onTrackFrame(frame);
|
||||
track->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([this](const Frame::Ptr &frame) {
|
||||
if (_allTrackReady) {
|
||||
onTrackFrame(frame);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void MediaSink::resetTracks() {
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
_anyTrackUnReady = false;
|
||||
_allTrackReady = false;
|
||||
_track_map.clear();
|
||||
_trackReadyCallback.clear();
|
||||
_ticker.resetTime();
|
||||
_max_track_size = 2;
|
||||
}
|
||||
|
||||
void MediaSink::inputFrame(const Frame::Ptr &frame) {
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
auto codec_id = frame->getCodecId();
|
||||
auto it = _track_map.find(codec_id);
|
||||
auto it = _track_map.find(frame->getCodecId());
|
||||
if (it == _track_map.end()) {
|
||||
return;
|
||||
}
|
||||
it->second->inputFrame(frame);
|
||||
checkTrackIfReady(it->second);
|
||||
}
|
||||
|
||||
if(!_allTrackReady && !_trackReadyCallback.empty() && it->second->ready()){
|
||||
void MediaSink::checkTrackIfReady_l(const Track::Ptr &track){
|
||||
//Track由未就绪状态转换成就绪状态,我们就触发onTrackReady回调
|
||||
auto it_callback = _trackReadyCallback.find(codec_id);
|
||||
if(it_callback != _trackReadyCallback.end()){
|
||||
auto it_callback = _trackReadyCallback.find(track->getCodecId());
|
||||
if (it_callback != _trackReadyCallback.end() && track->ready()) {
|
||||
it_callback->second();
|
||||
_trackReadyCallback.erase(it_callback);
|
||||
}
|
||||
}
|
||||
|
||||
if(!_allTrackReady && (_trackReadyCallback.empty() || _ticker.elapsedTime() > MAX_WAIT_MS)){
|
||||
_allTrackReady = true;
|
||||
_anyTrackUnReady = false;
|
||||
void MediaSink::checkTrackIfReady(const Track::Ptr &track){
|
||||
if (!_allTrackReady && !_trackReadyCallback.empty()) {
|
||||
if (track) {
|
||||
checkTrackIfReady_l(track);
|
||||
} else {
|
||||
for (auto &pr : _track_map) {
|
||||
checkTrackIfReady_l(pr.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!_allTrackReady){
|
||||
if(_ticker.elapsedTime() > MAX_WAIT_MS_READY){
|
||||
//如果超过规定时间,那么不再等待并忽略未准备好的Track
|
||||
emitAllTrackReady();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_trackReadyCallback.empty()){
|
||||
//在超时时间内,如果存在未准备好的Track,那么继续等待
|
||||
return;
|
||||
}
|
||||
|
||||
if(_track_map.size() == _max_track_size){
|
||||
//如果已经添加了音视频Track,并且不存在未准备好的Track,那么说明所有Track都准备好了
|
||||
emitAllTrackReady();
|
||||
return;
|
||||
}
|
||||
|
||||
if(_track_map.size() == 1 && _ticker.elapsedTime() > MAX_WAIT_MS_ADD_TRACK){
|
||||
//如果只有一个Track,那么在该Track添加后,我们最多还等待若干时间(可能后面还会添加Track)
|
||||
emitAllTrackReady();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSink::addTrackCompleted(){
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
_max_track_size = _track_map.size();
|
||||
}
|
||||
checkTrackIfReady(nullptr);
|
||||
}
|
||||
|
||||
void MediaSink::emitAllTrackReady() {
|
||||
if (_allTrackReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_trackReadyCallback.empty()) {
|
||||
//这是超时强制忽略未准备好的Track
|
||||
_trackReadyCallback.clear();
|
||||
@ -106,26 +146,21 @@ void MediaSink::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
if (!_track_map.empty()) {
|
||||
//最少有一个有效的Track
|
||||
_allTrackReady = true;
|
||||
onAllTrackReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSink::isAllTrackReady() const {
|
||||
return _allTrackReady;
|
||||
}
|
||||
|
||||
Track::Ptr MediaSink::getTrack(TrackType type,bool trackReady) const {
|
||||
vector<Track::Ptr> MediaSink::getTracks(bool trackReady) const{
|
||||
vector<Track::Ptr> ret;
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
for (auto &pr : _track_map){
|
||||
if(pr.second->getTrackType() == type){
|
||||
if(!trackReady){
|
||||
return pr.second;
|
||||
if(trackReady && !pr.second->ready()){
|
||||
continue;
|
||||
}
|
||||
return pr.second->ready() ? pr.second : nullptr;
|
||||
ret.emplace_back(pr.second);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,11 +38,31 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class MediaSinkInterface : public FrameWriterInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<MediaSinkInterface> Ptr;
|
||||
|
||||
MediaSinkInterface(){};
|
||||
virtual ~MediaSinkInterface(){};
|
||||
|
||||
/**
|
||||
* 添加track,内部会调用Track的clone方法
|
||||
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
|
||||
* @param track
|
||||
*/
|
||||
virtual void addTrack(const Track::Ptr & track) = 0;
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
*/
|
||||
virtual void resetTracks() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 该类的作用是等待Track ready()返回true也就是就绪后再通知派生类进行下一步的操作
|
||||
* 目的是输入Frame前由Track截取处理下,以便获取有效的信息(譬如sps pps aa_cfg)
|
||||
*/
|
||||
class MediaSink : public FrameWriterInterface , public std::enable_shared_from_this<MediaSink>{
|
||||
class MediaSink : public MediaSinkInterface , public TrackSource{
|
||||
public:
|
||||
typedef std::shared_ptr<MediaSink> Ptr;
|
||||
MediaSink(){}
|
||||
@ -59,26 +79,25 @@ public:
|
||||
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
|
||||
* @param track
|
||||
*/
|
||||
virtual void addTrack(const Track::Ptr & track);
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
|
||||
/**
|
||||
* 添加Track完毕,如果是单Track,会最多等待3秒才会触发onAllTrackReady
|
||||
* 这样会增加生成流的延时,如果添加了音视频双Track,那么可以不调用此方法
|
||||
* 否则为了降低流注册延时,请手动调用此方法
|
||||
*/
|
||||
void addTrackCompleted();
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
*/
|
||||
virtual void resetTracks();
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 全部Track是否都准备好了
|
||||
* @return
|
||||
*/
|
||||
bool isAllTrackReady() const;
|
||||
|
||||
/**
|
||||
* 获取特定类型的Track
|
||||
* @param type track类型
|
||||
* 获取所有Track
|
||||
* @param trackReady 是否获取已经准备好的Track
|
||||
* @return
|
||||
*/
|
||||
Track::Ptr getTrack(TrackType type,bool trackReady = true) const;
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override ;
|
||||
protected:
|
||||
/**
|
||||
* 某track已经准备好,其ready()状态返回true,
|
||||
@ -97,13 +116,24 @@ protected:
|
||||
* @param frame
|
||||
*/
|
||||
virtual void onTrackFrame(const Frame::Ptr &frame) {};
|
||||
private:
|
||||
/**
|
||||
* 触发onAllTrackReady事件
|
||||
*/
|
||||
void emitAllTrackReady();
|
||||
|
||||
/**
|
||||
* 检查track是否准备完毕
|
||||
*/
|
||||
void checkTrackIfReady(const Track::Ptr &track);
|
||||
void checkTrackIfReady_l(const Track::Ptr &track);
|
||||
private:
|
||||
mutable recursive_mutex _mtx;
|
||||
map<int,Track::Ptr> _track_map;
|
||||
map<int,function<void()> > _trackReadyCallback;
|
||||
bool _allTrackReady = false;
|
||||
bool _anyTrackUnReady = false;
|
||||
Ticker _ticker;
|
||||
int _max_track_size = 2;
|
||||
};
|
||||
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#include "MediaSource.h"
|
||||
#include "MediaFile/MediaReader.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
#include "Util/util.h"
|
||||
#include "Network/sockutil.h"
|
||||
#include "Network/TcpSession.h"
|
||||
@ -38,17 +38,145 @@ namespace mediakit {
|
||||
recursive_mutex MediaSource::g_mtxMediaSrc;
|
||||
MediaSource::SchemaVhostAppStreamMap MediaSource::g_mapMediaSrc;
|
||||
|
||||
MediaSource::MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) :
|
||||
_strSchema(strSchema), _strApp(strApp), _strId(strId) {
|
||||
if (strVhost.empty()) {
|
||||
_strVhost = DEFAULT_VHOST;
|
||||
} else {
|
||||
_strVhost = strVhost;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSource::findAsync(const MediaInfo &info,
|
||||
const std::shared_ptr<TcpSession> &session,
|
||||
bool retry,
|
||||
MediaSource::~MediaSource() {
|
||||
unregist();
|
||||
}
|
||||
|
||||
const string& MediaSource::getSchema() const {
|
||||
return _strSchema;
|
||||
}
|
||||
|
||||
const string& MediaSource::getVhost() const {
|
||||
return _strVhost;
|
||||
}
|
||||
|
||||
const string& MediaSource::getApp() const {
|
||||
//获取该源的id
|
||||
return _strApp;
|
||||
}
|
||||
|
||||
const string& MediaSource::getId() const {
|
||||
return _strId;
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MediaSource::getTracks(bool trackReady) const {
|
||||
auto strongPtr = _track_source.lock();
|
||||
if(strongPtr){
|
||||
return strongPtr->getTracks(trackReady);
|
||||
}
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
|
||||
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
||||
_track_source = track_src;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaResetTracks, *this);
|
||||
}
|
||||
|
||||
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
const std::weak_ptr<MediaSourceEvent>& MediaSource::getListener() const{
|
||||
return _listener;
|
||||
}
|
||||
|
||||
int MediaSource::totalReaderCount(){
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return readerCount();
|
||||
}
|
||||
return listener->totalReaderCount(*this);
|
||||
}
|
||||
bool MediaSource::seekTo(uint32_t ui32Stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(*this,ui32Stamp);
|
||||
}
|
||||
|
||||
bool MediaSource::close(bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return false;
|
||||
}
|
||||
return listener->close(*this,force);
|
||||
}
|
||||
|
||||
void MediaSource::onNoneReader(){
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return;
|
||||
}
|
||||
listener->onNoneReader(*this);
|
||||
}
|
||||
|
||||
void MediaSource::for_each_media(const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
for (auto &pr0 : g_mapMediaSrc) {
|
||||
for (auto &pr1 : pr0.second) {
|
||||
for (auto &pr2 : pr1.second) {
|
||||
for (auto &pr3 : pr2.second) {
|
||||
auto src = pr3.second.lock();
|
||||
if(src){
|
||||
cb(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename MAP, typename FUNC>
|
||||
static bool searchMedia(MAP &map, const string &schema, const string &vhost, const string &app, const string &id, FUNC &&func) {
|
||||
auto it0 = map.find(schema);
|
||||
if (it0 == map.end()) {
|
||||
//未找到协议
|
||||
return false;
|
||||
}
|
||||
auto it1 = it0->second.find(vhost);
|
||||
if (it1 == it0->second.end()) {
|
||||
//未找到vhost
|
||||
return false;
|
||||
}
|
||||
auto it2 = it1->second.find(app);
|
||||
if (it2 == it1->second.end()) {
|
||||
//未找到app
|
||||
return false;
|
||||
}
|
||||
auto it3 = it2->second.find(id);
|
||||
if (it3 == it2->second.end()) {
|
||||
//未找到streamId
|
||||
return false;
|
||||
}
|
||||
return func(it0, it1, it2, it3);
|
||||
}
|
||||
|
||||
template<typename MAP, typename IT0, typename IT1, typename IT2>
|
||||
static void eraseIfEmpty(MAP &map, IT0 it0, IT1 it1, IT2 it2) {
|
||||
if (it2->second.empty()) {
|
||||
it1->second.erase(it2);
|
||||
if (it1->second.empty()) {
|
||||
it0->second.erase(it1);
|
||||
if (it0->second.empty()) {
|
||||
map.erase(it0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry,
|
||||
const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
|
||||
auto src = MediaSource::find(info._schema,
|
||||
info._vhost,
|
||||
info._app,
|
||||
info._streamid,
|
||||
true);
|
||||
auto src = MediaSource::find(info._schema, info._vhost, info._app, info._streamid, true);
|
||||
if(src || !retry){
|
||||
cb(src);
|
||||
return;
|
||||
@ -81,7 +209,11 @@ void MediaSource::findAsync(const MediaInfo &info,
|
||||
return;
|
||||
}
|
||||
|
||||
if(!bRegist || schema != info._schema || vhost != info._vhost || app != info._app ||stream != info._streamid){
|
||||
if (!bRegist ||
|
||||
sender.getSchema() != info._schema ||
|
||||
sender.getVhost() != info._vhost ||
|
||||
sender.getApp() != info._app ||
|
||||
sender.getId() != info._streamid) {
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
return;
|
||||
}
|
||||
@ -99,18 +231,18 @@ void MediaSource::findAsync(const MediaInfo &info,
|
||||
}
|
||||
DebugL << "收到媒体注册事件,回复播放器:" << info._schema << "/" << info._vhost << "/" << info._app << "/" << info._streamid;
|
||||
//再找一遍媒体源,一般能找到
|
||||
findAsync(info,strongSession,false,cb);
|
||||
findAsync_l(info,strongSession,false,cb);
|
||||
}, false);
|
||||
};
|
||||
//监听媒体注册事件
|
||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
|
||||
}
|
||||
MediaSource::Ptr MediaSource::find(
|
||||
const string &schema,
|
||||
const string &vhost_tmp,
|
||||
const string &app,
|
||||
const string &id,
|
||||
bool bMake) {
|
||||
|
||||
void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr<TcpSession> &session,const function<void(const Ptr &src)> &cb){
|
||||
return findAsync_l(info, session, true, cb);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MediaSource::find(const string &schema, const string &vhost_tmp, const string &app, const string &id, bool bMake) {
|
||||
string vhost = vhost_tmp;
|
||||
if(vhost.empty()){
|
||||
vhost = DEFAULT_VHOST;
|
||||
@ -121,11 +253,11 @@ MediaSource::Ptr MediaSource::find(
|
||||
vhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
MediaSource::Ptr ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
//查找某一媒体源,找到后返回
|
||||
searchMedia(schema, vhost, app, id,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0 ,
|
||||
searchMedia(g_mapMediaSrc, schema, vhost, app, id, [&](SchemaVhostAppStreamMap::iterator &it0,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2,
|
||||
StreamMap::iterator &it3) {
|
||||
@ -133,14 +265,16 @@ MediaSource::Ptr MediaSource::find(
|
||||
if (!ret) {
|
||||
//该对象已经销毁
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(it0,it1,it2);
|
||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(!ret && bMake){
|
||||
//未查找媒体源,则创建一个
|
||||
ret = MediaReader::onMakeMediaSource(schema, vhost,app,id);
|
||||
ret = MP4Reader::onMakeMediaSource(schema, vhost,app,id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -155,18 +289,16 @@ void MediaSource::regist() {
|
||||
g_mapMediaSrc[_strSchema][_strVhost][_strApp][_strId] = shared_from_this();
|
||||
}
|
||||
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged,
|
||||
true,
|
||||
_strSchema,
|
||||
_strVhost,
|
||||
_strApp,
|
||||
_strId,
|
||||
*this);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, true, *this);
|
||||
}
|
||||
bool MediaSource::unregist() {
|
||||
|
||||
//反注册该源
|
||||
bool MediaSource::unregist() {
|
||||
bool ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
return searchMedia(_strSchema, _strVhost, _strApp, _strId, [&](SchemaVhostAppStreamMap::iterator &it0 ,
|
||||
ret = searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2,
|
||||
StreamMap::iterator &it3) {
|
||||
@ -176,21 +308,19 @@ bool MediaSource::unregist() {
|
||||
return false;
|
||||
}
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(it0,it1,it2);
|
||||
unregisted();
|
||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
void MediaSource::unregisted(){
|
||||
|
||||
if(ret){
|
||||
InfoL << "" << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged,
|
||||
false,
|
||||
_strSchema,
|
||||
_strVhost,
|
||||
_strApp,
|
||||
_strId,
|
||||
*this);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, false, *this);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
||||
|
||||
void MediaInfo::parse(const string &url){
|
||||
//string url = "rtsp://127.0.0.1:8554/live/id?key=val&a=1&&b=2&vhost=vhost.com";
|
||||
@ -226,9 +356,9 @@ void MediaInfo::parse(const string &url){
|
||||
if(pos != string::npos){
|
||||
_streamid = steamid.substr(0,pos);
|
||||
_param_strs = steamid.substr(pos + 1);
|
||||
_params = Parser::parseArgs(_param_strs);
|
||||
if(_params.find(VHOST_KEY) != _params.end()){
|
||||
_vhost = _params[VHOST_KEY];
|
||||
auto params = Parser::parseArgs(_param_strs);
|
||||
if(params.find(VHOST_KEY) != params.end()){
|
||||
_vhost = params[VHOST_KEY];
|
||||
}
|
||||
} else{
|
||||
_streamid = steamid;
|
||||
@ -241,6 +371,8 @@ void MediaInfo::parse(const string &url){
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
|
||||
|
||||
void MediaSourceEvent::onNoneReader(MediaSource &sender){
|
||||
//没有任何读取器消费该源,表明该源可以关闭了
|
||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId();
|
||||
|
@ -54,33 +54,33 @@ class MediaSourceEvent{
|
||||
public:
|
||||
MediaSourceEvent(){};
|
||||
virtual ~MediaSourceEvent(){};
|
||||
public:
|
||||
|
||||
// 通知拖动进度条
|
||||
virtual bool seekTo(MediaSource &sender,uint32_t ui32Stamp){
|
||||
//拖动进度条
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool close(MediaSource &sender,bool force) {
|
||||
// 通知其停止推流
|
||||
virtual bool close(MediaSource &sender,bool force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通知无人观看
|
||||
virtual void onNoneReader(MediaSource &sender);
|
||||
|
||||
// 观看总人数
|
||||
virtual int totalReaderCount(MediaSource &sender) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析url获取媒体相关信息
|
||||
*/
|
||||
class MediaInfo{
|
||||
public:
|
||||
MediaInfo(){}
|
||||
MediaInfo(const string &url){
|
||||
parse(url);
|
||||
}
|
||||
~MediaInfo(){}
|
||||
|
||||
MediaInfo(const string &url){ parse(url); }
|
||||
void parse(const string &url);
|
||||
|
||||
string &operator[](const string &key){
|
||||
return _params[key];
|
||||
}
|
||||
public:
|
||||
string _schema;
|
||||
string _host;
|
||||
@ -88,11 +88,13 @@ public:
|
||||
string _vhost;
|
||||
string _app;
|
||||
string _streamid;
|
||||
StrCaseMap _params;
|
||||
string _param_strs;
|
||||
};
|
||||
|
||||
class MediaSource: public enable_shared_from_this<MediaSource> {
|
||||
/**
|
||||
* 媒体源,任何rtsp/rtmp的直播流都源自该对象
|
||||
*/
|
||||
class MediaSource: public TrackSource, public enable_shared_from_this<MediaSource> {
|
||||
public:
|
||||
typedef std::shared_ptr<MediaSource> Ptr;
|
||||
typedef unordered_map<string, weak_ptr<MediaSource> > StreamMap;
|
||||
@ -100,155 +102,64 @@ public:
|
||||
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap;
|
||||
typedef unordered_map<string, VhostAppStreamMap > SchemaVhostAppStreamMap;
|
||||
|
||||
MediaSource(const string &strSchema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strId) :
|
||||
_strSchema(strSchema),
|
||||
_strApp(strApp),
|
||||
_strId(strId) {
|
||||
if(strVhost.empty()){
|
||||
_strVhost = DEFAULT_VHOST;
|
||||
}else{
|
||||
_strVhost = strVhost;
|
||||
}
|
||||
}
|
||||
virtual ~MediaSource() {
|
||||
unregist();
|
||||
}
|
||||
MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) ;
|
||||
virtual ~MediaSource() ;
|
||||
|
||||
static Ptr find(const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &id,
|
||||
bool bMake = true) ;
|
||||
// 获取协议类型
|
||||
const string& getSchema() const;
|
||||
// 虚拟主机
|
||||
const string& getVhost() const;
|
||||
// 应用名
|
||||
const string& getApp() const;
|
||||
// 流id
|
||||
const string& getId() const;
|
||||
|
||||
static void findAsync(const MediaInfo &info,
|
||||
const std::shared_ptr<TcpSession> &session,
|
||||
bool retry,
|
||||
const function<void(const MediaSource::Ptr &src)> &cb);
|
||||
// 设置TrackSource
|
||||
void setTrackSource(const std::weak_ptr<TrackSource> &track_src);
|
||||
// 获取所有Track
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
|
||||
const string& getSchema() const {
|
||||
return _strSchema;
|
||||
}
|
||||
const string& getVhost() const {
|
||||
return _strVhost;
|
||||
}
|
||||
const string& getApp() const {
|
||||
//获取该源的id
|
||||
return _strApp;
|
||||
}
|
||||
const string& getId() const {
|
||||
return _strId;
|
||||
}
|
||||
// 设置监听者
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
// 获取监听者
|
||||
const std::weak_ptr<MediaSourceEvent>& getListener() const;
|
||||
|
||||
bool seekTo(uint32_t ui32Stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(*this,ui32Stamp);
|
||||
}
|
||||
|
||||
virtual uint32_t getTimeStamp(TrackType trackType) = 0;
|
||||
|
||||
bool close(bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return false;
|
||||
}
|
||||
return listener->close(*this,force);
|
||||
}
|
||||
|
||||
void onNoneReader(){
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return;
|
||||
}
|
||||
listener->onNoneReader(*this);
|
||||
}
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
template <typename FUN>
|
||||
static void for_each_media(FUN && fun){
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
for (auto &pr0 : g_mapMediaSrc){
|
||||
for(auto &pr1 : pr0.second){
|
||||
for(auto &pr2 : pr1.second){
|
||||
for(auto &pr3 : pr2.second){
|
||||
fun(pr0.first,pr1.first,pr2.first,pr3.first,pr3.second.lock());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数
|
||||
virtual int readerCount() = 0;
|
||||
// 观看者个数,包括(hls/rtsp/rtmp)
|
||||
virtual int totalReaderCount();
|
||||
|
||||
/**
|
||||
* 获取track
|
||||
* @return
|
||||
*/
|
||||
virtual vector<Track::Ptr> getTracks(bool trackReady) const{
|
||||
return vector<Track::Ptr>(0);
|
||||
}
|
||||
// 获取流当前时间戳
|
||||
virtual uint32_t getTimeStamp(TrackType trackType) { return 0; };
|
||||
// 设置时间戳
|
||||
virtual void setTimeStamp(uint32_t uiStamp) {};
|
||||
|
||||
// 拖动进度条
|
||||
bool seekTo(uint32_t ui32Stamp);
|
||||
// 关闭该流
|
||||
bool close(bool force);
|
||||
// 该流无人观看
|
||||
void onNoneReader();
|
||||
|
||||
// 同步查找流
|
||||
static Ptr find(const string &schema, const string &vhost, const string &app, const string &id, bool bMake = true) ;
|
||||
// 异步查找流
|
||||
static void findAsync(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, const function<void(const Ptr &src)> &cb);
|
||||
// 遍历所有流
|
||||
static void for_each_media(const function<void(const Ptr &src)> &cb);
|
||||
protected:
|
||||
void regist() ;
|
||||
bool unregist() ;
|
||||
private:
|
||||
template <typename FUN>
|
||||
static bool searchMedia(const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &id,
|
||||
FUN &&fun){
|
||||
auto it0 = g_mapMediaSrc.find(schema);
|
||||
if (it0 == g_mapMediaSrc.end()) {
|
||||
//未找到协议
|
||||
return false;
|
||||
}
|
||||
auto it1 = it0->second.find(vhost);
|
||||
if(it1 == it0->second.end()){
|
||||
//未找到vhost
|
||||
return false;
|
||||
}
|
||||
auto it2 = it1->second.find(app);
|
||||
if(it2 == it1->second.end()){
|
||||
//未找到app
|
||||
return false;
|
||||
}
|
||||
auto it3 = it2->second.find(id);
|
||||
if(it3 == it2->second.end()){
|
||||
//未找到streamId
|
||||
return false;
|
||||
}
|
||||
return fun(it0,it1,it2,it3);
|
||||
}
|
||||
template <typename IT0,typename IT1,typename IT2>
|
||||
static void eraseIfEmpty(IT0 it0,IT1 it1,IT2 it2){
|
||||
if(it2->second.empty()){
|
||||
it1->second.erase(it2);
|
||||
if(it1->second.empty()){
|
||||
it0->second.erase(it1);
|
||||
if(it0->second.empty()){
|
||||
g_mapMediaSrc.erase(it0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void unregisted();
|
||||
protected:
|
||||
string _strSchema;
|
||||
string _strVhost;
|
||||
string _strApp;
|
||||
string _strId;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
private:
|
||||
string _strSchema;//协议类型
|
||||
string _strVhost; //vhost
|
||||
string _strApp; //媒体app
|
||||
string _strId; //媒体id
|
||||
static SchemaVhostAppStreamMap g_mapMediaSrc; //静态的媒体源表
|
||||
static recursive_mutex g_mtxMediaSrc; //访问静态的媒体源表的互斥锁
|
||||
weak_ptr<TrackSource> _track_source;
|
||||
static SchemaVhostAppStreamMap g_mapMediaSrc;
|
||||
static recursive_mutex g_mtxMediaSrc;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -29,71 +29,57 @@
|
||||
|
||||
#include "Rtsp/RtspMediaSourceMuxer.h"
|
||||
#include "Rtmp/RtmpMediaSourceMuxer.h"
|
||||
#include "MediaFile/MediaRecorder.h"
|
||||
#include "Record/Recorder.h"
|
||||
#include "Record/HlsMediaSource.h"
|
||||
#include "Record/HlsRecorder.h"
|
||||
|
||||
class MultiMediaSourceMuxer : public FrameWriterInterface{
|
||||
class MultiMediaSourceMuxer : public MediaSink , public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
||||
public:
|
||||
typedef std::shared_ptr<MultiMediaSourceMuxer> Ptr;
|
||||
class Listener{
|
||||
public:
|
||||
Listener() = default;
|
||||
virtual ~Listener() = default;
|
||||
virtual void onAllTrackReady() = 0;
|
||||
};
|
||||
|
||||
MultiMediaSourceMuxer(const string &vhost,
|
||||
const string &strApp,
|
||||
const string &strId,
|
||||
float dur_sec = 0.0,
|
||||
bool bEanbleRtsp = true,
|
||||
bool bEanbleRtmp = true,
|
||||
bool bEanbleHls = true,
|
||||
bool bEnableMp4 = false
|
||||
){
|
||||
if (bEanbleRtmp) {
|
||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, strApp, strId, std::make_shared<TitleMeta>(dur_sec));
|
||||
typedef std::shared_ptr<MultiMediaSourceMuxer> Ptr;
|
||||
MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec = 0.0,
|
||||
bool enable_rtsp = true, bool enable_rtmp = true, bool enable_hls = true, bool enable_mp4 = false){
|
||||
if (enable_rtmp) {
|
||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
||||
}
|
||||
if (bEanbleRtsp) {
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, strApp, strId, std::make_shared<TitleSdp>(dur_sec));
|
||||
if (enable_rtsp) {
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
||||
}
|
||||
_record = std::make_shared<MediaRecorder>(vhost,strApp,strId,bEanbleHls,bEnableMp4);
|
||||
|
||||
if(enable_hls){
|
||||
Recorder::startRecord(Recorder::type_hls,vhost, app, stream, "", true, false);
|
||||
}
|
||||
|
||||
if(enable_mp4){
|
||||
Recorder::startRecord(Recorder::type_mp4,vhost, app, stream, "", true, false);
|
||||
}
|
||||
|
||||
_get_hls_media_source = [vhost,app,stream](){
|
||||
auto recorder = dynamic_pointer_cast<HlsRecorder>(Recorder::getRecorder(Recorder::type_hls,vhost,app,stream));
|
||||
if(recorder){
|
||||
return recorder->getMediaSource();
|
||||
}
|
||||
return MediaSource::Ptr();
|
||||
};
|
||||
}
|
||||
virtual ~MultiMediaSourceMuxer(){}
|
||||
|
||||
|
||||
/**
|
||||
* 添加音视频媒体
|
||||
* @param track 媒体描述
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) {
|
||||
if(_rtmp){
|
||||
_rtmp->addTrack(track);
|
||||
}
|
||||
if(_rtsp){
|
||||
_rtsp->addTrack(track);
|
||||
}
|
||||
_record->addTrack(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置音视频媒体
|
||||
*/
|
||||
void resetTracks() {
|
||||
void resetTracks() override{
|
||||
if(_rtmp){
|
||||
_rtmp->resetTracks();
|
||||
}
|
||||
if(_rtsp){
|
||||
_rtsp->resetTracks();
|
||||
}
|
||||
_record->resetTracks();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入帧数据然后打包rtmp
|
||||
* @param frame 帧数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override {
|
||||
if(_rtmp) {
|
||||
_rtmp->inputFrame(frame);
|
||||
}
|
||||
if(_rtsp) {
|
||||
_rtsp->inputFrame(frame);
|
||||
}
|
||||
_record->inputFrame(frame);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,28 +90,93 @@ public:
|
||||
if(_rtmp) {
|
||||
_rtmp->setListener(listener);
|
||||
}
|
||||
|
||||
if(_rtsp) {
|
||||
_rtsp->setListener(listener);
|
||||
}
|
||||
|
||||
auto hls_src = _get_hls_media_source();
|
||||
if(hls_src){
|
||||
hls_src->setListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回总的消费者个数
|
||||
* @return
|
||||
*/
|
||||
int readerCount() const{
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0);
|
||||
int totalReaderCount() const{
|
||||
auto hls_src = _get_hls_media_source();
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
||||
}
|
||||
|
||||
void setTimeStamp(uint32_t stamp){
|
||||
if(_rtmp){
|
||||
_rtmp->setTimeStamp(stamp);
|
||||
}
|
||||
|
||||
if(_rtsp){
|
||||
_rtsp->setTimeStamp(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
void setTrackListener(Listener *listener){
|
||||
_listener = listener;
|
||||
}
|
||||
protected:
|
||||
/**
|
||||
* 添加音视频媒体
|
||||
* @param track 媒体描述
|
||||
*/
|
||||
void onTrackReady(const Track::Ptr & track) override {
|
||||
if(_rtmp){
|
||||
_rtmp->addTrack(track);
|
||||
}
|
||||
if(_rtsp){
|
||||
_rtsp->addTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入帧数据然后打包rtmp
|
||||
* @param frame 帧数据
|
||||
*/
|
||||
void onTrackFrame(const Frame::Ptr &frame) override {
|
||||
if(_rtmp) {
|
||||
_rtmp->inputFrame(frame);
|
||||
}
|
||||
if(_rtsp) {
|
||||
_rtsp->inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有Track都准备就绪,触发媒体注册事件
|
||||
*/
|
||||
void onAllTrackReady() override{
|
||||
if(_rtmp) {
|
||||
_rtmp->setTrackSource(shared_from_this());
|
||||
_rtmp->onAllTrackReady();
|
||||
}
|
||||
if(_rtsp) {
|
||||
_rtsp->setTrackSource(shared_from_this());
|
||||
_rtsp->onAllTrackReady();
|
||||
}
|
||||
|
||||
auto hls_src = _get_hls_media_source();
|
||||
if(hls_src){
|
||||
hls_src->setTrackSource(shared_from_this());
|
||||
}
|
||||
|
||||
if(_listener){
|
||||
_listener->onAllTrackReady();
|
||||
}
|
||||
}
|
||||
private:
|
||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||
MediaRecorder::Ptr _record;
|
||||
Listener *_listener = nullptr;
|
||||
function<MediaSource::Ptr ()> _get_hls_media_source;
|
||||
};
|
||||
|
||||
|
||||
|
@ -28,27 +28,26 @@ class StrCaseMap : public multimap<string, string, StrCaseCompare>{
|
||||
StrCaseMap() = default;
|
||||
~StrCaseMap() = default;
|
||||
|
||||
template <class K>
|
||||
string &operator[](K &&k){
|
||||
auto it = find(std::forward<K>(k));
|
||||
string &operator[](const string &k){
|
||||
auto it = find(k);
|
||||
if(it == end()){
|
||||
it = Super::emplace(std::forward<K>(k),"");
|
||||
it = Super::emplace(k,"");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <class K,class V>
|
||||
void emplace(K &&k , V &&v) {
|
||||
auto it = find(std::forward<K>(k));
|
||||
template <typename V>
|
||||
void emplace(const string &k, V &&v) {
|
||||
auto it = find(k);
|
||||
if(it != end()){
|
||||
return;
|
||||
}
|
||||
Super::emplace(std::forward<K>(k),std::forward<V>(v));
|
||||
Super::emplace(k,std::forward<V>(v));
|
||||
}
|
||||
|
||||
template <class K,class V>
|
||||
void emplace_force(K &&k , V &&v) {
|
||||
Super::emplace(std::forward<K>(k),std::forward<V>(v));
|
||||
template <typename V>
|
||||
void emplace_force(const string k , V &&v) {
|
||||
Super::emplace(k,std::forward<V>(v));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -26,12 +26,18 @@
|
||||
|
||||
#include "Stamp.h"
|
||||
|
||||
#define MAX_DELTA_STAMP 1000
|
||||
#define MAX_CTS 500
|
||||
#define ABS(x) ((x) > 0 ? (x) : (-x))
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||
if(!_last_stamp){
|
||||
//第一次计算时间戳增量,时间戳增量为0
|
||||
if(stamp){
|
||||
_last_stamp = stamp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -39,7 +45,8 @@ int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||
if(ret >= 0){
|
||||
//时间戳增量为正,返回之
|
||||
_last_stamp = stamp;
|
||||
return ret;
|
||||
//在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP
|
||||
return ret < MAX_DELTA_STAMP ? ret : (_playback ? ret : 0);
|
||||
}
|
||||
|
||||
//时间戳增量为负,说明时间戳回环了或回退了
|
||||
@ -60,13 +67,20 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,
|
||||
//pts和dts的差值
|
||||
int pts_dts_diff = pts - dts;
|
||||
|
||||
//相对时间戳
|
||||
_relativeStamp += deltaStamp(modifyStamp ? _ticker.elapsedTime() : dts);
|
||||
if(_last_dts != dts){
|
||||
//时间戳发生变更
|
||||
if(modifyStamp){
|
||||
_relativeStamp = _ticker.elapsedTime();
|
||||
}else{
|
||||
_relativeStamp += deltaStamp(dts);
|
||||
}
|
||||
_last_dts = dts;
|
||||
}
|
||||
dts_out = _relativeStamp;
|
||||
|
||||
//////////////以下是播放时间戳的计算//////////////////
|
||||
if(pts_dts_diff > 200 || pts_dts_diff < -200){
|
||||
//如果差值大于200毫秒,则认为由于回环导致时间戳错乱了
|
||||
if(ABS(pts_dts_diff) > MAX_CTS){
|
||||
//如果差值太大,则认为由于回环导致时间戳错乱了
|
||||
pts_dts_diff = 0;
|
||||
}
|
||||
|
||||
@ -86,4 +100,58 @@ int64_t Stamp::getRelativeStamp() const {
|
||||
}
|
||||
|
||||
|
||||
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
||||
bool ret = false;
|
||||
if(pts == _last_pts){
|
||||
//pts未变,返回上次结果
|
||||
if(_last_dts){
|
||||
dts = _last_dts;
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = getDts_l(pts,dts);
|
||||
if(ret){
|
||||
//保存本次结果
|
||||
_last_dts = dts;
|
||||
}
|
||||
//记录上次pts
|
||||
_last_pts = pts;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool DtsGenerator::getDts_l(uint32_t pts, uint32_t &dts){
|
||||
if(_sorter_max_size == 1){
|
||||
//没有B帧
|
||||
dts = pts;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!_sorter_max_size){
|
||||
if(pts > _last_max_pts){
|
||||
if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){
|
||||
_sorter_max_size = _frames_since_last_max_pts;
|
||||
_dts_pts_offset = (pts - _last_max_pts) / 2;
|
||||
}
|
||||
_frames_since_last_max_pts = 0;
|
||||
_last_max_pts = pts;
|
||||
}
|
||||
++_frames_since_last_max_pts;
|
||||
}
|
||||
|
||||
_pts_sorter.emplace(pts);
|
||||
if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){
|
||||
auto it = _pts_sorter.begin();
|
||||
dts = *it + _dts_pts_offset;
|
||||
if(dts > pts){
|
||||
//dts不能大于pts(基本不可能到达这个逻辑)
|
||||
dts = pts;
|
||||
}
|
||||
_pts_sorter.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -27,8 +27,9 @@
|
||||
#ifndef ZLMEDIAKIT_STAMP_H
|
||||
#define ZLMEDIAKIT_STAMP_H
|
||||
|
||||
#include "Util/TimeTicker.h"
|
||||
#include <set>
|
||||
#include <cstdint>
|
||||
#include "Util/TimeTicker.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
@ -84,9 +85,31 @@ public:
|
||||
int64_t getRelativeStamp() const ;
|
||||
private:
|
||||
int64_t _relativeStamp = 0;
|
||||
int64_t _last_dts = -1;
|
||||
SmoothTicker _ticker;
|
||||
};
|
||||
|
||||
|
||||
class DtsGenerator{
|
||||
public:
|
||||
DtsGenerator() = default;
|
||||
~DtsGenerator() = default;
|
||||
bool getDts(uint32_t pts, uint32_t &dts);
|
||||
private:
|
||||
bool getDts_l(uint32_t pts, uint32_t &dts);
|
||||
private:
|
||||
uint32_t _dts_pts_offset = 0;
|
||||
uint32_t _last_dts = 0;
|
||||
uint32_t _last_pts = 0;
|
||||
uint32_t _last_max_pts = 0;
|
||||
int _frames_since_last_max_pts = 0;
|
||||
int _sorter_max_size = 0;
|
||||
int _count_sorter_max_size = 0;
|
||||
set<uint32_t> _pts_sorter;
|
||||
|
||||
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_STAMP_H
|
@ -55,6 +55,7 @@ bool loadIniConfig(const char *ini_path){
|
||||
////////////广播名称///////////
|
||||
namespace Broadcast {
|
||||
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
||||
const string kBroadcastMediaResetTracks = "kBroadcastMediaResetTracks";
|
||||
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
||||
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
||||
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
||||
@ -67,6 +68,7 @@ const string kBroadcastReloadConfig = "kBroadcastReloadConfig";
|
||||
const string kBroadcastShellLogin = "kBroadcastShellLogin";
|
||||
const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream";
|
||||
const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader";
|
||||
const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess";
|
||||
} //namespace Broadcast
|
||||
|
||||
//通用配置项目
|
||||
@ -79,15 +81,21 @@ const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
||||
const string kUltraLowDelay = GENERAL_FIELD"ultraLowDelay";
|
||||
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
||||
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
||||
const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
|
||||
const string kPublishToHls = GENERAL_FIELD"publishToHls";
|
||||
const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kFlowThreshold] = 1024;
|
||||
mINI::Instance()[kStreamNoneReaderDelayMS] = 5 * 1000;
|
||||
mINI::Instance()[kMaxStreamWaitTimeMS] = 5 * 1000;
|
||||
mINI::Instance()[kEnableVhost] = 1;
|
||||
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
|
||||
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
|
||||
mINI::Instance()[kEnableVhost] = 0;
|
||||
mINI::Instance()[kUltraLowDelay] = 1;
|
||||
mINI::Instance()[kAddMuteAudio] = 1;
|
||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||
mINI::Instance()[kPublishToRtxp] = 1;
|
||||
mINI::Instance()[kPublishToHls] = 1;
|
||||
mINI::Instance()[kPublishToMP4] = 0;
|
||||
},nullptr);
|
||||
|
||||
}//namespace General
|
||||
@ -95,57 +103,40 @@ onceToken token([](){
|
||||
////////////HTTP配置///////////
|
||||
namespace Http {
|
||||
#define HTTP_FIELD "http."
|
||||
|
||||
//http 文件发送缓存大小
|
||||
#define HTTP_SEND_BUF_SIZE (64 * 1024)
|
||||
const string kSendBufSize = HTTP_FIELD"sendBufSize";
|
||||
|
||||
//http 最大请求字节数
|
||||
#define HTTP_MAX_REQ_SIZE (4*1024)
|
||||
const string kMaxReqSize = HTTP_FIELD"maxReqSize";
|
||||
|
||||
//http keep-alive秒数
|
||||
#define HTTP_KEEP_ALIVE_SECOND 10
|
||||
const string kKeepAliveSecond = HTTP_FIELD"keepAliveSecond";
|
||||
|
||||
//http keep-alive最大请求数
|
||||
#define HTTP_MAX_REQ_CNT 100
|
||||
const string kMaxReqCount = HTTP_FIELD"maxReqCount";
|
||||
|
||||
|
||||
//http 字符编码
|
||||
#if defined(_WIN32)
|
||||
#define HTTP_CHAR_SET "gb2312"
|
||||
#else
|
||||
#define HTTP_CHAR_SET "utf-8"
|
||||
#endif
|
||||
const string kCharSet = HTTP_FIELD"charSet";
|
||||
|
||||
//http 服务器根目录
|
||||
#define HTTP_ROOT_PATH "./httpRoot"
|
||||
const string kRootPath = HTTP_FIELD"rootPath";
|
||||
|
||||
//http 404错误提示内容
|
||||
#define HTTP_NOT_FOUND "<html>"\
|
||||
"<head><title>404 Not Found</title></head>"\
|
||||
"<body bgcolor=\"white\">"\
|
||||
"<center><h1>您访问的资源不存在!</h1></center>"\
|
||||
"<hr><center>"\
|
||||
SERVER_NAME\
|
||||
"</center>"\
|
||||
"</body>"\
|
||||
"</html>"
|
||||
const string kNotFound = HTTP_FIELD"notFound";
|
||||
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kSendBufSize] = HTTP_SEND_BUF_SIZE;
|
||||
mINI::Instance()[kMaxReqSize] = HTTP_MAX_REQ_SIZE;
|
||||
mINI::Instance()[kKeepAliveSecond] = HTTP_KEEP_ALIVE_SECOND;
|
||||
mINI::Instance()[kMaxReqCount] = HTTP_MAX_REQ_CNT;
|
||||
mINI::Instance()[kCharSet] = HTTP_CHAR_SET;
|
||||
mINI::Instance()[kRootPath] = HTTP_ROOT_PATH;
|
||||
mINI::Instance()[kNotFound] = HTTP_NOT_FOUND;
|
||||
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
||||
mINI::Instance()[kMaxReqSize] = 4*1024;
|
||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
#if defined(_WIN32)
|
||||
mINI::Instance()[kCharSet] = "gb2312";
|
||||
#else
|
||||
mINI::Instance()[kCharSet] ="utf-8";
|
||||
#endif
|
||||
|
||||
mINI::Instance()[kRootPath] = "./www";
|
||||
mINI::Instance()[kNotFound] =
|
||||
"<html>"
|
||||
"<head><title>404 Not Found</title></head>"
|
||||
"<body bgcolor=\"white\">"
|
||||
"<center><h1>您访问的资源不存在!</h1></center>"
|
||||
"<hr><center>"
|
||||
SERVER_NAME
|
||||
"</center>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
},nullptr);
|
||||
|
||||
}//namespace Http
|
||||
@ -153,12 +144,10 @@ onceToken token([](){
|
||||
////////////SHELL配置///////////
|
||||
namespace Shell {
|
||||
#define SHELL_FIELD "shell."
|
||||
|
||||
#define SHELL_MAX_REQ_SIZE 1024
|
||||
const string kMaxReqSize = SHELL_FIELD"maxReqSize";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kMaxReqSize] = SHELL_MAX_REQ_SIZE;
|
||||
mINI::Instance()[kMaxReqSize] = 1024;
|
||||
},nullptr);
|
||||
} //namespace Shell
|
||||
|
||||
@ -169,7 +158,6 @@ const string kAuthBasic = RTSP_FIELD"authBasic";
|
||||
const string kHandshakeSecond = RTSP_FIELD"handshakeSecond";
|
||||
const string kKeepAliveSecond = RTSP_FIELD"keepAliveSecond";
|
||||
const string kDirectProxy = RTSP_FIELD"directProxy";
|
||||
const string kModifyStamp = RTSP_FIELD"modifyStamp";
|
||||
|
||||
onceToken token([](){
|
||||
//默认Md5方式认证
|
||||
@ -177,9 +165,7 @@ onceToken token([](){
|
||||
mINI::Instance()[kHandshakeSecond] = 15;
|
||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
mINI::Instance()[kDirectProxy] = 1;
|
||||
mINI::Instance()[kModifyStamp] = false;
|
||||
},nullptr);
|
||||
|
||||
} //namespace Rtsp
|
||||
|
||||
////////////RTMP服务器配置///////////
|
||||
@ -190,44 +176,32 @@ const string kHandshakeSecond = RTMP_FIELD"handshakeSecond";
|
||||
const string kKeepAliveSecond = RTMP_FIELD"keepAliveSecond";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kModifyStamp] = true;
|
||||
mINI::Instance()[kModifyStamp] = false;
|
||||
mINI::Instance()[kHandshakeSecond] = 15;
|
||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
},nullptr);
|
||||
|
||||
} //namespace RTMP
|
||||
|
||||
|
||||
////////////RTP配置///////////
|
||||
namespace Rtp {
|
||||
#define RTP_FIELD "rtp."
|
||||
|
||||
//RTP打包最大MTU,公网情况下更小
|
||||
#define RTP_VIDOE_MTU_SIZE 1400
|
||||
const string kVideoMtuSize = RTP_FIELD"videoMtuSize";
|
||||
|
||||
#define RTP_Audio_MTU_SIZE 600
|
||||
const string kAudioMtuSize = RTP_FIELD"audioMtuSize";
|
||||
|
||||
//RTP排序缓存最大个数
|
||||
#define RTP_MAX_RTP_COUNT 50
|
||||
const string kMaxRtpCount = RTP_FIELD"maxRtpCount";
|
||||
|
||||
//如果RTP序列正确次数累计达到该数字就启动清空排序缓存
|
||||
#define RTP_CLEAR_COUNT 10
|
||||
const string kClearCount = RTP_FIELD"clearCount";
|
||||
|
||||
//最大RTP时间为13个小时,每13小时回环一次
|
||||
#define RTP_CYCLE_MS (13*60*60*1000)
|
||||
const string kCycleMS = RTP_FIELD"cycleMS";
|
||||
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kVideoMtuSize] = RTP_VIDOE_MTU_SIZE;
|
||||
mINI::Instance()[kAudioMtuSize] = RTP_Audio_MTU_SIZE;
|
||||
mINI::Instance()[kMaxRtpCount] = RTP_MAX_RTP_COUNT;
|
||||
mINI::Instance()[kClearCount] = RTP_CLEAR_COUNT;
|
||||
mINI::Instance()[kCycleMS] = RTP_CYCLE_MS;
|
||||
mINI::Instance()[kVideoMtuSize] = 1400;
|
||||
mINI::Instance()[kAudioMtuSize] = 600;
|
||||
mINI::Instance()[kMaxRtpCount] = 50;
|
||||
mINI::Instance()[kClearCount] = 10;
|
||||
mINI::Instance()[kCycleMS] = 13*60*60*1000;
|
||||
},nullptr);
|
||||
} //namespace Rtsp
|
||||
|
||||
@ -239,88 +213,86 @@ const string kAddrMin = MULTI_FIELD"addrMin";
|
||||
//组播分配截止地址
|
||||
const string kAddrMax = MULTI_FIELD"addrMax";
|
||||
//组播TTL
|
||||
#define MULTI_UDP_TTL 64
|
||||
const string kUdpTTL = MULTI_FIELD"udpTTL";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kAddrMin] = "239.0.0.0";
|
||||
mINI::Instance()[kAddrMax] = "239.255.255.255";
|
||||
mINI::Instance()[kUdpTTL] = MULTI_UDP_TTL;
|
||||
mINI::Instance()[kUdpTTL] = 64;
|
||||
},nullptr);
|
||||
|
||||
} //namespace MultiCast
|
||||
|
||||
////////////录像配置///////////
|
||||
namespace Record {
|
||||
#define RECORD_FIELD "record."
|
||||
|
||||
//查看录像的应用名称
|
||||
#define RECORD_APP_NAME "record"
|
||||
const string kAppName = RECORD_FIELD"appName";
|
||||
|
||||
//每次流化MP4文件的时长,单位毫秒
|
||||
#define RECORD_SAMPLE_MS 500
|
||||
const string kSampleMS = RECORD_FIELD"sampleMS";
|
||||
|
||||
//MP4文件录制大小,默认一个小时
|
||||
#define RECORD_FILE_SECOND (60*60)
|
||||
const string kFileSecond = RECORD_FIELD"fileSecond";
|
||||
|
||||
//录制文件路径
|
||||
#define RECORD_FILE_PATH HTTP_ROOT_PATH
|
||||
const string kFilePath = RECORD_FIELD"filePath";
|
||||
|
||||
//mp4文件写缓存大小
|
||||
const string kFileBufSize = RECORD_FIELD"fileBufSize";
|
||||
|
||||
//mp4录制完成后是否进行二次关键帧索引写入头部
|
||||
const string kFastStart = RECORD_FIELD"fastStart";
|
||||
|
||||
//mp4文件是否重头循环读取
|
||||
const string kFileRepeat = RECORD_FIELD"fileRepeat";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kAppName] = RECORD_APP_NAME;
|
||||
mINI::Instance()[kSampleMS] = RECORD_SAMPLE_MS;
|
||||
mINI::Instance()[kFileSecond] = RECORD_FILE_SECOND;
|
||||
mINI::Instance()[kFilePath] = RECORD_FILE_PATH;
|
||||
mINI::Instance()[kAppName] = "record";
|
||||
mINI::Instance()[kSampleMS] = 500;
|
||||
mINI::Instance()[kFileSecond] = 60*60;
|
||||
mINI::Instance()[kFilePath] = "./www";
|
||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||
mINI::Instance()[kFastStart] = false;
|
||||
mINI::Instance()[kFileRepeat] = false;
|
||||
},nullptr);
|
||||
|
||||
} //namespace Record
|
||||
|
||||
////////////HLS相关配置///////////
|
||||
namespace Hls {
|
||||
#define HLS_FIELD "hls."
|
||||
|
||||
//HLS切片时长,单位秒
|
||||
#define HLS_SEGMENT_DURATION 3
|
||||
const string kSegmentDuration = HLS_FIELD"segDur";
|
||||
|
||||
//HLS切片个数
|
||||
#define HLS_SEGMENT_NUM 3
|
||||
const string kSegmentNum = HLS_FIELD"segNum";
|
||||
|
||||
//HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||
const string kSegmentRetain = HLS_FIELD"segRetain";
|
||||
//HLS文件写缓存大小
|
||||
#define HLS_FILE_BUF_SIZE (64 * 1024)
|
||||
const string kFileBufSize = HLS_FIELD"fileBufSize";
|
||||
|
||||
//录制文件路径
|
||||
#define HLS_FILE_PATH (HTTP_ROOT_PATH)
|
||||
const string kFilePath = HLS_FIELD"filePath";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kSegmentDuration] = HLS_SEGMENT_DURATION;
|
||||
mINI::Instance()[kSegmentNum] = HLS_SEGMENT_NUM;
|
||||
mINI::Instance()[kFileBufSize] = HLS_FILE_BUF_SIZE;
|
||||
mINI::Instance()[kFilePath] = HLS_FILE_PATH;
|
||||
mINI::Instance()[kSegmentDuration] = 2;
|
||||
mINI::Instance()[kSegmentNum] = 3;
|
||||
mINI::Instance()[kSegmentRetain] = 5;
|
||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||
mINI::Instance()[kFilePath] = "./www";
|
||||
},nullptr);
|
||||
|
||||
} //namespace Hls
|
||||
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
namespace RtpProxy {
|
||||
#define RTP_PROXY_FIELD "rtp_proxy."
|
||||
//rtp调试数据保存目录
|
||||
const string kDumpDir = RTP_PROXY_FIELD"dumpDir";
|
||||
//是否限制udp数据来源ip和端口
|
||||
const string kCheckSource = RTP_PROXY_FIELD"checkSource";
|
||||
//rtp接收超时时间
|
||||
const string kTimeoutSec = RTP_PROXY_FIELD"timeoutSec";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kDumpDir] = "";
|
||||
mINI::Instance()[kCheckSource] = 1;
|
||||
mINI::Instance()[kTimeoutSec] = 15;
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
|
||||
namespace Client {
|
||||
const string kNetAdapter = "net_adapter";
|
||||
const string kRtpType = "rtp_type";
|
||||
@ -331,7 +303,6 @@ const string kTimeoutMS = "protocol_timeout_ms";
|
||||
const string kMediaTimeoutMS = "media_timeout_ms";
|
||||
const string kBeatIntervalMS = "beat_interval_ms";
|
||||
const string kMaxAnalysisMS = "max_analysis_ms";
|
||||
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
||||
|
@ -62,6 +62,7 @@ bool loadIniConfig(const char *ini_path = nullptr);
|
||||
#define HTTP_SCHEMA "http"
|
||||
#define RTSP_SCHEMA "rtsp"
|
||||
#define RTMP_SCHEMA "rtmp"
|
||||
#define HLS_SCHEMA "hls"
|
||||
#define DEFAULT_VHOST "__defaultVhost__"
|
||||
|
||||
////////////广播名称///////////
|
||||
@ -69,19 +70,28 @@ namespace Broadcast {
|
||||
|
||||
//注册或反注册MediaSource事件广播
|
||||
extern const string kBroadcastMediaChanged;
|
||||
#define BroadcastMediaChangedArgs const bool &bRegist, const string &schema,const string &vhost,const string &app,const string &stream,MediaSource &sender
|
||||
#define BroadcastMediaChangedArgs const bool &bRegist, MediaSource &sender
|
||||
|
||||
//MediaSource重置Track事件
|
||||
extern const string kBroadcastMediaResetTracks;
|
||||
#define BroadcastMediaResetTracksArgs MediaSource &sender
|
||||
|
||||
//录制mp4文件成功后广播
|
||||
extern const string kBroadcastRecordMP4;
|
||||
#define BroadcastRecordMP4Args const Mp4Info &info
|
||||
#define BroadcastRecordMP4Args const MP4Info &info
|
||||
|
||||
//收到http api请求广播
|
||||
extern const string kBroadcastHttpRequest;
|
||||
#define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,TcpSession &sender
|
||||
|
||||
//收到http 访问文件或目录的广播,通过该事件控制访问http目录的权限
|
||||
//在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
|
||||
extern const string kBroadcastHttpAccess;
|
||||
#define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender
|
||||
#define BroadcastHttpAccessArgs const Parser &parser,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender
|
||||
|
||||
//在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
|
||||
//在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的
|
||||
extern const string kBroadcastHttpBeforeAccess;
|
||||
#define BroadcastHttpBeforeAccessArgs const Parser &parser,string &path,TcpSession &sender
|
||||
|
||||
//该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证
|
||||
extern const string kBroadcastOnGetRtspRealm;
|
||||
@ -117,7 +127,7 @@ extern const string kBroadcastShellLogin;
|
||||
|
||||
//停止rtsp/rtmp/http-flv会话后流量汇报事件广播
|
||||
extern const string kBroadcastFlowReport;
|
||||
#define BroadcastFlowReportArgs const MediaInfo &args,const uint64_t &totalBytes,const uint64_t &totalDuration,const bool &isPlayer,TcpSession &sender
|
||||
#define BroadcastFlowReportArgs const MediaInfo &args,const uint64_t &totalBytes,const uint64_t &totalDuration,const bool &isPlayer, const string &sessionIdentifier, const string &peerIP,const uint16_t &peerPort
|
||||
|
||||
//未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了
|
||||
extern const string kBroadcastNotFoundStream;
|
||||
@ -155,11 +165,6 @@ extern const string kBroadcastReloadConfig;
|
||||
static type arg = mINI::Instance()[key]; \
|
||||
LISTEN_RELOAD_KEY(arg,key);
|
||||
|
||||
|
||||
//兼容老代码
|
||||
#define GET_CONFIG_AND_REGISTER GET_CONFIG
|
||||
#define BroadcastRtmpPublishArgs BroadcastMediaPublishArgs
|
||||
#define kBroadcastRtmpPublish kBroadcastMediaPublish
|
||||
} //namespace Broadcast
|
||||
|
||||
////////////通用配置///////////
|
||||
@ -182,6 +187,12 @@ extern const string kAddMuteAudio;
|
||||
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
extern const string kResetWhenRePlay;
|
||||
//是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToRtxp ;
|
||||
//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToHls ;
|
||||
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToMP4 ;
|
||||
}//namespace General
|
||||
|
||||
|
||||
@ -193,8 +204,6 @@ extern const string kSendBufSize;
|
||||
extern const string kMaxReqSize;
|
||||
//http keep-alive秒数
|
||||
extern const string kKeepAliveSecond;
|
||||
//http keep-alive最大请求数
|
||||
extern const string kMaxReqCount;
|
||||
//http 字符编码
|
||||
extern const string kCharSet;
|
||||
//http 服务器根目录
|
||||
@ -223,8 +232,6 @@ extern const string kKeepAliveSecond;
|
||||
//假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
|
||||
//默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
|
||||
extern const string kDirectProxy;
|
||||
//rtsp推流是否修改时间戳
|
||||
extern const string kModifyStamp;
|
||||
} //namespace Rtsp
|
||||
|
||||
////////////RTMP服务器配置///////////
|
||||
@ -283,14 +290,25 @@ extern const string kFileRepeat;
|
||||
namespace Hls {
|
||||
//HLS切片时长,单位秒
|
||||
extern const string kSegmentDuration;
|
||||
//HLS切片个数,如果设置为0,则不删除切片,而是保存为点播
|
||||
//m3u8文件中HLS切片个数,如果设置为0,则不删除切片,而是保存为点播
|
||||
extern const string kSegmentNum;
|
||||
//HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||
extern const string kSegmentRetain;
|
||||
//HLS文件写缓存大小
|
||||
extern const string kFileBufSize;
|
||||
//录制文件路径
|
||||
extern const string kFilePath;
|
||||
} //namespace Hls
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
namespace RtpProxy {
|
||||
//rtp调试数据保存目录,置空则不生成
|
||||
extern const string kDumpDir;
|
||||
//是否限制udp数据来源ip和端口
|
||||
extern const string kCheckSource;
|
||||
//rtp接收超时时间
|
||||
extern const string kTimeoutSec;
|
||||
} //namespace RtpProxy
|
||||
|
||||
/**
|
||||
* rtsp/rtmp播放器、推流器相关设置名,
|
||||
|
@ -104,7 +104,6 @@ public:
|
||||
//表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
|
||||
unsigned int no_raw_data_blocks_in_frame; //2 uimsfb
|
||||
unsigned char buffer[2 * 1024 + 7];
|
||||
uint16_t sequence;
|
||||
uint32_t timeStamp;
|
||||
uint32_t iPrefixSize = 7;
|
||||
} ;
|
||||
@ -257,6 +256,9 @@ private:
|
||||
* 解析2个字节的aac配置
|
||||
*/
|
||||
void onReady(){
|
||||
if(_cfg.size() < 2){
|
||||
return;
|
||||
}
|
||||
AACFrame aacFrame;
|
||||
makeAdtsHeader(_cfg,aacFrame);
|
||||
getAACInfo(aacFrame,_sampleRate,_channel);
|
||||
|
@ -40,8 +40,6 @@ AACFrame::Ptr AACRtmpDecoder::obtainFrame() {
|
||||
}
|
||||
|
||||
bool AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool key_pos) {
|
||||
RtmpCodec::inputRtmp(pkt, false);
|
||||
|
||||
if (pkt->isCfgFrame()) {
|
||||
_aac_cfg = pkt->getAacCfg();
|
||||
return false;
|
||||
@ -78,19 +76,24 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) {
|
||||
_track = dynamic_pointer_cast<AACTrack>(track);
|
||||
}
|
||||
|
||||
void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
RtmpCodec::inputFrame(frame);
|
||||
void AACRtmpEncoder::makeConfigPacket() {
|
||||
if (_track && _track->ready()) {
|
||||
//从track中和获取aac配置信息
|
||||
_aac_cfg = _track->getAacCfg();
|
||||
}
|
||||
|
||||
if (!_aac_cfg.empty()) {
|
||||
makeAudioConfigPkt();
|
||||
}
|
||||
}
|
||||
|
||||
void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
if (_aac_cfg.empty()) {
|
||||
if (frame->prefixSize() >= 7) {
|
||||
//包含adts头,从adts头获取aac配置信息
|
||||
_aac_cfg = makeAdtsConfig(reinterpret_cast<const uint8_t *>(frame->data()));
|
||||
makeAudioConfigPkt();
|
||||
} else if(_track && _track->ready()){
|
||||
//从track中和获取aac配置信息
|
||||
_aac_cfg = _track->getAacCfg();
|
||||
makeAudioConfigPkt();
|
||||
}
|
||||
makeConfigPacket();
|
||||
}
|
||||
|
||||
if(!_aac_cfg.empty()){
|
||||
@ -106,7 +109,7 @@ void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = frame->stamp();
|
||||
rtmpPkt->timeStamp = frame->dts();
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
}
|
||||
|
@ -88,6 +88,10 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 生成config包
|
||||
*/
|
||||
void makeConfigPacket() override;
|
||||
private:
|
||||
void makeAudioConfigPkt();
|
||||
private:
|
||||
|
@ -40,10 +40,8 @@ AACRtpEncoder::AACRtpEncoder(uint32_t ui32Ssrc,
|
||||
}
|
||||
|
||||
void AACRtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
RtpCodec::inputFrame(frame);
|
||||
|
||||
GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS);
|
||||
auto uiStamp = frame->stamp();
|
||||
auto uiStamp = frame->dts();
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
|
||||
@ -102,8 +100,6 @@ AACFrame::Ptr AACRtpDecoder::obtainFrame() {
|
||||
}
|
||||
|
||||
bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) {
|
||||
RtpCodec::inputRtp(rtppack, false);
|
||||
|
||||
// 获取rtp数据长度
|
||||
int length = rtppack->size() - rtppack->offset;
|
||||
|
||||
@ -153,7 +149,6 @@ bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) {
|
||||
memcpy(_adts->buffer + _adts->aac_frame_length, rtp_packet_payload + next_aac_payload_offset, cur_aac_payload_len);
|
||||
_adts->aac_frame_length += (cur_aac_payload_len);
|
||||
if (rtppack->mark == true) {
|
||||
_adts->sequence = rtppack->sequence;
|
||||
_adts->timeStamp = rtppack->timeStamp;
|
||||
writeAdtsHeader(*_adts, _adts->buffer);
|
||||
onGetAAC(_adts);
|
||||
|
@ -63,13 +63,15 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
||||
//a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH9oBQBboQAAAAwBAAAAPI8YMqA==,aM48gA==
|
||||
auto map = Parser::parseArgs(FindField(track->_fmtp.data()," ", nullptr),";","=");
|
||||
auto sps_pps = map["sprop-parameter-sets"];
|
||||
if(sps_pps.empty()){
|
||||
return std::make_shared<H264Track>();
|
||||
}
|
||||
string base64_SPS = FindField(sps_pps.data(), NULL, ",");
|
||||
string base64_PPS = FindField(sps_pps.data(), ",", NULL);
|
||||
auto sps = decodeBase64(base64_SPS);
|
||||
auto pps = decodeBase64(base64_PPS);
|
||||
if(sps.empty() || pps.empty()){
|
||||
//如果sdp里面没有sps/pps,那么可能在后续的rtp里面恢复出sps/pps
|
||||
return std::make_shared<H264Track>();
|
||||
}
|
||||
|
||||
return std::make_shared<H264Track>(sps,pps,0,0);
|
||||
}
|
||||
|
||||
@ -79,6 +81,10 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
||||
auto vps = decodeBase64(map["sprop-vps"]);
|
||||
auto sps = decodeBase64(map["sprop-sps"]);
|
||||
auto pps = decodeBase64(map["sprop-pps"]);
|
||||
if(sps.empty() || pps.empty()){
|
||||
//如果sdp里面没有sps/pps,那么可能在后续的rtp里面恢复出sps/pps
|
||||
return std::make_shared<H265Track>();
|
||||
}
|
||||
return std::make_shared<H265Track>(vps,sps,pps,0,0,0);
|
||||
}
|
||||
|
||||
@ -174,6 +180,9 @@ CodecId Factory::getCodecIdByAmf(const AMFValue &val){
|
||||
if(str == "mp4a"){
|
||||
return CodecAAC;
|
||||
}
|
||||
if(str == "hev1" || str == "hvc1"){
|
||||
return CodecH265;
|
||||
}
|
||||
WarnL << "暂不支持该Amf:" << str;
|
||||
return CodecInvalid;
|
||||
}
|
||||
@ -181,12 +190,9 @@ CodecId Factory::getCodecIdByAmf(const AMFValue &val){
|
||||
if (val.type() != AMF_NULL){
|
||||
auto type_id = val.as_integer();
|
||||
switch (type_id){
|
||||
case 7:{
|
||||
return CodecH264;
|
||||
}
|
||||
case 10:{
|
||||
return CodecAAC;
|
||||
}
|
||||
case 7: return CodecH264;
|
||||
case 10: return CodecAAC;
|
||||
case 12: return CodecH265;
|
||||
default:
|
||||
WarnL << "暂不支持该Amf:" << type_id;
|
||||
return CodecInvalid;
|
||||
@ -213,14 +219,10 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track) {
|
||||
|
||||
AMFValue Factory::getAmfByCodecId(CodecId codecId) {
|
||||
switch (codecId){
|
||||
case CodecAAC:{
|
||||
return AMFValue("mp4a");
|
||||
}
|
||||
case CodecH264:{
|
||||
return AMFValue("avc1");
|
||||
}
|
||||
default:
|
||||
return AMFValue(AMF_NULL);
|
||||
case CodecAAC: return AMFValue("mp4a");
|
||||
case CodecH264: return AMFValue("avc1");
|
||||
case CodecH265: return AMFValue(12);
|
||||
default: return AMFValue(AMF_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ typedef enum {
|
||||
TrackVideo = 0,
|
||||
TrackAudio,
|
||||
TrackTitle,
|
||||
TrackMax = 0x7FFF
|
||||
TrackMax = 3
|
||||
} TrackType;
|
||||
|
||||
/**
|
||||
@ -81,13 +81,6 @@ class Frame : public Buffer, public CodecInfo {
|
||||
public:
|
||||
typedef std::shared_ptr<Frame> Ptr;
|
||||
virtual ~Frame(){}
|
||||
/**
|
||||
* 时间戳,已经废弃,请使用dts() 、pts()接口
|
||||
*/
|
||||
inline uint32_t stamp() const {
|
||||
return dts();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 返回解码时间戳,单位毫秒
|
||||
@ -198,79 +191,15 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 帧环形缓存接口类
|
||||
*/
|
||||
class FrameRingInterface : public FrameWriterInterface{
|
||||
public:
|
||||
typedef RingBuffer<Frame::Ptr> RingType;
|
||||
typedef std::shared_ptr<FrameRingInterface> Ptr;
|
||||
|
||||
FrameRingInterface(){}
|
||||
virtual ~FrameRingInterface(){}
|
||||
|
||||
/**
|
||||
* 获取帧环形缓存
|
||||
* @return
|
||||
*/
|
||||
virtual RingType::Ptr getFrameRing() const = 0;
|
||||
|
||||
/**
|
||||
* 设置帧环形缓存
|
||||
* @param ring
|
||||
*/
|
||||
virtual void setFrameRing(const RingType::Ptr &ring) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 帧环形缓存
|
||||
*/
|
||||
class FrameRing : public FrameRingInterface{
|
||||
public:
|
||||
typedef std::shared_ptr<FrameRing> Ptr;
|
||||
|
||||
FrameRing(){
|
||||
}
|
||||
virtual ~FrameRing(){}
|
||||
|
||||
/**
|
||||
* 获取帧环形缓存
|
||||
* @return
|
||||
*/
|
||||
RingType::Ptr getFrameRing() const override {
|
||||
return _frameRing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置帧环形缓存
|
||||
* @param ring
|
||||
*/
|
||||
void setFrameRing(const RingType::Ptr &ring) override {
|
||||
_frameRing = ring;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入数据帧
|
||||
* @param frame
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
if(_frameRing){
|
||||
_frameRing->write(frame,frame->keyFrame());
|
||||
}
|
||||
}
|
||||
protected:
|
||||
RingType::Ptr _frameRing;
|
||||
};
|
||||
|
||||
/**
|
||||
* 支持代理转发的帧环形缓存
|
||||
*/
|
||||
class FrameRingInterfaceDelegate : public FrameRing {
|
||||
class FrameDispatcher : public FrameWriterInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<FrameRingInterfaceDelegate> Ptr;
|
||||
typedef std::shared_ptr<FrameDispatcher> Ptr;
|
||||
|
||||
FrameRingInterfaceDelegate(){}
|
||||
virtual ~FrameRingInterfaceDelegate(){}
|
||||
FrameDispatcher(){}
|
||||
virtual ~FrameDispatcher(){}
|
||||
|
||||
void addDelegate(const FrameWriterInterface::Ptr &delegate){
|
||||
lock_guard<mutex> lck(_mtx);
|
||||
@ -287,7 +216,6 @@ public:
|
||||
* @param frame 帧
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
FrameRing::inputFrame(frame);
|
||||
lock_guard<mutex> lck(_mtx);
|
||||
for(auto &pr : _delegateMap){
|
||||
pr.second->inputFrame(frame);
|
||||
|
@ -49,25 +49,25 @@ public:
|
||||
NAL_SPS = 7,
|
||||
NAL_PPS = 8,
|
||||
NAL_IDR = 5,
|
||||
NAL_B_P = 1
|
||||
NAL_SEI = 6,
|
||||
} NalType;
|
||||
|
||||
char *data() const override{
|
||||
return (char *)buffer.data();
|
||||
return (char *)_buffer.data();
|
||||
}
|
||||
uint32_t size() const override {
|
||||
return buffer.size();
|
||||
return _buffer.size();
|
||||
}
|
||||
uint32_t dts() const override {
|
||||
return timeStamp;
|
||||
return _dts;
|
||||
}
|
||||
|
||||
uint32_t pts() const override {
|
||||
return ptsStamp ? ptsStamp : timeStamp;
|
||||
return _pts ? _pts : _dts;
|
||||
}
|
||||
|
||||
uint32_t prefixSize() const override{
|
||||
return iPrefixSize;
|
||||
return _prefix_size;
|
||||
}
|
||||
|
||||
TrackType getTrackType() const override{
|
||||
@ -79,11 +79,11 @@ public:
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return H264_TYPE(buffer[iPrefixSize]) == H264Frame::NAL_IDR;
|
||||
return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
switch(H264_TYPE(buffer[iPrefixSize]) ){
|
||||
switch(H264_TYPE(_buffer[_prefix_size]) ){
|
||||
case H264Frame::NAL_SPS:
|
||||
case H264Frame::NAL_PPS:
|
||||
return true;
|
||||
@ -92,10 +92,10 @@ public:
|
||||
}
|
||||
}
|
||||
public:
|
||||
uint32_t timeStamp;
|
||||
uint32_t ptsStamp = 0;
|
||||
string buffer;
|
||||
uint32_t iPrefixSize = 4;
|
||||
uint32_t _dts = 0;
|
||||
uint32_t _pts = 0;
|
||||
uint32_t _prefix_size = 4;
|
||||
string _buffer;
|
||||
};
|
||||
|
||||
|
||||
@ -315,18 +315,15 @@ private:
|
||||
//I
|
||||
insertConfigFrame(frame);
|
||||
VideoTrack::inputFrame(frame);
|
||||
_last_frame_is_idr = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case H264Frame::NAL_B_P:{
|
||||
//B or P
|
||||
default:
|
||||
VideoTrack::inputFrame(frame);
|
||||
_last_frame_is_idr = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_last_frame_is_idr = type == H264Frame::NAL_IDR;
|
||||
if(_width == 0 && ready()){
|
||||
onReady();
|
||||
}
|
||||
@ -343,19 +340,19 @@ private:
|
||||
|
||||
if(!_sps.empty()){
|
||||
auto spsFrame = std::make_shared<H264Frame>();
|
||||
spsFrame->iPrefixSize = 4;
|
||||
spsFrame->buffer.assign("\x0\x0\x0\x1",4);
|
||||
spsFrame->buffer.append(_sps);
|
||||
spsFrame->timeStamp = frame->stamp();
|
||||
spsFrame->_prefix_size = 4;
|
||||
spsFrame->_buffer.assign("\x0\x0\x0\x1",4);
|
||||
spsFrame->_buffer.append(_sps);
|
||||
spsFrame->_dts = frame->dts();
|
||||
VideoTrack::inputFrame(spsFrame);
|
||||
}
|
||||
|
||||
if(!_pps.empty()){
|
||||
auto ppsFrame = std::make_shared<H264Frame>();
|
||||
ppsFrame->iPrefixSize = 4;
|
||||
ppsFrame->buffer.assign("\x0\x0\x0\x1",4);
|
||||
ppsFrame->buffer.append(_pps);
|
||||
ppsFrame->timeStamp = frame->stamp();
|
||||
ppsFrame->_prefix_size = 4;
|
||||
ppsFrame->_buffer.assign("\x0\x0\x0\x1",4);
|
||||
ppsFrame->_buffer.append(_pps);
|
||||
ppsFrame->_dts = frame->dts();
|
||||
VideoTrack::inputFrame(ppsFrame);
|
||||
}
|
||||
}
|
||||
|
@ -35,15 +35,13 @@ H264RtmpDecoder::H264RtmpDecoder() {
|
||||
H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = obtainObj();
|
||||
frame->buffer.clear();
|
||||
frame->iPrefixSize = 4;
|
||||
frame->_buffer.clear();
|
||||
frame->_prefix_size = 4;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
||||
key_pos = decodeRtmp(rtmp);
|
||||
RtmpCodec::inputRtmp(rtmp, key_pos);
|
||||
return key_pos;
|
||||
return decodeRtmp(rtmp);
|
||||
}
|
||||
|
||||
bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
@ -80,10 +78,10 @@ bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
|
||||
inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
||||
#if 1
|
||||
_h264frame->timeStamp = dts;
|
||||
_h264frame->ptsStamp = pts;
|
||||
_h264frame->buffer.assign("\x0\x0\x0\x1", 4); //添加264头
|
||||
_h264frame->buffer.append(pcData, iLen);
|
||||
_h264frame->_dts = dts;
|
||||
_h264frame->_pts = pts;
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4); //添加264头
|
||||
_h264frame->_buffer.append(pcData, iLen);
|
||||
|
||||
//写入环形缓存
|
||||
RtmpCodec::inputFrame(_h264frame);
|
||||
@ -101,12 +99,23 @@ inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dt
|
||||
|
||||
H264RtmpEncoder::H264RtmpEncoder(const Track::Ptr &track) {
|
||||
_track = dynamic_pointer_cast<H264Track>(track);
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::makeConfigPacket(){
|
||||
if (_track && _track->ready()) {
|
||||
//尝试从track中获取sps pps信息
|
||||
_sps = _track->getSps();
|
||||
_pps = _track->getPps();
|
||||
}
|
||||
|
||||
if (!_sps.empty() && !_pps.empty()) {
|
||||
//获取到sps/pps
|
||||
makeVideoConfigPkt();
|
||||
_gotSpsPps = true;
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
RtmpCodec::inputFrame(frame);
|
||||
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
auto type = H264_TYPE(((uint8_t*)pcData)[0]);
|
||||
@ -116,38 +125,26 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
switch (type) {
|
||||
case H264Frame::NAL_SPS: {
|
||||
//sps
|
||||
if(_sps.empty()){
|
||||
_sps = string(pcData, iLen);
|
||||
}
|
||||
}
|
||||
makeConfigPacket();
|
||||
break;
|
||||
}
|
||||
case H264Frame::NAL_PPS: {
|
||||
//pps
|
||||
if(_pps.empty()){
|
||||
_pps = string(pcData, iLen);
|
||||
}
|
||||
}
|
||||
makeConfigPacket();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(_track && _track->ready()){
|
||||
//尝试从track中获取sps pps信息
|
||||
_sps = _track->getSps();
|
||||
_pps = _track->getPps();
|
||||
}
|
||||
|
||||
if(!_sps.empty() && !_pps.empty()){
|
||||
_gotSpsPps = true;
|
||||
makeVideoConfigPkt();
|
||||
}
|
||||
if(type == H264Frame::NAL_SEI){
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type){
|
||||
case H264Frame::NAL_IDR:
|
||||
case H264Frame::NAL_B_P:{
|
||||
if(_lastPacket && _lastPacket->timeStamp != frame->stamp()) {
|
||||
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
@ -156,7 +153,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
//I or P or B frame
|
||||
int8_t flags = 7; //h.264
|
||||
bool is_config = false;
|
||||
flags |= ((frame->keyFrame() ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
|
||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
_lastPacket->strBuf.clear();
|
||||
@ -168,7 +165,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
||||
_lastPacket->streamId = STREAM_MEDIA;
|
||||
_lastPacket->timeStamp = frame->stamp();
|
||||
_lastPacket->timeStamp = frame->dts();
|
||||
_lastPacket->typeId = MSG_VIDEO;
|
||||
|
||||
}
|
||||
@ -177,12 +174,6 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
_lastPacket->strBuf.append(pcData, iLen);
|
||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
|
@ -90,6 +90,11 @@ public:
|
||||
* @param frame 帧数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 生成config包
|
||||
*/
|
||||
void makeConfigPacket() override;
|
||||
private:
|
||||
void makeVideoConfigPkt();
|
||||
private:
|
||||
|
@ -70,15 +70,13 @@ H264RtpDecoder::H264RtpDecoder() {
|
||||
H264Frame::Ptr H264RtpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<H264Frame>::obtainObj();
|
||||
frame->buffer.clear();
|
||||
frame->iPrefixSize = 4;
|
||||
frame->_buffer.clear();
|
||||
frame->_prefix_size = 4;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H264RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) {
|
||||
key_pos = decodeRtp(rtp);
|
||||
RtpCodec::inputRtp(rtp, key_pos);
|
||||
return key_pos;
|
||||
return decodeRtp(rtp);
|
||||
}
|
||||
|
||||
bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
@ -115,9 +113,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
|
||||
if (nal.type >= 0 && nal.type < 24) {
|
||||
//a full frame
|
||||
_h264frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->buffer.append((char *)frame, length);
|
||||
_h264frame->timeStamp = rtppack->timeStamp;
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.append((char *)frame, length);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
auto key = _h264frame->keyFrame();
|
||||
onGetH264(_h264frame);
|
||||
return (key); //i frame
|
||||
@ -144,9 +142,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
//过小的帧丢弃
|
||||
NALU nal;
|
||||
MakeNalu(ptr[0], nal);
|
||||
_h264frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->buffer.append((char *)ptr, len);
|
||||
_h264frame->timeStamp = rtppack->timeStamp;
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.append((char *)ptr, len);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
if(nal.type == H264Frame::NAL_IDR){
|
||||
haveIDR = true;
|
||||
}
|
||||
@ -164,10 +162,10 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
if (fu.S) {
|
||||
//该帧的第一个rtp包 FU-A start
|
||||
char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type);
|
||||
_h264frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->buffer.push_back(tmp);
|
||||
_h264frame->buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->timeStamp = rtppack->timeStamp;
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.push_back(tmp);
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return _h264frame->keyFrame();
|
||||
@ -175,25 +173,24 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
|
||||
if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) {
|
||||
//中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃
|
||||
_h264frame->buffer.clear();
|
||||
_h264frame->_buffer.clear();
|
||||
WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fu.E) {
|
||||
//该帧的中间rtp包 FU-A mid
|
||||
_h264frame->buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
//该帧最后一个rtp包 FU-A end
|
||||
_h264frame->buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->timeStamp = rtppack->timeStamp;
|
||||
auto key = _h264frame->keyFrame();
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
onGetH264(_h264frame);
|
||||
return key;
|
||||
return false;
|
||||
}
|
||||
|
||||
default:{
|
||||
@ -211,8 +208,19 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
}
|
||||
|
||||
void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) {
|
||||
auto flag = _dts_generator.getDts(frame->_pts,frame->_dts);
|
||||
if(!flag){
|
||||
if(frame->configFrame() || frame->keyFrame()){
|
||||
flag = true;
|
||||
frame->_dts = frame->_pts;
|
||||
}
|
||||
}
|
||||
|
||||
//根据pts计算dts
|
||||
if(flag){
|
||||
//写入环形缓存
|
||||
RtpCodec::inputFrame(frame);
|
||||
}
|
||||
_h264frame = obtainFrame();
|
||||
}
|
||||
|
||||
@ -232,11 +240,9 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ui32Ssrc,
|
||||
}
|
||||
|
||||
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
RtpCodec::inputFrame(frame);
|
||||
|
||||
GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS);
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto uiStamp = frame->stamp();
|
||||
auto uiStamp = frame->pts();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
//获取NALU的5bit 帧类型
|
||||
unsigned char naluType = H264_TYPE(pcData[0]);
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "Rtsp/RtpCodec.h"
|
||||
#include "Util/ResourcePool.h"
|
||||
#include "Extension/H264.h"
|
||||
#include "Common/Stamp.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
@ -66,6 +67,7 @@ private:
|
||||
H264Frame::Ptr obtainFrame();
|
||||
private:
|
||||
H264Frame::Ptr _h264frame;
|
||||
DtsGenerator _dts_generator;
|
||||
int _lastSeq = 0;
|
||||
};
|
||||
|
||||
|
@ -74,23 +74,23 @@ public:
|
||||
} NaleType;
|
||||
|
||||
char *data() const override {
|
||||
return (char *) buffer.data();
|
||||
return (char *) _buffer.data();
|
||||
}
|
||||
|
||||
uint32_t size() const override {
|
||||
return buffer.size();
|
||||
return _buffer.size();
|
||||
}
|
||||
|
||||
uint32_t dts() const override {
|
||||
return timeStamp;
|
||||
return _dts;
|
||||
}
|
||||
|
||||
uint32_t pts() const override {
|
||||
return ptsStamp ? ptsStamp : timeStamp;
|
||||
return _pts ? _pts : _dts;
|
||||
}
|
||||
|
||||
uint32_t prefixSize() const override {
|
||||
return iPrefixSize;
|
||||
return _prefix_size;
|
||||
}
|
||||
|
||||
TrackType getTrackType() const override {
|
||||
@ -102,11 +102,11 @@ public:
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return isKeyFrame(H265_TYPE(buffer[iPrefixSize]));
|
||||
return isKeyFrame(H265_TYPE(_buffer[_prefix_size]));
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
switch(H265_TYPE(buffer[iPrefixSize])){
|
||||
switch(H265_TYPE(_buffer[_prefix_size])){
|
||||
case H265Frame::NAL_VPS:
|
||||
case H265Frame::NAL_SPS:
|
||||
case H265Frame::NAL_PPS:
|
||||
@ -131,10 +131,10 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
uint32_t timeStamp;
|
||||
uint32_t ptsStamp = 0;
|
||||
string buffer;
|
||||
uint32_t iPrefixSize = 4;
|
||||
uint32_t _dts = 0;
|
||||
uint32_t _pts = 0;
|
||||
uint32_t _prefix_size = 4;
|
||||
string _buffer;
|
||||
};
|
||||
|
||||
|
||||
@ -356,27 +356,27 @@ private:
|
||||
}
|
||||
if(!_vps.empty()){
|
||||
auto vpsFrame = std::make_shared<H265Frame>();
|
||||
vpsFrame->iPrefixSize = 4;
|
||||
vpsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
vpsFrame->buffer.append(_vps);
|
||||
vpsFrame->timeStamp = frame->stamp();
|
||||
vpsFrame->_prefix_size = 4;
|
||||
vpsFrame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
vpsFrame->_buffer.append(_vps);
|
||||
vpsFrame->_dts = frame->dts();
|
||||
VideoTrack::inputFrame(vpsFrame);
|
||||
}
|
||||
if (!_sps.empty()) {
|
||||
auto spsFrame = std::make_shared<H265Frame>();
|
||||
spsFrame->iPrefixSize = 4;
|
||||
spsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
spsFrame->buffer.append(_sps);
|
||||
spsFrame->timeStamp = frame->stamp();
|
||||
spsFrame->_prefix_size = 4;
|
||||
spsFrame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
spsFrame->_buffer.append(_sps);
|
||||
spsFrame->_dts = frame->dts();
|
||||
VideoTrack::inputFrame(spsFrame);
|
||||
}
|
||||
|
||||
if (!_pps.empty()) {
|
||||
auto ppsFrame = std::make_shared<H265Frame>();
|
||||
ppsFrame->iPrefixSize = 4;
|
||||
ppsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
ppsFrame->buffer.append(_pps);
|
||||
ppsFrame->timeStamp = frame->stamp();
|
||||
ppsFrame->_prefix_size = 4;
|
||||
ppsFrame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
ppsFrame->_buffer.append(_pps);
|
||||
ppsFrame->_dts = frame->dts();
|
||||
VideoTrack::inputFrame(ppsFrame);
|
||||
}
|
||||
}
|
||||
|
@ -70,15 +70,13 @@ H265RtpDecoder::H265RtpDecoder() {
|
||||
H265Frame::Ptr H265RtpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<H265Frame>::obtainObj();
|
||||
frame->buffer.clear();
|
||||
frame->iPrefixSize = 4;
|
||||
frame->_buffer.clear();
|
||||
frame->_prefix_size = 4;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H265RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) {
|
||||
key_pos = decodeRtp(rtp);
|
||||
RtpCodec::inputRtp(rtp, key_pos);
|
||||
return key_pos;
|
||||
return decodeRtp(rtp);
|
||||
}
|
||||
|
||||
bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
@ -101,11 +99,11 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
MakeFU(frame[2], fu);
|
||||
if (fu.S) {
|
||||
//该帧的第一个rtp包
|
||||
_h265frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h265frame->buffer.push_back(fu.type << 1);
|
||||
_h265frame->buffer.push_back(0x01);
|
||||
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
||||
_h265frame->timeStamp = rtppack->timeStamp;
|
||||
_h265frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h265frame->_buffer.push_back(fu.type << 1);
|
||||
_h265frame->_buffer.push_back(0x01);
|
||||
_h265frame->_buffer.append((char *) frame + 3, length - 3);
|
||||
_h265frame->_pts = rtppack->timeStamp;
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return (_h265frame->keyFrame()); //i frame
|
||||
@ -113,32 +111,31 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
|
||||
if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) {
|
||||
//中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃
|
||||
_h265frame->buffer.clear();
|
||||
_h265frame->_buffer.clear();
|
||||
WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fu.E) {
|
||||
//该帧的中间rtp包
|
||||
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
||||
_h265frame->_buffer.append((char *) frame + 3, length - 3);
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
//该帧最后一个rtp包
|
||||
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
||||
_h265frame->timeStamp = rtppack->timeStamp;
|
||||
auto key = _h265frame->keyFrame();
|
||||
_h265frame->_buffer.append((char *) frame + 3, length - 3);
|
||||
_h265frame->_pts = rtppack->timeStamp;
|
||||
onGetH265(_h265frame);
|
||||
return key;
|
||||
return false;
|
||||
}
|
||||
|
||||
default: // 4.4.1. Single NAL Unit Packets (p24)
|
||||
//a full frame
|
||||
_h265frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h265frame->buffer.append((char *)frame, length);
|
||||
_h265frame->timeStamp = rtppack->timeStamp;
|
||||
_h265frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h265frame->_buffer.append((char *)frame, length);
|
||||
_h265frame->_pts = rtppack->timeStamp;
|
||||
auto key = _h265frame->keyFrame();
|
||||
onGetH265(_h265frame);
|
||||
return key;
|
||||
@ -146,8 +143,18 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
}
|
||||
|
||||
void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) {
|
||||
//计算dts
|
||||
auto flag = _dts_generator.getDts(frame->_pts,frame->_dts);
|
||||
if(!flag){
|
||||
if(frame->configFrame() || frame->keyFrame()){
|
||||
flag = true;
|
||||
frame->_dts = frame->_pts;
|
||||
}
|
||||
}
|
||||
if(flag){
|
||||
//写入环形缓存
|
||||
RtpCodec::inputFrame(frame);
|
||||
}
|
||||
_h265frame = obtainFrame();
|
||||
}
|
||||
|
||||
@ -167,11 +174,9 @@ H265RtpEncoder::H265RtpEncoder(uint32_t ui32Ssrc,
|
||||
}
|
||||
|
||||
void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
RtpCodec::inputFrame(frame);
|
||||
|
||||
GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS);
|
||||
uint8_t *pcData = (uint8_t*)frame->data() + frame->prefixSize();
|
||||
auto uiStamp = frame->stamp();
|
||||
auto uiStamp = frame->pts();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
unsigned char naluType = H265_TYPE(pcData[0]); //获取NALU的5bit 帧类型
|
||||
uiStamp %= cycleMS;
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "Rtsp/RtpCodec.h"
|
||||
#include "Util/ResourcePool.h"
|
||||
#include "Extension/H265.h"
|
||||
#include "Common/Stamp.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
@ -67,6 +68,7 @@ private:
|
||||
H265Frame::Ptr obtainFrame();
|
||||
private:
|
||||
H265Frame::Ptr _h265frame;
|
||||
DtsGenerator _dts_generator;
|
||||
int _lastSeq = 0;
|
||||
};
|
||||
|
||||
|
@ -1330,7 +1330,7 @@ int hevcDecodeShortTermRps(T_GetBitContext *pvBuf,
|
||||
int iDeltaRps;
|
||||
unsigned int uiAbsDeltaRps;
|
||||
uint8_t u8UseDeltaFlag = 0;
|
||||
uint8_t u8DeltaRpsSign;
|
||||
uint8_t u8DeltaRpsSign = 0;
|
||||
|
||||
if (is_slice_header) {
|
||||
unsigned int uiDeltaIdx = parseUe(pvBuf) + 1;
|
||||
|
@ -39,7 +39,7 @@ namespace mediakit{
|
||||
/**
|
||||
* 媒体通道描述类,也支持帧输入输出
|
||||
*/
|
||||
class Track : public FrameRingInterfaceDelegate , public CodecInfo{
|
||||
class Track : public FrameDispatcher , public CodecInfo{
|
||||
public:
|
||||
typedef std::shared_ptr<Track> Ptr;
|
||||
Track(){}
|
||||
@ -131,6 +131,35 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class TrackSource{
|
||||
public:
|
||||
TrackSource(){}
|
||||
virtual ~TrackSource(){}
|
||||
|
||||
/**
|
||||
* 获取全部的Track
|
||||
* @param trackReady 是否获取全部已经准备好的Track
|
||||
* @return
|
||||
*/
|
||||
virtual vector<Track::Ptr> getTracks(bool trackReady = true) const = 0;
|
||||
|
||||
/**
|
||||
* 获取特定Track
|
||||
* @param type track类型
|
||||
* @param trackReady 是否获取全部已经准备好的Track
|
||||
* @return
|
||||
*/
|
||||
Track::Ptr getTrack(TrackType type , bool trackReady = true) const {
|
||||
auto tracks = getTracks(trackReady);
|
||||
for(auto &track : tracks){
|
||||
if(track->getTrackType() == type){
|
||||
return track;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_TRACK_H
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "Network/Buffer.h"
|
||||
#include "Util/ResourcePool.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
@ -45,23 +46,55 @@ namespace mediakit {
|
||||
/**
|
||||
* http content部分基类定义
|
||||
*/
|
||||
class HttpBody{
|
||||
class HttpBody : public std::enable_shared_from_this<HttpBody>{
|
||||
public:
|
||||
typedef std::shared_ptr<HttpBody> Ptr;
|
||||
HttpBody(){}
|
||||
HttpBody(){
|
||||
// _async_read_thread = WorkThreadPool::Instance().getPoller();
|
||||
}
|
||||
virtual ~HttpBody(){}
|
||||
|
||||
/**
|
||||
* 剩余数据大小
|
||||
* 剩余数据大小,如果返回>=INT64_MAX, 那么就不设置content-length
|
||||
*/
|
||||
virtual uint64_t remainSize() { return 0;};
|
||||
|
||||
/**
|
||||
* 读取一定字节数,返回大小可能小于size
|
||||
* @param size 请求大小
|
||||
* @return 字节对象
|
||||
* @return 字节对象,如果读完了,那么请返回nullptr
|
||||
*/
|
||||
virtual Buffer::Ptr readData(uint32_t size) { return nullptr;};
|
||||
|
||||
/**
|
||||
* 异步请求读取一定字节数,返回大小可能小于size
|
||||
* @param size 请求大小
|
||||
* @param cb 回调函数
|
||||
*/
|
||||
virtual void readDataAsync(uint32_t size,const function<void(const Buffer::Ptr &buf)> &cb){
|
||||
#if 0
|
||||
if(size >= remainSize()){
|
||||
//假如剩余数据很小,那么同步获取(为了优化性能)
|
||||
cb(readData(size));
|
||||
return;
|
||||
}
|
||||
//如果是大文件,那么后台读取
|
||||
weak_ptr<HttpBody> weakSelf = shared_from_this();
|
||||
_async_read_thread->async([cb,size,weakSelf](){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(strongSelf){
|
||||
cb(strongSelf->readData(size));
|
||||
}
|
||||
});
|
||||
#else
|
||||
//由于unix和linux是通过mmap的方式读取文件,所以把读文件操作放在后台线程并不能提高性能
|
||||
//反而会由于频繁的线程切换导致性能降低以及延时增加,所以我们默认同步获取文件内容
|
||||
//(其实并没有读,拷贝文件数据时在内核态完成文件读)
|
||||
cb(readData(size));
|
||||
#endif
|
||||
}
|
||||
private:
|
||||
// EventPoller::Ptr _async_read_thread;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -76,8 +76,8 @@ bool HttpServerCookie::isExpired() {
|
||||
return _ticker.elapsedTime() > _max_elapsed * 1000;
|
||||
}
|
||||
|
||||
std::shared_ptr<lock_guard<mutex> > HttpServerCookie::getLock(){
|
||||
return std::make_shared<lock_guard<mutex> >(_mtx);
|
||||
std::shared_ptr<lock_guard<recursive_mutex> > HttpServerCookie::getLock(){
|
||||
return std::make_shared<lock_guard<recursive_mutex> >(_mtx);
|
||||
}
|
||||
|
||||
string HttpServerCookie::cookieExpireTime() const{
|
||||
|
@ -108,9 +108,7 @@ public:
|
||||
* 获取区域锁
|
||||
* @return
|
||||
*/
|
||||
std::shared_ptr<lock_guard<mutex> > getLock();
|
||||
|
||||
|
||||
std::shared_ptr<lock_guard<recursive_mutex> > getLock();
|
||||
private:
|
||||
string cookieExpireTime() const ;
|
||||
private:
|
||||
@ -119,7 +117,7 @@ private:
|
||||
string _cookie_uuid;
|
||||
uint64_t _max_elapsed;
|
||||
Ticker _ticker;
|
||||
mutex _mtx;
|
||||
recursive_mutex _mtx;
|
||||
std::weak_ptr<HttpCookieManager> _manager;
|
||||
};
|
||||
|
||||
|
673
src/Http/HttpFileManager.cpp
Normal file
673
src/Http/HttpFileManager.cpp
Normal file
@ -0,0 +1,673 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#if !defined(_WIN32)
|
||||
#include <dirent.h>
|
||||
#endif //!defined(_WIN32)
|
||||
#include <iomanip>
|
||||
#include "HttpFileManager.h"
|
||||
#include "Util/File.h"
|
||||
#include "HttpSession.h"
|
||||
#include "Record/HlsMediaSource.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
// hls的播放cookie缓存时间默认60秒,
|
||||
// 每次访问一次该cookie,那么将重新刷新cookie有效期
|
||||
// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权
|
||||
static int kHlsCookieSecond = 60;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kHlsSuffix = "/hls.m3u8";
|
||||
|
||||
class HttpCookieAttachment{
|
||||
public:
|
||||
HttpCookieAttachment() {};
|
||||
~HttpCookieAttachment() {};
|
||||
public:
|
||||
//cookie生效作用域,本cookie只对该目录下的文件生效
|
||||
string _path;
|
||||
//上次鉴权失败信息,为空则上次鉴权成功
|
||||
string _err_msg;
|
||||
//本cookie是否为hls直播的
|
||||
bool _is_hls = false;
|
||||
//hls直播时的其他一些信息,主要用于播放器个数计数以及流量计数
|
||||
HlsCookieData::Ptr _hls_data;
|
||||
//如果是hls直播,那么判断该cookie是否使用过MediaSource::findAsync查找过
|
||||
//如果程序未正常退出,会残余上次的hls文件,所以判断hls直播是否存在的关键不是文件存在与否
|
||||
//而是应该判断HlsMediaSource是否已注册,但是这样会每次获取m3u8文件时都会用MediaSource::findAsync判断一次
|
||||
//会导致程序性能低下,所以我们应该在cookie声明周期的第一次判断HlsMediaSource是否已经注册,后续通过文件存在与否判断
|
||||
bool _have_find_media_source = false;
|
||||
};
|
||||
|
||||
static const char *s_mime_src[][2] = {
|
||||
{"html", "text/html"},
|
||||
{"htm", "text/html"},
|
||||
{"shtml", "text/html"},
|
||||
{"css", "text/css"},
|
||||
{"xml", "text/xml"},
|
||||
{"gif", "image/gif"},
|
||||
{"jpeg", "image/jpeg"},
|
||||
{"jpg", "image/jpeg"},
|
||||
{"js", "application/javascript"},
|
||||
{"map", "application/javascript" },
|
||||
{"atom", "application/atom+xml"},
|
||||
{"rss", "application/rss+xml"},
|
||||
{"mml", "text/mathml"},
|
||||
{"txt", "text/plain"},
|
||||
{"jad", "text/vnd.sun.j2me.app-descriptor"},
|
||||
{"wml", "text/vnd.wap.wml"},
|
||||
{"htc", "text/x-component"},
|
||||
{"png", "image/png"},
|
||||
{"tif", "image/tiff"},
|
||||
{"tiff", "image/tiff"},
|
||||
{"wbmp", "image/vnd.wap.wbmp"},
|
||||
{"ico", "image/x-icon"},
|
||||
{"jng", "image/x-jng"},
|
||||
{"bmp", "image/x-ms-bmp"},
|
||||
{"svg", "image/svg+xml"},
|
||||
{"svgz", "image/svg+xml"},
|
||||
{"webp", "image/webp"},
|
||||
{"woff", "application/font-woff"},
|
||||
{"woff2","application/font-woff" },
|
||||
{"jar", "application/java-archive"},
|
||||
{"war", "application/java-archive"},
|
||||
{"ear", "application/java-archive"},
|
||||
{"json", "application/json"},
|
||||
{"hqx", "application/mac-binhex40"},
|
||||
{"doc", "application/msword"},
|
||||
{"pdf", "application/pdf"},
|
||||
{"ps", "application/postscript"},
|
||||
{"eps", "application/postscript"},
|
||||
{"ai", "application/postscript"},
|
||||
{"rtf", "application/rtf"},
|
||||
{"m3u8", "application/vnd.apple.mpegurl"},
|
||||
{"xls", "application/vnd.ms-excel"},
|
||||
{"eot", "application/vnd.ms-fontobject"},
|
||||
{"ppt", "application/vnd.ms-powerpoint"},
|
||||
{"wmlc", "application/vnd.wap.wmlc"},
|
||||
{"kml", "application/vnd.google-earth.kml+xml"},
|
||||
{"kmz", "application/vnd.google-earth.kmz"},
|
||||
{"7z", "application/x-7z-compressed"},
|
||||
{"cco", "application/x-cocoa"},
|
||||
{"jardiff", "application/x-java-archive-diff"},
|
||||
{"jnlp", "application/x-java-jnlp-file"},
|
||||
{"run", "application/x-makeself"},
|
||||
{"pl", "application/x-perl"},
|
||||
{"pm", "application/x-perl"},
|
||||
{"prc", "application/x-pilot"},
|
||||
{"pdb", "application/x-pilot"},
|
||||
{"rar", "application/x-rar-compressed"},
|
||||
{"rpm", "application/x-redhat-package-manager"},
|
||||
{"sea", "application/x-sea"},
|
||||
{"swf", "application/x-shockwave-flash"},
|
||||
{"sit", "application/x-stuffit"},
|
||||
{"tcl", "application/x-tcl"},
|
||||
{"tk", "application/x-tcl"},
|
||||
{"der", "application/x-x509-ca-cert"},
|
||||
{"pem", "application/x-x509-ca-cert"},
|
||||
{"crt", "application/x-x509-ca-cert"},
|
||||
{"xpi", "application/x-xpinstall"},
|
||||
{"xhtml", "application/xhtml+xml"},
|
||||
{"xspf", "application/xspf+xml"},
|
||||
{"zip", "application/zip"},
|
||||
{"bin", "application/octet-stream"},
|
||||
{"exe", "application/octet-stream"},
|
||||
{"dll", "application/octet-stream"},
|
||||
{"deb", "application/octet-stream"},
|
||||
{"dmg", "application/octet-stream"},
|
||||
{"iso", "application/octet-stream"},
|
||||
{"img", "application/octet-stream"},
|
||||
{"msi", "application/octet-stream"},
|
||||
{"msp", "application/octet-stream"},
|
||||
{"msm", "application/octet-stream"},
|
||||
{"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
|
||||
{"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
|
||||
{"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
||||
{"mid", "audio/midi"},
|
||||
{"midi", "audio/midi"},
|
||||
{"kar", "audio/midi"},
|
||||
{"mp3", "audio/mpeg"},
|
||||
{"ogg", "audio/ogg"},
|
||||
{"m4a", "audio/x-m4a"},
|
||||
{"ra", "audio/x-realaudio"},
|
||||
{"3gpp", "video/3gpp"},
|
||||
{"3gp", "video/3gpp"},
|
||||
{"ts", "video/mp2t"},
|
||||
{"mp4", "video/mp4"},
|
||||
{"mpeg", "video/mpeg"},
|
||||
{"mpg", "video/mpeg"},
|
||||
{"mov", "video/quicktime"},
|
||||
{"webm", "video/webm"},
|
||||
{"flv", "video/x-flv"},
|
||||
{"m4v", "video/x-m4v"},
|
||||
{"mng", "video/x-mng"},
|
||||
{"asx", "video/x-ms-asf"},
|
||||
{"asf", "video/x-ms-asf"},
|
||||
{"wmv", "video/x-ms-wmv"},
|
||||
{"avi", "video/x-msvideo"},
|
||||
};
|
||||
|
||||
const string &HttpFileManager::getContentType(const char *name) {
|
||||
const char *dot;
|
||||
dot = strrchr(name, '.');
|
||||
static StrCaseMap mapType;
|
||||
static onceToken token([&]() {
|
||||
for (unsigned int i = 0; i < sizeof (s_mime_src) / sizeof (s_mime_src[0]); ++i) {
|
||||
mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]);
|
||||
}
|
||||
});
|
||||
static string defaultType = "text/plain";
|
||||
if (!dot) {
|
||||
return defaultType;
|
||||
}
|
||||
auto it = mapType.find(dot + 1);
|
||||
if (it == mapType.end()) {
|
||||
return defaultType;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static string searchIndexFile(const string &dir){
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
if ((pDir = opendir(dir.data())) == NULL) {
|
||||
return "";
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"};
|
||||
if(indexSet.find(pDirent->d_name) != indexSet.end()){
|
||||
string ret = pDirent->d_name;
|
||||
closedir(pDir);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
closedir(pDir);
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
|
||||
string strPathPrefix(strFullPath);
|
||||
string last_dir_name;
|
||||
if(strPathPrefix.back() == '/'){
|
||||
strPathPrefix.pop_back();
|
||||
}else{
|
||||
last_dir_name = split(strPathPrefix,"/").back();
|
||||
}
|
||||
|
||||
if (!File::is_dir(strPathPrefix.data())) {
|
||||
return false;
|
||||
}
|
||||
stringstream ss;
|
||||
ss << "<html>\r\n"
|
||||
"<head>\r\n"
|
||||
"<title>文件索引</title>\r\n"
|
||||
"</head>\r\n"
|
||||
"<body>\r\n"
|
||||
"<h1>文件索引:";
|
||||
|
||||
ss << httpPath;
|
||||
ss << "</h1>\r\n";
|
||||
if (httpPath != "/") {
|
||||
ss << "<li><a href=\"";
|
||||
ss << "/";
|
||||
ss << "\">";
|
||||
ss << "根目录";
|
||||
ss << "</a></li>\r\n";
|
||||
|
||||
ss << "<li><a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << "./";
|
||||
}else{
|
||||
ss << "../";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << "上级目录";
|
||||
ss << "</a></li>\r\n";
|
||||
}
|
||||
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
if ((pDir = opendir(strPathPrefix.data())) == NULL) {
|
||||
return false;
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
if (File::is_special_dir(pDirent->d_name)) {
|
||||
continue;
|
||||
}
|
||||
if(pDirent->d_name[0] == '.'){
|
||||
continue;
|
||||
}
|
||||
setFile.emplace(pDirent->d_name);
|
||||
}
|
||||
int i = 0;
|
||||
for(auto &strFile :setFile ){
|
||||
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
||||
bool isDir = File::is_dir(strAbsolutePath.data());
|
||||
ss << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << last_dir_name << "/" << strFile;
|
||||
}else{
|
||||
ss << strFile;
|
||||
}
|
||||
|
||||
if(isDir){
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << strFile;
|
||||
if (isDir) {
|
||||
ss << "/</a></li>\r\n";
|
||||
continue;
|
||||
}
|
||||
//是文件
|
||||
struct stat fileData;
|
||||
if (0 == stat(strAbsolutePath.data(), &fileData)) {
|
||||
auto &fileSize = fileData.st_size;
|
||||
if (fileSize < 1024) {
|
||||
ss << " (" << fileData.st_size << "B)" << endl;
|
||||
} else if (fileSize < 1024 * 1024) {
|
||||
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024.0 << "KB)";
|
||||
} else if (fileSize < 1024 * 1024 * 1024) {
|
||||
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024.0 << "MB)";
|
||||
} else {
|
||||
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024 / 1024.0 << "GB)";
|
||||
}
|
||||
}
|
||||
ss << "</a></li>\r\n";
|
||||
}
|
||||
closedir(pDir);
|
||||
ss << "<ul>\r\n";
|
||||
ss << "</ul>\r\n</body></html>";
|
||||
ss.str().swap(strRet);
|
||||
return true;
|
||||
}
|
||||
|
||||
//字符串是否以xx结尾
|
||||
static bool end_of(const string &str, const string &substr){
|
||||
auto pos = str.rfind(substr);
|
||||
return pos != string::npos && pos == str.size() - substr.size();
|
||||
};
|
||||
|
||||
//拦截hls的播放请求
|
||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err,"",kHlsCookieSecond);
|
||||
};
|
||||
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,sender);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断http客户端是否有权限访问文件的逻辑步骤
|
||||
* 1、根据http请求头查找cookie,找到进入步骤3
|
||||
* 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5
|
||||
* 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
|
||||
* 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
|
||||
* 5、触发kBroadcastHttpAccess事件
|
||||
*/
|
||||
static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, bool is_dir,
|
||||
const function<void(const string &errMsg, const HttpServerCookie::Ptr &cookie)> &callback) {
|
||||
//获取用户唯一id
|
||||
auto uid = parser.Params();
|
||||
auto path = parser.Url();
|
||||
|
||||
//先根据http头中的cookie字段获取cookie
|
||||
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getValues());
|
||||
//如果不是从http头中找到的cookie,我们让http客户端设置下cookie
|
||||
bool cookie_from_header = true;
|
||||
if (!cookie && !uid.empty()) {
|
||||
//客户端请求中无cookie,再根据该用户的用户id获取cookie
|
||||
cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
|
||||
cookie_from_header = false;
|
||||
}
|
||||
|
||||
if (cookie) {
|
||||
//找到了cookie,对cookie上锁先
|
||||
auto lck = cookie->getLock();
|
||||
auto attachment = (*cookie)[kCookieName].get<HttpCookieAttachment>();
|
||||
if (path.find(attachment._path) == 0) {
|
||||
//上次cookie是限定本目录
|
||||
if (attachment._err_msg.empty()) {
|
||||
//上次鉴权成功
|
||||
if(attachment._is_hls){
|
||||
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
||||
cookie->updateTime();
|
||||
cookie_from_header = false;
|
||||
}
|
||||
callback("", cookie_from_header ? nullptr : cookie);
|
||||
return;
|
||||
}
|
||||
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
||||
if (parser.Params().empty() || parser.Params() == cookie->getUid()) {
|
||||
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
||||
callback(attachment._err_msg, cookie_from_header ? nullptr : cookie);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权
|
||||
HttpCookieManager::Instance().delCookie(cookie);
|
||||
}
|
||||
|
||||
bool is_hls = mediaInfo._schema == HLS_SCHEMA;
|
||||
string identifier = sender.getIdentifier();
|
||||
string peer_ip = sender.get_peer_ip();
|
||||
uint16_t peer_port = sender.get_peer_port();
|
||||
|
||||
//该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
|
||||
HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo, identifier, peer_ip, peer_port]
|
||||
(const string &errMsg, const string &cookie_path_in, int cookieLifeSecond) {
|
||||
HttpServerCookie::Ptr cookie;
|
||||
if (cookieLifeSecond) {
|
||||
//本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
|
||||
string cookie_path = cookie_path_in;
|
||||
if (cookie_path.empty()) {
|
||||
//如果未设置鉴权目录,那么我们采用当前目录
|
||||
cookie_path = is_dir ? path : path.substr(0, path.rfind("/") + 1);
|
||||
}
|
||||
|
||||
cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
|
||||
//对cookie上锁
|
||||
auto lck = cookie->getLock();
|
||||
HttpCookieAttachment attachment;
|
||||
//记录用户能访问的路径
|
||||
attachment._path = cookie_path;
|
||||
//记录能否访问
|
||||
attachment._err_msg = errMsg;
|
||||
//记录访问的是否为hls
|
||||
attachment._is_hls = is_hls;
|
||||
if(is_hls){
|
||||
//hls相关信息
|
||||
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, identifier, peer_ip, peer_port);
|
||||
//hls未查找MediaSource
|
||||
attachment._have_find_media_source = false;
|
||||
}
|
||||
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
|
||||
callback(errMsg, cookie);
|
||||
}else{
|
||||
callback(errMsg, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
if (is_hls && emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender)) {
|
||||
//是hls的播放鉴权,拦截之
|
||||
return;
|
||||
}
|
||||
|
||||
//事件未被拦截,则认为是http下载请求
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
|
||||
if (!flag) {
|
||||
//此事件无人监听,我们默认都有权限访问
|
||||
callback("", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送404 Not Found
|
||||
*/
|
||||
static void sendNotFound(const HttpFileManager::invoker &cb) {
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
cb("404 Not Found","text/html",StrCaseMap(),std::make_shared<HttpStringBody>(notFound));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接文件路径
|
||||
*/
|
||||
static string pathCat(const string &a, const string &b){
|
||||
if(a.back() == '/'){
|
||||
return a + b;
|
||||
}
|
||||
return a + '/' + b;
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问文件
|
||||
* @param sender 事件触发者
|
||||
* @param parser http请求
|
||||
* @param mediaInfo http url信息
|
||||
* @param strFile 文件绝对路径
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, const string &strFile, const HttpFileManager::invoker &cb) {
|
||||
bool is_hls = end_of(strFile, kHlsSuffix);
|
||||
bool file_exist = File::is_file(strFile.data());
|
||||
if (!is_hls && !file_exist) {
|
||||
//文件不存在且不是hls,那么直接返回404
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_hls){
|
||||
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
|
||||
}
|
||||
|
||||
weak_ptr<TcpSession> weakSession = sender.shared_from_this();
|
||||
//判断是否有权限访问该文件
|
||||
canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if(!strongSession){
|
||||
//http客户端已经断开,不需要回复
|
||||
return;
|
||||
}
|
||||
if (!errMsg.empty()) {
|
||||
//文件鉴权失败
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
|
||||
StrCaseMap httpHeader;
|
||||
if (cookie) {
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||
if (cookie && file_exist) {
|
||||
cookie->getLock();
|
||||
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
|
||||
if (is_hls) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
|
||||
}
|
||||
}
|
||||
cb(codeOut.data(), HttpFileManager::getContentType(strFile.data()), headerOut, body);
|
||||
};
|
||||
invoker.responseFile(parser.getValues(), httpHeader, strFile);
|
||||
};
|
||||
|
||||
if (!is_hls) {
|
||||
//不是hls,直接回复文件或404
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
} else {
|
||||
//是hls直播,判断是否存在
|
||||
bool have_find_media_src = false;
|
||||
if(cookie){
|
||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
||||
if(!have_find_media_src){
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
||||
}
|
||||
}
|
||||
if(have_find_media_src){
|
||||
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
//hls文件不存在,我们等待其生成并延后回复
|
||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
||||
//hls已经生成或者超时后仍未生成,那么不管怎么样都返回客户端
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSession &sender){
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问文件或文件夹
|
||||
* @param sender 事件触发者
|
||||
* @param parser http请求
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const HttpFileManager::invoker &cb) {
|
||||
auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl();
|
||||
MediaInfo mediaInfo(fullUrl);
|
||||
auto strFile = getFilePath(parser, mediaInfo, sender);
|
||||
//访问的是文件夹
|
||||
if (File::is_dir(strFile.data())) {
|
||||
auto indexFile = searchIndexFile(strFile);
|
||||
if (!indexFile.empty()) {
|
||||
//发现该文件夹下有index文件
|
||||
strFile = pathCat(strFile, indexFile);
|
||||
parser.setUrl(pathCat(parser.Url(), indexFile));
|
||||
accessFile(sender, parser, mediaInfo, strFile, cb);
|
||||
return;
|
||||
}
|
||||
string strMenu;
|
||||
//生成文件夹菜单索引
|
||||
if (!makeFolderMenu(parser.Url(), strFile, strMenu)) {
|
||||
//文件夹不存在
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
}
|
||||
//判断是否有权限访问该目录
|
||||
canAccessPath(sender, parser, mediaInfo, true, [strMenu, cb](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
|
||||
if (!errMsg.empty()) {
|
||||
const_cast<string &>(strMenu) = errMsg;
|
||||
}
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
cb(errMsg.empty() ? "200 OK" : "401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(strMenu));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//访问的是文件
|
||||
accessFile(sender, parser, mediaInfo, strFile, cb);
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////HttpResponseInvokerImp//////////////////////////////////////
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
|
||||
if(_lambad){
|
||||
_lambad(codeOut,headerOut,body);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{
|
||||
this->operator()(codeOut,headerOut,std::make_shared<HttpStringBody>(body));
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
|
||||
_lambad = lambda;
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
|
||||
if(!lambda){
|
||||
_lambad = nullptr;
|
||||
return;
|
||||
}
|
||||
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){
|
||||
string str;
|
||||
if(body && body->remainSize()){
|
||||
str = body->readData(body->remainSize())->toString();
|
||||
}
|
||||
lambda(codeOut,headerOut,str);
|
||||
};
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
const StrCaseMap &responseHeader,
|
||||
const string &filePath) const {
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap&>(responseHeader);
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
|
||||
if (!fp) {
|
||||
//打开文件失败
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
|
||||
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
||||
httpHeader["Content-Type"] = strContentType;
|
||||
(*this)("404 Not Found", httpHeader, notFound);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||
int64_t iRangeStart = 0;
|
||||
int64_t iRangeEnd = 0 ;
|
||||
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
|
||||
|
||||
const char *pcHttpResult = NULL;
|
||||
if (strRange.size() == 0) {
|
||||
//全部下载
|
||||
pcHttpResult = "200 OK";
|
||||
iRangeEnd = fileSize - 1;
|
||||
} else {
|
||||
//分节下载
|
||||
pcHttpResult = "206 Partial Content";
|
||||
iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
||||
iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data());
|
||||
if (iRangeEnd == 0) {
|
||||
iRangeEnd = fileSize - 1;
|
||||
}
|
||||
//分节下载返回Content-Range头
|
||||
httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl);
|
||||
}
|
||||
|
||||
//回复文件
|
||||
HttpBody::Ptr fileBody = std::make_shared<HttpFileBody>(fp, iRangeStart, iRangeEnd - iRangeStart + 1);
|
||||
(*this)(pcHttpResult, httpHeader, fileBody);
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::operator bool(){
|
||||
return _lambad.operator bool();
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
87
src/Http/HttpFileManager.h
Normal file
87
src/Http/HttpFileManager.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_HTTPFILEMANAGER_H
|
||||
#define ZLMEDIAKIT_HTTPFILEMANAGER_H
|
||||
|
||||
#include "HttpBody.h"
|
||||
#include "HttpCookie.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Network/TcpSession.h"
|
||||
#include "Util/function_traits.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HttpResponseInvokerImp{
|
||||
public:
|
||||
typedef std::function<void(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body)> HttpResponseInvokerLambda0;
|
||||
typedef std::function<void(const string &codeOut, const StrCaseMap &headerOut, const string &body)> HttpResponseInvokerLambda1;
|
||||
|
||||
HttpResponseInvokerImp(){}
|
||||
~HttpResponseInvokerImp(){}
|
||||
template<typename C>
|
||||
HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename function_traits<C>::stl_function_type(c)) {}
|
||||
HttpResponseInvokerImp(const HttpResponseInvokerLambda0 &lambda);
|
||||
HttpResponseInvokerImp(const HttpResponseInvokerLambda1 &lambda);
|
||||
|
||||
void operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const;
|
||||
void operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const;
|
||||
void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath) const;
|
||||
operator bool();
|
||||
private:
|
||||
HttpResponseInvokerLambda0 _lambad;
|
||||
};
|
||||
|
||||
/**
|
||||
* 该对象用于控制http静态文件夹服务器的访问权限
|
||||
*/
|
||||
class HttpFileManager {
|
||||
public:
|
||||
typedef function<void(const string &status_code, const string &content_type, const StrCaseMap &responseHeader, const HttpBody::Ptr &body)> invoker;
|
||||
|
||||
/**
|
||||
* 访问文件或文件夹
|
||||
* @param sender 事件触发者
|
||||
* @param parser http请求
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
static void onAccessPath(TcpSession &sender, Parser &parser, const invoker &cb);
|
||||
|
||||
/**
|
||||
* 获取mime值
|
||||
* @param name 文件后缀
|
||||
* @return mime值
|
||||
*/
|
||||
static const string &getContentType(const char *name);
|
||||
private:
|
||||
HttpFileManager() = delete;
|
||||
~HttpFileManager() = delete;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //ZLMEDIAKIT_HTTPFILEMANAGER_H
|
@ -53,6 +53,7 @@ void HttpRequester::onResponseCompleted() {
|
||||
|
||||
void HttpRequester::onDisconnect(const SockException &ex){
|
||||
if(_onResult){
|
||||
const_cast<Parser &>(response()).setContent(_strRecvBody);
|
||||
_onResult(ex,responseStatus(),responseHeader(),_strRecvBody);
|
||||
_onResult = nullptr;
|
||||
}
|
||||
@ -69,5 +70,9 @@ void HttpRequester::clear() {
|
||||
_onResult = nullptr;
|
||||
}
|
||||
|
||||
void HttpRequester::setOnResult(const HttpRequesterResult &onResult) {
|
||||
_onResult = onResult;
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -38,6 +38,7 @@ public:
|
||||
typedef std::function<void(const SockException &ex,const string &status,const HttpHeader &header,const string &strRecvBody)> HttpRequesterResult;
|
||||
HttpRequester();
|
||||
virtual ~HttpRequester();
|
||||
void setOnResult(const HttpRequesterResult &onResult);
|
||||
void startRequester(const string &url,const HttpRequesterResult &onResult,float timeOutSecond = 10);
|
||||
void clear() override ;
|
||||
private:
|
||||
|
@ -35,163 +35,12 @@
|
||||
#include "Common/config.h"
|
||||
#include "strCoding.h"
|
||||
#include "HttpSession.h"
|
||||
#include "Util/File.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Util/SHA1.h"
|
||||
#include "Rtmp/utils.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
static int kHlsCookieSecond = 10 * 60;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kCookiePathKey = "kCookiePathKey";
|
||||
static const string kAccessErrKey = "kAccessErrKey";
|
||||
|
||||
string dateStr() {
|
||||
char buf[64];
|
||||
time_t tt = time(NULL);
|
||||
strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char *HttpSession::get_mime_type(const char *name) {
|
||||
const char *dot;
|
||||
dot = strrchr(name, '.');
|
||||
static HttpSession::KeyValue mapType;
|
||||
static onceToken token([&]() {
|
||||
mapType.emplace(".html", "text/html");
|
||||
mapType.emplace(".htm", "text/html");
|
||||
mapType.emplace(".mp4", "video/mp4");
|
||||
mapType.emplace(".mkv", "video/x-matroska");
|
||||
mapType.emplace(".rmvb", "application/vnd.rn-realmedia");
|
||||
mapType.emplace(".rm", "application/vnd.rn-realmedia");
|
||||
mapType.emplace(".m3u8", "application/vnd.apple.mpegurl");
|
||||
mapType.emplace(".jpg", "image/jpeg");
|
||||
mapType.emplace(".jpeg", "image/jpeg");
|
||||
mapType.emplace(".gif", "image/gif");
|
||||
mapType.emplace(".png", "image/png");
|
||||
mapType.emplace(".ico", "image/x-icon");
|
||||
mapType.emplace(".css", "text/css");
|
||||
mapType.emplace(".js", "application/javascript");
|
||||
mapType.emplace(".au", "audio/basic");
|
||||
mapType.emplace(".wav", "audio/wav");
|
||||
mapType.emplace(".avi", "video/x-msvideo");
|
||||
mapType.emplace(".mov", "video/quicktime");
|
||||
mapType.emplace(".qt", "video/quicktime");
|
||||
mapType.emplace(".mpeg", "video/mpeg");
|
||||
mapType.emplace(".mpe", "video/mpeg");
|
||||
mapType.emplace(".vrml", "model/vrml");
|
||||
mapType.emplace(".wrl", "model/vrml");
|
||||
mapType.emplace(".midi", "audio/midi");
|
||||
mapType.emplace(".mid", "audio/midi");
|
||||
mapType.emplace(".mp3", "audio/mpeg");
|
||||
mapType.emplace(".ogg", "application/ogg");
|
||||
mapType.emplace(".pac", "application/x-ns-proxy-autoconfig");
|
||||
mapType.emplace(".flv", "video/x-flv");
|
||||
}, nullptr);
|
||||
if (!dot) {
|
||||
return "text/plain";
|
||||
}
|
||||
auto it = mapType.find(dot);
|
||||
if (it == mapType.end()) {
|
||||
return "text/plain";
|
||||
}
|
||||
return it->second.data();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
|
||||
if(_lambad){
|
||||
_lambad(codeOut,headerOut,body);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{
|
||||
this->operator()(codeOut,headerOut,std::make_shared<HttpStringBody>(body));
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
|
||||
_lambad = lambda;
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
|
||||
if(!lambda){
|
||||
_lambad = nullptr;
|
||||
return;
|
||||
}
|
||||
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){
|
||||
string str;
|
||||
if(body && body->remainSize()){
|
||||
str = body->readData(body->remainSize())->toString();
|
||||
}
|
||||
lambda(codeOut,headerOut,str);
|
||||
};
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
const StrCaseMap &responseHeader,
|
||||
const string &filePath) const {
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap&>(responseHeader);
|
||||
do {
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (!fp) {
|
||||
//打开文件失败
|
||||
break;
|
||||
}
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||
int64_t iRangeStart = 0;
|
||||
int64_t iRangeEnd = 0 ;
|
||||
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
|
||||
|
||||
const char *pcHttpResult = NULL;
|
||||
if (strRange.size() == 0) {
|
||||
//全部下载
|
||||
pcHttpResult = "200 OK";
|
||||
iRangeEnd = fileSize - 1;
|
||||
} else {
|
||||
//分节下载
|
||||
pcHttpResult = "206 Partial Content";
|
||||
iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
||||
iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data());
|
||||
if (iRangeEnd == 0) {
|
||||
iRangeEnd = fileSize - 1;
|
||||
}
|
||||
//分节下载返回Content-Range头
|
||||
httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl);
|
||||
}
|
||||
|
||||
//回复文件
|
||||
HttpBody::Ptr fileBody = std::make_shared<HttpFileBody>(fp, iRangeStart, iRangeEnd - iRangeStart + 1);
|
||||
(*this)(pcHttpResult, httpHeader, fileBody);
|
||||
return;
|
||||
}while(false);
|
||||
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
|
||||
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
||||
httpHeader["Content-Type"] = strContentType;
|
||||
(*this)("404 Not Found", httpHeader, notFound);
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::operator bool(){
|
||||
return _lambad.operator bool();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
HttpSession::HttpSession(const Socket::Ptr &pSock) : TcpSession(pSock) {
|
||||
TraceP(this);
|
||||
GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
|
||||
@ -206,19 +55,18 @@ HttpSession::~HttpSession() {
|
||||
|
||||
int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) {
|
||||
typedef void (HttpSession::*HttpCMDHandle)(int64_t &);
|
||||
static unordered_map<string, HttpCMDHandle> g_mapCmdIndex;
|
||||
static unordered_map<string, HttpCMDHandle> s_func_map;
|
||||
static onceToken token([]() {
|
||||
g_mapCmdIndex.emplace("GET",&HttpSession::Handle_Req_GET);
|
||||
g_mapCmdIndex.emplace("POST",&HttpSession::Handle_Req_POST);
|
||||
s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
|
||||
s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
|
||||
}, nullptr);
|
||||
|
||||
_parser.Parse(header);
|
||||
urlDecode(_parser);
|
||||
string cmd = _parser.Method();
|
||||
auto it = g_mapCmdIndex.find(cmd);
|
||||
if (it == g_mapCmdIndex.end()) {
|
||||
auto it = s_func_map.find(cmd);
|
||||
if (it == s_func_map.end()) {
|
||||
sendResponse("403 Forbidden", true);
|
||||
shutdown(SockException(Err_shutdown,StrPrinter << "403 Forbidden:" << cmd));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -230,10 +78,6 @@ int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) {
|
||||
auto &fun = it->second;
|
||||
try {
|
||||
(this->*fun)(content_len);
|
||||
}catch (SockException &ex){
|
||||
if(ex){
|
||||
shutdown(ex);
|
||||
}
|
||||
}catch (exception &ex){
|
||||
shutdown(SockException(Err_shutdown,ex.what()));
|
||||
}
|
||||
@ -259,21 +103,18 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||
|
||||
void HttpSession::onError(const SockException& err) {
|
||||
if(_is_flv_stream){
|
||||
uint64_t duration = _ticker.createdTime()/1000;
|
||||
//flv播放器
|
||||
WarnP(this) << "播放器("
|
||||
WarnP(this) << "FLV播放器("
|
||||
<< _mediaInfo._vhost << "/"
|
||||
<< _mediaInfo._app << "/"
|
||||
<< _mediaInfo._streamid
|
||||
<< ")断开:" << err.what();
|
||||
<< ")断开:" << err.what()
|
||||
<< ",耗时(s):" << duration;
|
||||
|
||||
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
|
||||
if(_ui64TotalBytes > iFlowThreshold * 1024){
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport,
|
||||
_mediaInfo,
|
||||
_ui64TotalBytes,
|
||||
_ticker.createdTime()/1000,
|
||||
true,
|
||||
*this);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration , true, getIdentifier(), get_peer_ip(), get_peer_port());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -312,7 +153,7 @@ bool HttpSession::checkWebSocket(){
|
||||
|
||||
auto res_cb = [this,headerOut](){
|
||||
_flv_over_websocket = true;
|
||||
sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr,false);
|
||||
sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr, true);
|
||||
};
|
||||
|
||||
//判断是否为websocket-flv
|
||||
@ -324,12 +165,12 @@ bool HttpSession::checkWebSocket(){
|
||||
//如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接
|
||||
if(!onWebSocketConnect(_parser)){
|
||||
sendResponse("501 Not Implemented",true, nullptr, headerOut);
|
||||
shutdown(SockException(Err_shutdown,"WebSocket server not implemented"));
|
||||
return true;
|
||||
}
|
||||
sendResponse("101 Switching Protocols",false, nullptr,headerOut);
|
||||
return true;
|
||||
}
|
||||
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
||||
bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
@ -350,12 +191,10 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
return false;
|
||||
}
|
||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
||||
|
||||
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
||||
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this,cb](const MediaSource::Ptr &src){
|
||||
MediaSource::findAsync(_mediaInfo,weakSelf.lock(),[weakSelf,bClose,this,cb](const MediaSource::Ptr &src){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
//本对象已经销毁
|
||||
@ -365,9 +204,6 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
if(!rtmp_src){
|
||||
//未找到该流
|
||||
sendNotFound(bClose);
|
||||
if(bClose){
|
||||
shutdown(SockException(Err_shutdown,"flv stream not found"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
//找到流了
|
||||
@ -375,13 +211,12 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
bool authSuccess = err.empty();
|
||||
if(!authSuccess){
|
||||
sendResponse("401 Unauthorized", true, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err));
|
||||
return ;
|
||||
}
|
||||
|
||||
if(!cb) {
|
||||
//找到rtmp源,发送http头,负载后续发送
|
||||
sendResponse("200 OK", false, "video/x-flv",KeyValue(),nullptr,false);
|
||||
sendResponse("200 OK", false, "video/x-flv",KeyValue(),nullptr,true);
|
||||
}else{
|
||||
cb();
|
||||
}
|
||||
@ -421,158 +256,6 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
return true;
|
||||
}
|
||||
|
||||
bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) ;
|
||||
|
||||
static string findIndexFile(const string &dir){
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
if ((pDir = opendir(dir.data())) == NULL) {
|
||||
return "";
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"};
|
||||
if(indexSet.find(pDirent->d_name) != indexSet.end()){
|
||||
closedir(pDir);
|
||||
return pDirent->d_name;
|
||||
}
|
||||
}
|
||||
closedir(pDir);
|
||||
return "";
|
||||
}
|
||||
|
||||
string HttpSession::getClientUid(){
|
||||
//如果http客户端不支持cookie,那么我们可以通过url参数来追踪用户
|
||||
//如果url参数也没有,那么只能通过ip+端口号来追踪用户
|
||||
//追踪用户的目的是为了减少http短链接情况的重复鉴权验证,通过缓存记录鉴权结果,提高性能
|
||||
string uid = _parser.Params();
|
||||
if(uid.empty()){
|
||||
uid = StrPrinter << get_peer_ip() << ":" << get_peer_port();
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
|
||||
//字符串是否以xx结尾
|
||||
static bool end_of(const string &str, const string &substr){
|
||||
auto pos = str.rfind(substr);
|
||||
return pos != string::npos && pos == str.size() - substr.size();
|
||||
};
|
||||
|
||||
//拦截hls的播放请求
|
||||
static bool checkHls(BroadcastHttpAccessArgs){
|
||||
if(!end_of(args._streamid,("/hls.m3u8"))) {
|
||||
//不是hls
|
||||
return false;
|
||||
}
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err,"",kHlsCookieSecond);
|
||||
};
|
||||
|
||||
auto args_copy = args;
|
||||
replace(args_copy._streamid,"/hls.m3u8","");
|
||||
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
|
||||
}
|
||||
|
||||
void HttpSession::canAccessPath(const string &path,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback_in){
|
||||
auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
||||
try {
|
||||
callback_in(errMsg,cookie);
|
||||
}catch (SockException &ex){
|
||||
if(ex){
|
||||
shutdown(ex);
|
||||
}
|
||||
}catch (exception &ex){
|
||||
shutdown(SockException(Err_shutdown,ex.what()));
|
||||
}
|
||||
};
|
||||
|
||||
//获取用户唯一id
|
||||
auto uid = getClientUid();
|
||||
//先根据http头中的cookie字段获取cookie
|
||||
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, _parser.getValues());
|
||||
//如果不是从http头中找到的cookie,我们让http客户端设置下cookie
|
||||
bool cookie_from_header = true;
|
||||
if(!cookie){
|
||||
//客户端请求中无cookie,再根据该用户的用户id获取cookie
|
||||
cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
|
||||
cookie_from_header = false;
|
||||
}
|
||||
|
||||
if(cookie){
|
||||
//找到了cookie,对cookie上锁先
|
||||
auto lck = cookie->getLock();
|
||||
auto accessErr = (*cookie)[kAccessErrKey].get<string>();
|
||||
auto cookiePath = (*cookie)[kCookiePathKey].get<string>();
|
||||
if(path.find(cookiePath) == 0){
|
||||
//上次cookie是限定本目录
|
||||
if(accessErr.empty()){
|
||||
//上次鉴权成功
|
||||
callback("", cookie_from_header ? nullptr : cookie);
|
||||
return;
|
||||
}
|
||||
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
||||
if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
|
||||
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
||||
callback(accessErr, cookie_from_header ? nullptr : cookie);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权
|
||||
HttpCookieManager::Instance().delCookie(cookie);
|
||||
}
|
||||
|
||||
//该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpAccessPathInvoker accessPathInvoker = [weakSelf,callback,uid,path,is_dir] (const string &errMsg,const string &cookie_path_in, int cookieLifeSecond) {
|
||||
HttpServerCookie::Ptr cookie ;
|
||||
if(cookieLifeSecond) {
|
||||
//本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
|
||||
string cookie_path = cookie_path_in;
|
||||
if(cookie_path.empty()){
|
||||
//如果未设置鉴权目录,那么我们采用当前目录
|
||||
cookie_path = is_dir ? path : path.substr(0,path.rfind("/") + 1);
|
||||
}
|
||||
|
||||
cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
|
||||
//对cookie上锁
|
||||
auto lck = cookie->getLock();
|
||||
//记录用户能访问的路径
|
||||
(*cookie)[kCookiePathKey].set<string>(cookie_path);
|
||||
//记录能否访问
|
||||
(*cookie)[kAccessErrKey].set<string>(errMsg);
|
||||
}
|
||||
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//自己已经销毁
|
||||
return;
|
||||
}
|
||||
strongSelf->async([weakSelf,callback,cookie,errMsg]() {
|
||||
//切换到自己线程
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//自己已经销毁
|
||||
return;
|
||||
}
|
||||
callback(errMsg, cookie);
|
||||
});
|
||||
};
|
||||
|
||||
if(checkHls(_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this)){
|
||||
//是hls的播放鉴权,拦截之
|
||||
return;
|
||||
}
|
||||
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this);
|
||||
if(!flag){
|
||||
//此事件无人监听,我们默认都有权限访问
|
||||
callback("", nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
//先看看是否为WebSocket请求
|
||||
@ -596,220 +279,159 @@ void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
//事件未被拦截,则认为是http下载请求
|
||||
auto fullUrl = string(HTTP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl();
|
||||
_mediaInfo.parse(fullUrl);
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
|
||||
/////////////HTTP连接是否需要被关闭////////////////
|
||||
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
GET_CONFIG(string,rootPath,Http::kRootPath);
|
||||
auto strFile = File::absolutePath(enableVhost ? _mediaInfo._vhost + _parser.Url() : _parser.Url(),rootPath);
|
||||
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
||||
|
||||
do{
|
||||
//访问的是文件夹
|
||||
if (strFile.back() == '/' || File::is_dir(strFile.data())) {
|
||||
auto indexFile = findIndexFile(strFile);
|
||||
if(!indexFile.empty()){
|
||||
//发现该文件夹下有index文件
|
||||
strFile = strFile + "/" + indexFile;
|
||||
_parser.setUrl(_parser.Url() + "/" + indexFile);
|
||||
break;
|
||||
}
|
||||
string strMeun;
|
||||
//生成文件夹菜单索引
|
||||
if (!makeMeun(_parser.Url(),strFile,strMeun)) {
|
||||
//文件夹不存在
|
||||
sendNotFound(bClose);
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder");
|
||||
}
|
||||
|
||||
//判断是否有权限访问该目录
|
||||
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
||||
if(!errMsg.empty()){
|
||||
const_cast<string &>(strMeun) = errMsg;
|
||||
}
|
||||
KeyValue headerOut;
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" ,bClose, "text/html", headerOut, std::make_shared<HttpStringBody>(strMeun));
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
|
||||
});
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type,
|
||||
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
}while(0);
|
||||
|
||||
auto parser = _parser;
|
||||
//判断是否有权限访问该文件
|
||||
canAccessPath(_parser.Url(),false,[this,parser,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
||||
if(!errMsg.empty()){
|
||||
KeyValue headerOut;
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
strongSelf->async([weakSelf, bClose, status_code, content_type, responseHeader, body]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
sendResponse("401 Unauthorized" ,bClose, nullptr, headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed");
|
||||
}
|
||||
|
||||
KeyValue httpHeader;
|
||||
if(cookie){
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
|
||||
HttpResponseInvoker invoker = [this,bClose,&strFile](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){
|
||||
sendResponse(codeOut.data(), bClose, get_mime_type(strFile.data()), headerOut, body);
|
||||
};
|
||||
invoker.responseFile(parser.getValues(),httpHeader,strFile);
|
||||
strongSelf->sendResponse(status_code.data(), bClose, content_type.data(), responseHeader, body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) {
|
||||
string strPathPrefix(strFullPath);
|
||||
string last_dir_name;
|
||||
if(strPathPrefix.back() == '/'){
|
||||
strPathPrefix.pop_back();
|
||||
}else{
|
||||
last_dir_name = split(strPathPrefix,"/").back();
|
||||
static string dateStr() {
|
||||
char buf[64];
|
||||
time_t tt = time(NULL);
|
||||
strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (!File::is_dir(strPathPrefix.data())) {
|
||||
class AsyncSenderData {
|
||||
public:
|
||||
friend class AsyncSender;
|
||||
typedef std::shared_ptr<AsyncSenderData> Ptr;
|
||||
AsyncSenderData(const TcpSession::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) {
|
||||
_session = dynamic_pointer_cast<HttpSession>(session);
|
||||
_body = body;
|
||||
_close_when_complete = close_when_complete;
|
||||
}
|
||||
~AsyncSenderData() = default;
|
||||
private:
|
||||
std::weak_ptr<HttpSession> _session;
|
||||
HttpBody::Ptr _body;
|
||||
bool _close_when_complete;
|
||||
bool _read_complete = false;
|
||||
};
|
||||
|
||||
class AsyncSender {
|
||||
public:
|
||||
typedef std::shared_ptr<AsyncSender> Ptr;
|
||||
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
|
||||
if (data->_read_complete) {
|
||||
if (data->_close_when_complete) {
|
||||
//发送完毕需要关闭socket
|
||||
shutdown(data->_session.lock());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
stringstream ss;
|
||||
ss << "<html>\r\n"
|
||||
"<head>\r\n"
|
||||
"<title>文件索引</title>\r\n"
|
||||
"</head>\r\n"
|
||||
"<body>\r\n"
|
||||
"<h1>文件索引:";
|
||||
|
||||
ss << httpPath;
|
||||
ss << "</h1>\r\n";
|
||||
if (httpPath != "/") {
|
||||
ss << "<li><a href=\"";
|
||||
ss << "/";
|
||||
ss << "\">";
|
||||
ss << "根目录";
|
||||
ss << "</a></li>\r\n";
|
||||
|
||||
ss << "<li><a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << "./";
|
||||
}else{
|
||||
ss << "../";
|
||||
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
||||
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
|
||||
auto session = data->_session.lock();
|
||||
if (!session) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
ss << "\">";
|
||||
ss << "上级目录";
|
||||
ss << "</a></li>\r\n";
|
||||
session->async([data, sendBuf]() {
|
||||
auto session = data->_session.lock();
|
||||
if (!session) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
if ((pDir = opendir(strPathPrefix.data())) == NULL) {
|
||||
return false;
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
if (File::is_special_dir(pDirent->d_name)) {
|
||||
continue;
|
||||
}
|
||||
if(pDirent->d_name[0] == '.'){
|
||||
continue;
|
||||
}
|
||||
setFile.emplace(pDirent->d_name);
|
||||
}
|
||||
int i = 0;
|
||||
for(auto &strFile :setFile ){
|
||||
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
||||
bool isDir = File::is_dir(strAbsolutePath.data());
|
||||
ss << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << last_dir_name << "/" << strFile;
|
||||
}else{
|
||||
ss << strFile;
|
||||
}
|
||||
|
||||
if(isDir){
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << strFile;
|
||||
if (isDir) {
|
||||
ss << "/</a></li>\r\n";
|
||||
continue;
|
||||
}
|
||||
//是文件
|
||||
struct stat fileData;
|
||||
if (0 == stat(strAbsolutePath.data(), &fileData)) {
|
||||
auto &fileSize = fileData.st_size;
|
||||
if (fileSize < 1024) {
|
||||
ss << " (" << fileData.st_size << "B)" << endl;
|
||||
} else if (fileSize < 1024 * 1024) {
|
||||
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024.0 << "KB)";
|
||||
} else if (fileSize < 1024 * 1024 * 1024) {
|
||||
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024.0 << "MB)";
|
||||
} else {
|
||||
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024 / 1024.0 << "GB)";
|
||||
}
|
||||
}
|
||||
ss << "</a></li>\r\n";
|
||||
}
|
||||
closedir(pDir);
|
||||
ss << "<ul>\r\n";
|
||||
ss << "</ul>\r\n</body></html>";
|
||||
ss.str().swap(strRet);
|
||||
onRequestData(data, session, sendBuf);
|
||||
}, false);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
|
||||
session->_ticker.resetTime();
|
||||
if (sendBuf && session->send(sendBuf) != -1) {
|
||||
//文件还未读完,还需要继续发送
|
||||
if (!session->isSocketBusy()) {
|
||||
//socket还可写,继续请求数据
|
||||
onSocketFlushed(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//文件写完了
|
||||
data->_read_complete = true;
|
||||
if (!session->isSocketBusy() && data->_close_when_complete) {
|
||||
shutdown(session);
|
||||
}
|
||||
}
|
||||
|
||||
static void shutdown(const std::shared_ptr<HttpSession> &session) {
|
||||
if(session){
|
||||
session->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const string kDate = "Date";
|
||||
static const string kServer = "Server";
|
||||
static const string kConnection = "Connection";
|
||||
static const string kKeepAlive = "Keep-Alive";
|
||||
static const string kContentType = "Content-Type";
|
||||
static const string kContentLength = "Content-Length";
|
||||
static const string kAccessControlAllowOrigin = "Access-Control-Allow-Origin";
|
||||
static const string kAccessControlAllowCredentials = "Access-Control-Allow-Credentials";
|
||||
static const string kServerName = SERVER_NAME;
|
||||
|
||||
void HttpSession::sendResponse(const char *pcStatus,
|
||||
bool bClose,
|
||||
const char *pcContentType,
|
||||
const HttpSession::KeyValue &header,
|
||||
const HttpBody::Ptr &body,
|
||||
bool set_content_len ){
|
||||
|
||||
bool is_http_flv ){
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
||||
|
||||
//body默认为空
|
||||
int64_t size = 0;
|
||||
if (body && body->remainSize()) {
|
||||
//有body,获取body大小
|
||||
size = body->remainSize();
|
||||
if (size >= INT64_MAX) {
|
||||
//不固定长度的body,那么不设置content-length字段
|
||||
size = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(!set_content_len || size == -1){
|
||||
//如果是不定长度body,或者不设置conten-length,
|
||||
//那么一定是Keep-Alive类型
|
||||
if(is_http_flv){
|
||||
//http-flv直播是Keep-Alive类型
|
||||
bClose = false;
|
||||
}else if(size >= INT64_MAX){
|
||||
//不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
|
||||
bClose = true;
|
||||
}
|
||||
|
||||
HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
|
||||
headerOut.emplace("Date", dateStr());
|
||||
headerOut.emplace("Server", SERVER_NAME);
|
||||
headerOut.emplace("Connection", bClose ? "close" : "keep-alive");
|
||||
headerOut.emplace(kDate, dateStr());
|
||||
headerOut.emplace(kServer, kServerName);
|
||||
headerOut.emplace(kConnection, bClose ? "close" : "keep-alive");
|
||||
if(!bClose){
|
||||
headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl);
|
||||
string keepAliveString = "timeout=";
|
||||
keepAliveString += to_string(keepAliveSec);
|
||||
keepAliveString += ", max=100";
|
||||
headerOut.emplace(kKeepAlive,std::move(keepAliveString));
|
||||
}
|
||||
|
||||
if(!_origin.empty()){
|
||||
//设置跨域
|
||||
headerOut.emplace("Access-Control-Allow-Origin",_origin);
|
||||
headerOut.emplace("Access-Control-Allow-Credentials", "true");
|
||||
headerOut.emplace(kAccessControlAllowOrigin,_origin);
|
||||
headerOut.emplace(kAccessControlAllowCredentials, "true");
|
||||
}
|
||||
|
||||
if(set_content_len && size >= 0){
|
||||
//文件长度为定值或者,且不是http-flv强制设置Content-Length
|
||||
headerOut["Content-Length"] = StrPrinter << size << endl;
|
||||
if(!is_http_flv && size >= 0 && size < INT64_MAX){
|
||||
//文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||
headerOut[kContentLength] = to_string(size);
|
||||
}
|
||||
|
||||
if(size && !pcContentType){
|
||||
@ -819,82 +441,48 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
|
||||
if(size && pcContentType){
|
||||
//有body时,设置文件类型
|
||||
auto strContentType = StrPrinter << pcContentType << "; charset=" << charSet << endl;
|
||||
headerOut.emplace("Content-Type",strContentType);
|
||||
string strContentType = pcContentType;
|
||||
strContentType += "; charset=";
|
||||
strContentType += charSet;
|
||||
headerOut.emplace(kContentType,std::move(strContentType));
|
||||
}
|
||||
|
||||
//发送http头
|
||||
_StrPrinter printer;
|
||||
printer << "HTTP/1.1 " << pcStatus << "\r\n";
|
||||
string str;
|
||||
str.reserve(256);
|
||||
str += "HTTP/1.1 " ;
|
||||
str += pcStatus ;
|
||||
str += "\r\n";
|
||||
for (auto &pr : header) {
|
||||
printer << pr.first << ": " << pr.second << "\r\n";
|
||||
str += pr.first ;
|
||||
str += ": ";
|
||||
str += pr.second;
|
||||
str += "\r\n";
|
||||
}
|
||||
|
||||
printer << "\r\n";
|
||||
send(printer << endl);
|
||||
str += "\r\n";
|
||||
send(std::move(str));
|
||||
_ticker.resetTime();
|
||||
|
||||
if(!size){
|
||||
//没有body
|
||||
if(bClose){
|
||||
shutdown(SockException(Err_shutdown,"close connection after send http header completed"));
|
||||
shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << pcStatus));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//发送http body
|
||||
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
|
||||
auto onFlush = [body,bClose,weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
//本对象已经销毁
|
||||
return false;
|
||||
}
|
||||
while(true){
|
||||
//更新超时计时器
|
||||
strongSelf->_ticker.resetTime();
|
||||
//读取文件
|
||||
auto sendBuf = body->readData(sendBufSize);
|
||||
if (!sendBuf) {
|
||||
//文件读完
|
||||
if(strongSelf->isSocketBusy() && bClose){
|
||||
//套接字忙,我们等待触发下一次onFlush事件
|
||||
//待所有数据flush到socket fd再移除onFlush事件监听
|
||||
//标记文件读写完毕
|
||||
return true;
|
||||
}
|
||||
//文件全部flush到socket fd,可以直接关闭socket了
|
||||
break;
|
||||
}
|
||||
|
||||
//文件还未读完
|
||||
if(strongSelf->send(sendBuf) == -1) {
|
||||
//socket已经销毁,不再监听onFlush事件
|
||||
return false;
|
||||
}
|
||||
if(strongSelf->isSocketBusy()){
|
||||
//socket忙,那么停止继续写,等待下一次onFlush事件,然后再读文件写socket
|
||||
return true;
|
||||
}
|
||||
//socket还可写,继续写socket
|
||||
}
|
||||
|
||||
if(bClose) {
|
||||
//最后一次flush事件,文件也发送完毕了,可以关闭socket了
|
||||
strongSelf->shutdown(SockException(Err_shutdown,"close connection after send http body completed"));
|
||||
}
|
||||
//不再监听onFlush事件
|
||||
return false;
|
||||
};
|
||||
|
||||
if(body->remainSize() > sendBufSize){
|
||||
//文件下载提升发送性能
|
||||
setSocketFlags();
|
||||
}
|
||||
onFlush();
|
||||
_sock->setOnFlush(onFlush);
|
||||
|
||||
//发送http body
|
||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(),body,bClose);
|
||||
_sock->setOnFlush([data](){
|
||||
return AsyncSender::onSocketFlushed(data);
|
||||
});
|
||||
AsyncSender::onSocketFlushed(data);
|
||||
}
|
||||
|
||||
string HttpSession::urlDecode(const string &str){
|
||||
@ -917,10 +505,7 @@ void HttpSession::urlDecode(Parser &parser){
|
||||
}
|
||||
|
||||
bool HttpSession::emitHttpEvent(bool doInvoke){
|
||||
///////////////////是否断开本链接///////////////////////
|
||||
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
||||
|
||||
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
/////////////////////异步回复Invoker///////////////////////////////
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpResponseInvoker invoker = [weakSelf,bClose](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){
|
||||
@ -934,13 +519,6 @@ bool HttpSession::emitHttpEvent(bool doInvoke){
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
|
||||
if(codeOut.empty()){
|
||||
//回调提供的参数异常
|
||||
strongSelf->sendNotFound(bClose);
|
||||
return;
|
||||
}
|
||||
|
||||
strongSelf->sendResponse(codeOut.data(), bClose, nullptr, headerOut, body);
|
||||
});
|
||||
};
|
||||
@ -950,17 +528,12 @@ bool HttpSession::emitHttpEvent(bool doInvoke){
|
||||
if(!consumed && doInvoke){
|
||||
//该事件无人消费,所以返回404
|
||||
invoker("404 Not Found",KeyValue(), HttpBody::Ptr());
|
||||
if(bClose){
|
||||
//close类型,回复完毕,关闭连接
|
||||
shutdown(SockException(Err_shutdown,"404 Not Found"));
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
||||
GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize);
|
||||
GET_CONFIG(int,maxReqCnt,Http::kMaxReqCount);
|
||||
|
||||
int64_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
|
||||
|
||||
@ -1000,7 +573,7 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
||||
content_len = -1;
|
||||
auto parserCopy = _parser;
|
||||
std::shared_ptr<uint64_t> recvedContentLen = std::make_shared<uint64_t>(0);
|
||||
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > maxReqCnt);
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
|
||||
_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,uint64_t len){
|
||||
*(recvedContentLen) += len;
|
||||
|
@ -27,46 +27,19 @@
|
||||
#define SRC_HTTP_HTTPSESSION_H_
|
||||
|
||||
#include <functional>
|
||||
#include "Common/config.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Network/TcpSession.h"
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Rtmp/RtmpMediaSource.h"
|
||||
#include "Rtmp/FlvMuxer.h"
|
||||
#include "HttpRequestSplitter.h"
|
||||
#include "WebSocketSplitter.h"
|
||||
#include "HttpCookieManager.h"
|
||||
#include "HttpBody.h"
|
||||
#include "Util/function_traits.h"
|
||||
#include "HttpFileManager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
/**
|
||||
* 该类实现与老代码的兼容适配
|
||||
*/
|
||||
class HttpResponseInvokerImp{
|
||||
public:
|
||||
typedef std::function<void(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body)> HttpResponseInvokerLambda0;
|
||||
typedef std::function<void(const string &codeOut, const StrCaseMap &headerOut, const string &body)> HttpResponseInvokerLambda1;
|
||||
|
||||
HttpResponseInvokerImp(){}
|
||||
~HttpResponseInvokerImp(){}
|
||||
template<typename C>
|
||||
HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename function_traits<C>::stl_function_type(c)) {}
|
||||
HttpResponseInvokerImp(const HttpResponseInvokerLambda0 &lambda);
|
||||
HttpResponseInvokerImp(const HttpResponseInvokerLambda1 &lambda);
|
||||
|
||||
void operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const;
|
||||
void operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const;
|
||||
void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath) const;
|
||||
operator bool();
|
||||
private:
|
||||
HttpResponseInvokerLambda0 _lambad;
|
||||
};
|
||||
|
||||
class HttpSession: public TcpSession,
|
||||
public FlvMuxer,
|
||||
public HttpRequestSplitter,
|
||||
@ -74,7 +47,7 @@ class HttpSession: public TcpSession,
|
||||
public:
|
||||
typedef StrCaseMap KeyValue;
|
||||
typedef HttpResponseInvokerImp HttpResponseInvoker;
|
||||
|
||||
friend class AsyncSender;
|
||||
/**
|
||||
* @param errMsg 如果为空,则代表鉴权通过,否则为错误提示
|
||||
* @param accessPath 运行或禁止访问的根目录
|
||||
@ -88,9 +61,7 @@ public:
|
||||
virtual void onRecv(const Buffer::Ptr &) override;
|
||||
virtual void onError(const SockException &err) override;
|
||||
virtual void onManager() override;
|
||||
|
||||
static string urlDecode(const string &str);
|
||||
static const char* get_mime_type(const char* name);
|
||||
protected:
|
||||
//FlvMuxer override
|
||||
void onWrite(const Buffer::Ptr &data) override ;
|
||||
@ -144,27 +115,7 @@ private:
|
||||
void sendNotFound(bool bClose);
|
||||
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
||||
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
||||
const HttpBody::Ptr &body = nullptr,bool set_content_len = true);
|
||||
/**
|
||||
* 判断http客户端是否有权限访问文件的逻辑步骤
|
||||
*
|
||||
* 1、根据http请求头查找cookie,找到进入步骤3
|
||||
* 2、根据http url参数(如果没有根据ip+端口号)查找cookie,如果还是未找到cookie则进入步骤5
|
||||
* 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
|
||||
* 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
|
||||
* 5、触发kBroadcastHttpAccess事件
|
||||
* @param path 文件或目录
|
||||
* @param is_dir path是否为目录
|
||||
* @param callback 有权限或无权限的回调
|
||||
*/
|
||||
void canAccessPath(const string &path,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback);
|
||||
|
||||
/**
|
||||
* 获取用户唯一识别id
|
||||
* 有url参数返回参数,无参数返回ip+端口号
|
||||
* @return
|
||||
*/
|
||||
string getClientUid();
|
||||
const HttpBody::Ptr &body = nullptr,bool is_http_flv = false);
|
||||
|
||||
//设置socket标志
|
||||
void setSocketFlags();
|
||||
@ -172,7 +123,6 @@ private:
|
||||
string _origin;
|
||||
Parser _parser;
|
||||
Ticker _ticker;
|
||||
uint32_t _iReqCnt = 0;
|
||||
//消耗的总流量
|
||||
uint64_t _ui64TotalBytes = 0;
|
||||
//flv over http
|
||||
|
@ -89,6 +89,7 @@ public:
|
||||
|
||||
HttpWsClient(ClientTypeImp<ClientType,DataType> &delegate) : _delegate(delegate){
|
||||
_Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false)));
|
||||
setPoller(delegate.getPoller());
|
||||
}
|
||||
~HttpWsClient(){}
|
||||
|
||||
@ -302,7 +303,7 @@ private:
|
||||
//拦截websocket数据接收
|
||||
_onRecv = [this](const Buffer::Ptr &pBuf){
|
||||
//解析websocket数据包
|
||||
WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size());
|
||||
this->WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size());
|
||||
};
|
||||
return;
|
||||
}
|
||||
@ -348,21 +349,25 @@ public:
|
||||
/**
|
||||
* 重载startConnect方法,
|
||||
* 目的是替换TcpClient的连接服务器行为,使之先完成WebSocket握手
|
||||
* @param strUrl websocket服务器ip或域名
|
||||
* @param host websocket服务器ip或域名
|
||||
* @param iPort websocket服务器端口
|
||||
* @param fTimeOutSec 超时时间
|
||||
*/
|
||||
void startConnect(const string &strUrl, uint16_t iPort, float fTimeOutSec = 3) override {
|
||||
void startConnect(const string &host, uint16_t iPort, float fTimeOutSec = 3) override {
|
||||
string ws_url;
|
||||
if(useWSS){
|
||||
//加密的ws
|
||||
ws_url = StrPrinter << "wss://" + strUrl << ":" << iPort << "/" ;
|
||||
ws_url = StrPrinter << "wss://" + host << ":" << iPort << "/" ;
|
||||
}else{
|
||||
//明文ws
|
||||
ws_url = StrPrinter << "ws://" + strUrl << ":" << iPort << "/" ;
|
||||
ws_url = StrPrinter << "ws://" + host << ":" << iPort << "/" ;
|
||||
}
|
||||
_wsClient->startWsClient(ws_url,fTimeOutSec);
|
||||
}
|
||||
|
||||
void startWebSocket(const string &ws_url,float fTimeOutSec = 3){
|
||||
_wsClient->startWsClient(ws_url,fTimeOutSec);
|
||||
}
|
||||
private:
|
||||
typename HttpWsClient<ClientType,DataType>::Ptr _wsClient;
|
||||
};
|
||||
|
@ -28,17 +28,82 @@
|
||||
#define ZLMEDIAKIT_WEBSOCKETSESSION_H
|
||||
|
||||
#include "HttpSession.h"
|
||||
#include "Network/TcpServer.h"
|
||||
|
||||
/**
|
||||
* 数据发送拦截器
|
||||
*/
|
||||
class SendInterceptor{
|
||||
public:
|
||||
typedef function<int(const Buffer::Ptr &buf)> onBeforeSendCB;
|
||||
SendInterceptor() = default;
|
||||
virtual ~SendInterceptor() = default;
|
||||
virtual void setOnBeforeSendCB(const onBeforeSendCB &cb) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 该类实现了TcpSession派生类发送数据的截取
|
||||
* 目的是发送业务数据前进行websocket协议的打包
|
||||
*/
|
||||
template <typename TcpSessionType>
|
||||
class TcpSessionTypeImp : public TcpSessionType, public SendInterceptor{
|
||||
public:
|
||||
typedef std::shared_ptr<TcpSessionTypeImp> Ptr;
|
||||
|
||||
TcpSessionTypeImp(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) :
|
||||
_identifier(parent.getIdentifier()), TcpSessionType(pSock) {}
|
||||
|
||||
~TcpSessionTypeImp() {}
|
||||
|
||||
/**
|
||||
* 设置发送数据截取回调函数
|
||||
* @param cb 截取回调函数
|
||||
*/
|
||||
void setOnBeforeSendCB(const onBeforeSendCB &cb) override {
|
||||
_beforeSendCB = cb;
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 重载send函数截取数据
|
||||
* @param buf 需要截取的数据
|
||||
* @return 数据字节数
|
||||
*/
|
||||
int send(const Buffer::Ptr &buf) override {
|
||||
if (_beforeSendCB) {
|
||||
return _beforeSendCB(buf);
|
||||
}
|
||||
return TcpSessionType::send(buf);
|
||||
}
|
||||
|
||||
string getIdentifier() const override {
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
private:
|
||||
onBeforeSendCB _beforeSendCB;
|
||||
string _identifier;
|
||||
};
|
||||
|
||||
template <typename TcpSessionType>
|
||||
class TcpSessionCreator {
|
||||
public:
|
||||
//返回的TcpSession必须派生于SendInterceptor,可以返回null
|
||||
TcpSession::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock){
|
||||
return std::make_shared<TcpSessionTypeImp<TcpSessionType> >(header,parent,pSock);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 通过该模板类可以透明化WebSocket协议,
|
||||
* 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等
|
||||
* @tparam SessionType 业务协议的TcpSession类
|
||||
*/
|
||||
template <class SessionType,class HttpSessionType = HttpSession,WebSocketHeader::Type DataType = WebSocketHeader::TEXT>
|
||||
class WebSocketSession : public HttpSessionType {
|
||||
template<typename Creator, typename HttpSessionType = HttpSession, WebSocketHeader::Type DataType = WebSocketHeader::TEXT>
|
||||
class WebSocketSessionBase : public HttpSessionType {
|
||||
public:
|
||||
WebSocketSession(const Socket::Ptr &pSock) : HttpSessionType(pSock){}
|
||||
virtual ~WebSocketSession(){}
|
||||
WebSocketSessionBase(const Socket::Ptr &pSock) : HttpSessionType(pSock){}
|
||||
virtual ~WebSocketSessionBase(){}
|
||||
|
||||
//收到eof或其他导致脱离TcpServer事件的回调
|
||||
void onError(const SockException &err) override{
|
||||
@ -68,15 +133,19 @@ protected:
|
||||
*/
|
||||
bool onWebSocketConnect(const Parser &header) override{
|
||||
//创建websocket session类
|
||||
_session = std::make_shared<SessionImp>(HttpSessionType::getIdentifier(),HttpSessionType::_sock);
|
||||
_session = _creator(header, *this,HttpSessionType::_sock);
|
||||
if(!_session){
|
||||
//此url不允许创建websocket连接
|
||||
return false;
|
||||
}
|
||||
auto strongServer = _weakServer.lock();
|
||||
if(strongServer){
|
||||
_session->attachServer(*strongServer);
|
||||
}
|
||||
|
||||
//此处截取数据并进行websocket协议打包
|
||||
weak_ptr<WebSocketSession> weakSelf = dynamic_pointer_cast<WebSocketSession>(HttpSessionType::shared_from_this());
|
||||
_session->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf){
|
||||
weak_ptr<WebSocketSessionBase> weakSelf = dynamic_pointer_cast<WebSocketSessionBase>(HttpSessionType::shared_from_this());
|
||||
dynamic_pointer_cast<SendInterceptor>(_session)->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (strongSelf) {
|
||||
WebSocketHeader header;
|
||||
@ -154,50 +223,19 @@ protected:
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||
SocketHelper::send(buffer);
|
||||
}
|
||||
private:
|
||||
typedef function<int(const Buffer::Ptr &buf)> onBeforeSendCB;
|
||||
/**
|
||||
* 该类实现了TcpSession派生类发送数据的截取
|
||||
* 目的是发送业务数据前进行websocket协议的打包
|
||||
*/
|
||||
class SessionImp : public SessionType{
|
||||
public:
|
||||
SessionImp(const string &identifier,const Socket::Ptr &pSock) :
|
||||
_identifier(identifier),SessionType(pSock){}
|
||||
|
||||
~SessionImp(){}
|
||||
|
||||
/**
|
||||
* 设置发送数据截取回调函数
|
||||
* @param cb 截取回调函数
|
||||
*/
|
||||
void setOnBeforeSendCB(const onBeforeSendCB &cb){
|
||||
_beforeSendCB = cb;
|
||||
}
|
||||
protected:
|
||||
/**
|
||||
* 重载send函数截取数据
|
||||
* @param buf 需要截取的数据
|
||||
* @return 数据字节数
|
||||
*/
|
||||
int send(const Buffer::Ptr &buf) override {
|
||||
if(_beforeSendCB){
|
||||
return _beforeSendCB(buf);
|
||||
}
|
||||
return SessionType::send(buf);
|
||||
}
|
||||
string getIdentifier() const override{
|
||||
return _identifier;
|
||||
}
|
||||
private:
|
||||
onBeforeSendCB _beforeSendCB;
|
||||
string _identifier;
|
||||
};
|
||||
private:
|
||||
string _remian_data;
|
||||
weak_ptr<TcpServer> _weakServer;
|
||||
std::shared_ptr<SessionImp> _session;
|
||||
TcpSession::Ptr _session;
|
||||
Creator _creator;
|
||||
};
|
||||
|
||||
|
||||
template<typename TcpSessionType,typename HttpSessionType = HttpSession,WebSocketHeader::Type DataType = WebSocketHeader::TEXT>
|
||||
class WebSocketSession : public WebSocketSessionBase<TcpSessionCreator<TcpSessionType>,HttpSessionType,DataType>{
|
||||
public:
|
||||
WebSocketSession(const Socket::Ptr &pSock) : WebSocketSessionBase<TcpSessionCreator<TcpSessionType>,HttpSessionType,DataType>(pSock){}
|
||||
virtual ~WebSocketSession(){}
|
||||
};
|
||||
|
||||
#endif //ZLMEDIAKIT_WEBSOCKETSESSION_H
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user