Merge pull request #10 from xiongziliang/master

update
This commit is contained in:
baiyfcu 2020-03-06 14:54:21 +08:00 committed by GitHub
commit 87d4a8ed78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
185 changed files with 13115 additions and 4354 deletions

25
.github/workflows/ccpp.yml vendored Normal file
View 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
View File

@ -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
View File

@ -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

View File

@ -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.hWinsock.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()
set(LINK_LIB_LIST zlmediakit zltoolkit)
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" )
#mpegts
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()
#movflvMP4
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()
#rtprtpps/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.hWinsock.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)
#
add_subdirectory(tests)
#
add_subdirectory(server)
if (NOT IOS)
#
add_subdirectory(tests)
#
add_subdirectory(server)
endif ()

View File

@ -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

427
README.md
View File

@ -1,242 +1,257 @@
![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 threadextreme 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 |
| :------------------------------: | :--: | :--: | :--: | :---: |
| 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
| 功能/编码格式 | 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 |
| 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:
| 功能/编码格式 | 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 |
| 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 |
- 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 |
- Server supported:
- 支持的服务器类型列表
| Server | Y/N |
| :-----------------: | :--: |
| RTSP[S] Play Server | Y |
| RTSP[S] Push Server | Y |
| RTMP | Y |
| HTTP[S]/WebSocket[S] | Y |
| 服务类型 | 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 |
| 客户端类型 | Y/N |
| :---------: | :--: |
| RTSP Player | Y |
| RTSP Pusher | Y |
| RTMP Player | Y |
| RTMP Pusher | Y |
| HTTP[S] | Y |
| WebSocket[S] | Y |
## 后续任务
- 完善支持H265
## 编译要求
- 编译器支持C++11GCC4.8/Clang3.3/VC2015或以上
- cmake3.2或以上
## System Requirements
## 编译前必看!!!
- Compiler support c++11GCC4.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 MacOScompiling 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 MacOScompiling 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 MacOScompiling 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 MacOScompiling 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 MacOScompiling on windows is cumber
map<string , PlayerProxy::Ptr> proxyMap;
int i=0;
for(auto url : urlList){
//PlayerProxy构造函数前两个参数分别为应用名app,流idstreamId
//比如说应用为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。
[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit)
这里是测试报告:[benchmark.md](https://github.com/xiongziliang/ZLMediaKit/blob/master/benchmark.md)
[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit)
- 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 group542509000

View File

@ -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++11GCC4.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,流idstreamId
//比如说应用为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
View 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 threadextreme 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++11GCC4.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 MacOScompiling 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 group542509000

28
api/CMakeLists.txt Normal file
View 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
View 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监听端口800
* @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监听端口5540
* @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监听端口19350
* @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
View 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 10
* @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

View 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 0strlen获取
*/
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推流时是否运行转rtsprtsp推流时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
View 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 == 0http会话是完整的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 urlhttp/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
View 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 idcamera
* @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编码profileadts头时用于生产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不知道后续是否还要添加track3
* 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 00
*/
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
View 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
View 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 01
* @param codec_id 0H2641H2652AAC
* @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.01.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.01.0
*/
API_EXPORT float API_CALL mk_player_progress(mk_player ctx);
/**
* rtsp时有效
* @param ctx
* @param track_type 01
*/
API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type);
#ifdef __cplusplus
}
#endif
#endif /* MK_PLAYER_H_ */

View 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
View 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
View 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
View 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 0strlen获取
*/
API_EXPORT void API_CALL mk_tcp_client_send(mk_tcp_client ctx, const char *data, int len);
/**
* 线
* @param ctx
* @param data
* @param len 0strlen获取
*/
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
View 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
View 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
View 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
View 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);
}
});
});
}

View 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
View 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
View 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
View 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);
}

View 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
View 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());
});
});
}

View 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
View 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;
}

View 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
View 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
View 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
View 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
View 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 10
* @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
View 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
View 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 .

View File

@ -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

View File

@ -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)

View File

@ -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
View 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)

View File

@ -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或rtmphook接口(on_publish)中可以覆盖该设置
publishToRtxp=1
#是否默认推流时转换成hlshook接口(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大小

View 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

View 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

View 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

View 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

View File

@ -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()
file(GLOB MediaServer_src_list ./*.cpp ./*.h)
#message(STATUS ${MediaServer_src_list})
add_executable(MediaServer ${MediaServer_src_list})

View File

@ -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;
}
@ -147,11 +156,11 @@ void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSourc
return;
}
if(!bRegist ||
schema != strongSelf->_media_info._schema ||
vhost != strongSelf->_media_info._vhost ||
app != strongSelf->_media_info._app ||
stream != strongSelf->_media_info._streamid){
if (!bRegist ||
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());
}

View File

@ -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;
};

View File

@ -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"
@ -39,75 +46,95 @@
using namespace toolkit;
void Process::run(const string &cmd, const string &log_file_tmp) {
kill(2000);
_pid = fork();
if (_pid < 0) {
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
}
if (_pid == 0) {
//子进程关闭core文件生成
struct rlimit rlim = {0,0};
setrlimit(RLIMIT_CORE, &rlim);
kill(2000);
#ifdef _WIN32
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si)); //结构体初始化;
ZeroMemory(&pi, sizeof(pi));
//在启动子进程时暂时禁用SIGINT、SIGTERM信号
// ignore the SIGINT and SIGTERM
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
LPTSTR lpDir = const_cast<char*>(cmd.data());
string log_file ;
if(log_file_tmp.empty()){
log_file = "/dev/null";
}else{
log_file = StrPrinter << log_file_tmp << "." << getpid();
}
if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){
//下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
int log_fd = -1;
int flags = O_CREAT | O_WRONLY | O_APPEND;
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
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 {
// 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));
}
if (dup2(log_fd, STDERR_FILENO) < 0) {
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
}
// close log fd
::close(log_fd);
}
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
_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());
}
if (_pid == 0) {
//子进程关闭core文件生成
struct rlimit rlim = { 0,0 };
setrlimit(RLIMIT_CORE, &rlim);
// close other fds
// TODO: do in right way.
for (int i = 3; i < 1024; i++) {
::close(i);
}
//在启动子进程时暂时禁用SIGINT、SIGTERM信号
// ignore the SIGINT and SIGTERM
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
auto params = split(cmd, " ");
// memory leak in child process, it's ok.
char **charpv_params = new char *[params.size() + 1];
for (int i = 0; i < (int) params.size(); i++) {
std::string &p = params[i];
charpv_params[i] = (char *) p.data();
}
// EOF: NULL
charpv_params[params.size()] = NULL;
string log_file;
if (log_file_tmp.empty()) {
log_file = "/dev/null";
}
else {
log_file = StrPrinter << log_file_tmp << "." << getpid();
}
// TODO: execv or execvp
auto ret = execv(params[0].c_str(), charpv_params);
if (ret < 0) {
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
}
exit(ret);
}
int log_fd = -1;
int flags = O_CREAT | O_WRONLY | O_APPEND;
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
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 {
// 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));
}
if (dup2(log_fd, STDERR_FILENO) < 0) {
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
}
// close log fd
::close(log_fd);
}
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
InfoL << "start child proces " << _pid;
// close other fds
// TODO: do in right way.
for (int i = 3; i < 1024; i++) {
::close(i);
}
auto params = split(cmd, " ");
// memory leak in child process, it's ok.
char **charpv_params = new char *[params.size() + 1];
for (int i = 0; i < (int)params.size(); i++) {
std::string &p = params[i];
charpv_params[i] = (char *)p.data();
}
// EOF: NULL
charpv_params[params.size()] = NULL;
// TODO: execv or execvp
auto ret = execv(params[0].c_str(), charpv_params);
if (ret < 0) {
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
}
exit(ret);
}
InfoL << "start child proces " << _pid;
#endif // _WIN32
}
/**
*
* @param pid
@ -120,20 +147,30 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
return false;
}
int status = 0;
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
int exit_code = (status & 0xFF00) >> 8;
if(exit_code_ptr){
*exit_code_ptr = (status & 0xFF00) >> 8;
}
if (p < 0) {
WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg();
return false;
}
if (p > 0) {
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
return false;
}
//WarnL << "process is running, pid=" << _pid;
#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) {
*exit_code_ptr = (status & 0xFF00) >> 8;
}
if (p < 0) {
WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg();
return false;
}
if (p > 0) {
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
return false;
}
#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 (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
//进程可能已经退出了
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
return;
}
if(force){
//发送SIGKILL信号后阻塞等待退出

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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,34 +101,54 @@ 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) {
ApiArgsType allArgs;
if(parser["Content-Type"].find("application/x-www-form-urlencoded") == 0){
if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
auto contentArgs = parser.parseArgs(parser.Content());
for (auto &pr : contentArgs) {
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
}
}else if(parser["Content-Type"].find("application/json") == 0){
} else if (parser["Content-Type"].find("application/json") == 0) {
try {
stringstream ss(parser.Content());
Value jsonArgs;
ss >> jsonArgs;
auto keys = jsonArgs.getMemberNames();
for (auto key = keys.begin(); key != keys.end(); ++key){
for (auto key = keys.begin(); key != keys.end(); ++key) {
allArgs[*key] = jsonArgs[*key].asString();
}
}catch (std::exception &ex){
} catch (std::exception &ex) {
WarnL << ex.what();
}
}else if(!parser["Content-Type"].empty()){
} else if (!parser["Content-Type"].empty()) {
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
}

View File

@ -47,5 +47,7 @@ extern const string kPort;
void installWebApi();
void unInstallWebApi();
//配置文件路径
extern string g_ini_file;
#endif //ZLMEDIAKIT_WEBAPI_H

View File

@ -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事件触发机制
* 1http请求头查找cookie3
* 2http url参数(ip+)cookiecookie则进入步骤5
* 2http url参数查找cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
@ -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(){

View File

@ -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());
//加载证书,证书包含公钥和私钥
SSL_Initor::Instance().loadCertificate(ssl_file.data());
//信任某个自签名证书
SSL_Initor::Instance().trustCertificate(ssl_file.data());
//不忽略无效证书证书(例如自签名或过期证书)
SSL_Initor::Instance().ignoreInvalidCertificate(true);
if(!File::is_dir(ssl_file.data())){
//不是文件夹,加载证书,证书包含公钥和私钥
SSL_Initor::Instance().loadCertificate(ssl_file.data());
}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);

View File

@ -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>());

View File

@ -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 0dts
*/
void inputH265(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0);
/**
* adts头的aac帧
* @param pcDataWithAdts adts头的aac帧

View File

@ -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,98 +38,129 @@ 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();
}
_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()){
//Track由未就绪状态转换成就绪状态我们就触发onTrackReady回调
auto it_callback = _trackReadyCallback.find(codec_id);
if(it_callback != _trackReadyCallback.end()){
it_callback->second();
_trackReadyCallback.erase(it_callback);
}
void MediaSink::checkTrackIfReady_l(const Track::Ptr &track){
//Track由未就绪状态转换成就绪状态我们就触发onTrackReady回调
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;
if(!_trackReadyCallback.empty()){
//这是超时强制忽略未准备好的Track
_trackReadyCallback.clear();
//移除未准备好的Track
for(auto it = _track_map.begin() ; it != _track_map.end() ; ){
if(!it->second->ready()){
it = _track_map.erase(it);
continue;
}
++it;
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(!_track_map.empty()){
//最少有一个有效的Track
onAllTrackReady();
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;
}
}
}
bool MediaSink::isAllTrackReady() const {
return _allTrackReady;
void MediaSink::addTrackCompleted(){
{
lock_guard<recursive_mutex> lck(_mtx);
_max_track_size = _track_map.size();
}
checkTrackIfReady(nullptr);
}
Track::Ptr MediaSink::getTrack(TrackType type,bool trackReady) const {
void MediaSink::emitAllTrackReady() {
if (_allTrackReady) {
return;
}
if (!_trackReadyCallback.empty()) {
//这是超时强制忽略未准备好的Track
_trackReadyCallback.clear();
//移除未准备好的Track
for (auto it = _track_map.begin(); it != _track_map.end();) {
if (!it->second->ready()) {
it = _track_map.erase(it);
continue;
}
++it;
}
}
if (!_track_map.empty()) {
//最少有一个有效的Track
_allTrackReady = true;
onAllTrackReady();
}
}
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;
}
return pr.second->ready() ? pr.second : nullptr;
if(trackReady && !pr.second->ready()){
continue;
}
ret.emplace_back(pr.second);
}
return nullptr;
return std::move(ret);
}

View File

@ -38,11 +38,31 @@ using namespace toolkit;
namespace mediakit{
class MediaSinkInterface : public FrameWriterInterface {
public:
typedef std::shared_ptr<MediaSinkInterface> Ptr;
MediaSinkInterface(){};
virtual ~MediaSinkInterface(){};
/**
* trackTrack的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完毕Track3onAllTrackReady
* 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;
};

View File

@ -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,
const function<void(const MediaSource::Ptr &src)> &cb){
MediaSource::~MediaSource() {
unregist();
}
auto src = MediaSource::find(info._schema,
info._vhost,
info._app,
info._streamid,
true);
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);
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,26 +253,28 @@ MediaSource::Ptr MediaSource::find(
vhost = DEFAULT_VHOST;
}
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
MediaSource::Ptr ret;
//查找某一媒体源,找到后返回
searchMedia(schema, vhost, app, id,
[&](SchemaVhostAppStreamMap::iterator &it0 ,
VhostAppStreamMap::iterator &it1,
AppStreamMap::iterator &it2,
StreamMap::iterator &it3){
ret = it3->second.lock();
if(!ret){
//该对象已经销毁
it2->second.erase(it3);
eraseIfEmpty(it0,it1,it2);
return false;
}
return true;
});
{
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
//查找某一媒体源,找到后返回
searchMedia(g_mapMediaSrc, schema, vhost, app, id, [&](SchemaVhostAppStreamMap::iterator &it0,
VhostAppStreamMap::iterator &it1,
AppStreamMap::iterator &it2,
StreamMap::iterator &it3) {
ret = it3->second.lock();
if (!ret) {
//该对象已经销毁
it2->second.erase(it3);
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,43 +289,39 @@ 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() {
//反注册该源
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
return searchMedia(_strSchema, _strVhost, _strApp, _strId, [&](SchemaVhostAppStreamMap::iterator &it0 ,
VhostAppStreamMap::iterator &it1,
AppStreamMap::iterator &it2,
StreamMap::iterator &it3){
auto strongMedia = it3->second.lock();
if(strongMedia && this != strongMedia.get()){
//不是自己,不允许反注册
return false;
}
it2->second.erase(it3);
eraseIfEmpty(it0,it1,it2);
unregisted();
return true;
});
}
void MediaSource::unregisted(){
InfoL << "" << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged,
false,
_strSchema,
_strVhost,
_strApp,
_strId,
*this);
bool ret;
{
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
ret = searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId,
[&](SchemaVhostAppStreamMap::iterator &it0,
VhostAppStreamMap::iterator &it1,
AppStreamMap::iterator &it2,
StreamMap::iterator &it3) {
auto strongMedia = it3->second.lock();
if (strongMedia && this != strongMedia.get()) {
//不是自己,不允许反注册
return false;
}
it2->second.erase(it3);
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
return true;
});
}
if(ret){
InfoL << "" << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
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";
auto schema_pos = url.find("://");
@ -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();

View File

@ -45,7 +45,7 @@ using namespace toolkit;
namespace toolkit{
class TcpSession;
}//namespace toolkit
}// namespace toolkit
namespace mediakit {
@ -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) {
//通知其停止推流
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 */

View File

@ -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;
};

View File

@ -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));
}
};

View File

@ -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
_last_stamp = stamp;
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

View File

@ -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

View File

@ -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

View File

@ -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或rtmphook接口(on_publish)中可以覆盖该设置
extern const string kPublishToRtxp ;
//是否默认推流时转换成hlshook接口(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播放器

View File

@ -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:
* 2aac配置
*/
void onReady(){
if(_cfg.size() < 2){
return;
}
AACFrame aacFrame;
makeAdtsHeader(_cfg,aacFrame);
getAACInfo(aacFrame,_sampleRate,_channel);

View File

@ -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()){
if(frame->prefixSize() >= 7){
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);
}

View File

@ -88,6 +88,10 @@ public:
*/
void inputFrame(const Frame::Ptr &frame) override;
/**
* config包
*/
void makeConfigPacket() override;
private:
void makeAudioConfigPkt();
private:

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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,87 +99,80 @@ 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]);
if(!_gotSpsPps){
if (!_gotSpsPps) {
//尝试从frame中获取sps pps
switch (type){
case H264Frame::NAL_SPS:{
switch (type) {
case H264Frame::NAL_SPS: {
//sps
if(_sps.empty()){
_sps = string(pcData,iLen);
}
}
_sps = string(pcData, iLen);
makeConfigPacket();
break;
case H264Frame::NAL_PPS:{
}
case H264Frame::NAL_PPS: {
//pps
if(_pps.empty()){
_pps = string(pcData,iLen);
}
}
_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();
}
}
switch (type){
case H264Frame::NAL_IDR:
case H264Frame::NAL_B_P:{
if(_lastPacket && _lastPacket->timeStamp != frame->stamp()) {
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
_lastPacket = nullptr;
}
if(!_lastPacket) {
//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);
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
_lastPacket->strBuf.clear();
_lastPacket->strBuf.push_back(flags);
_lastPacket->strBuf.push_back(!is_config);
auto cts = frame->pts() - frame->dts();
cts = htonl(cts);
_lastPacket->strBuf.append((char *)&cts + 1, 3);
_lastPacket->chunkId = CHUNK_VIDEO;
_lastPacket->streamId = STREAM_MEDIA;
_lastPacket->timeStamp = frame->stamp();
_lastPacket->typeId = MSG_VIDEO;
}
auto size = htonl(iLen);
_lastPacket->strBuf.append((char *) &size, 4);
_lastPacket->strBuf.append(pcData, iLen);
_lastPacket->bodySize = _lastPacket->strBuf.size();
}
break;
default:
break;
if(type == H264Frame::NAL_SEI){
return;
}
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
_lastPacket = nullptr;
}
if(!_lastPacket) {
//I or P or B frame
int8_t flags = 7; //h.264
bool is_config = false;
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
_lastPacket->strBuf.clear();
_lastPacket->strBuf.push_back(flags);
_lastPacket->strBuf.push_back(!is_config);
auto cts = frame->pts() - frame->dts();
cts = htonl(cts);
_lastPacket->strBuf.append((char *)&cts + 1, 3);
_lastPacket->chunkId = CHUNK_VIDEO;
_lastPacket->streamId = STREAM_MEDIA;
_lastPacket->timeStamp = frame->dts();
_lastPacket->typeId = MSG_VIDEO;
}
auto size = htonl(iLen);
_lastPacket->strBuf.append((char *) &size, 4);
_lastPacket->strBuf.append(pcData, iLen);
_lastPacket->bodySize = _lastPacket->strBuf.size();
}

View File

@ -90,6 +90,11 @@ public:
* @param frame
*/
void inputFrame(const Frame::Ptr &frame) override;
/**
* config包
*/
void makeConfigPacket() override;
private:
void makeVideoConfigPkt();
private:

View File

@ -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) {
//写入环形缓存
RtpCodec::inputFrame(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]);

View File

@ -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;
};

View File

@ -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);
}
}

View File

@ -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) {
//写入环形缓存
RtpCodec::inputFrame(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;

View File

@ -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;
};

View File

@ -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;

View File

@ -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

View File

@ -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;
};
/**

View File

@ -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{

View File

@ -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;
};

View 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客户端是否有权限访问文件的逻辑步骤
* 1http请求头查找cookie3
* 2http url参数查找cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
*/
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

View 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

View File

@ -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

View File

@ -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:

View File

@ -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();
}
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;
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;
}
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;
}
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
auto session = data->_session.lock();
if (!session) {
//本对象已经销毁
return;
}
session->async([data, sendBuf]() {
auto session = data->_session.lock();
if (!session) {
//本对象已经销毁
return;
}
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;
};
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
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;

View File

@ -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客户端是否有权限访问文件的逻辑步骤
*
* 1http请求头查找cookie3
* 2http url参数(ip+)cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
* @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

View File

@ -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;
};

View File

@ -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,23 +133,27 @@ 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){
if (strongSelf) {
WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = DataType;
header._mask_flag = false;
strongSelf->WebSocketSplitter::encode(header,buf);
strongSelf->WebSocketSplitter::encode(header, buf);
}
return buf->size();
});
@ -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