Push force replace (#2899)

Co-authored-by: Alex <liyu7352@gmail.com>
Co-authored-by: xia-chu <771730766@qq.com>
Co-authored-by: Johnny <hellojinqiang@gmail.com>
Co-authored-by: BackT0TheFuture <10088733+BackT0TheFuture@users.noreply.github.com>
Co-authored-by: ljx0305 <ljx0305@gmail.com>
Co-authored-by: Per-Arne Andersen <per@sysx.no>
Co-authored-by: codeRATny <60806889+codeRATny@users.noreply.github.com>
Co-authored-by: 老衲不出家 <tannzh2018@outlook.com>
Co-authored-by: Kiki <haijuanchen.sun@gmail.com>
Co-authored-by: PioLing <964472638@qq.com>
Co-authored-by: dengjfzh <76604422+dengjfzh@users.noreply.github.com>
Co-authored-by: a-ucontrol <55526028+a-ucontrol@users.noreply.github.com>
Co-authored-by: 百鸣 <94030128+ixingqiao@users.noreply.github.com>
Co-authored-by: fruit Juice <2317232721@qq.com>
Co-authored-by: Luosh <fjlshyyyy@qq.com>
Co-authored-by: tbago <moonzalor@gmail.com>
Co-authored-by: Talus <xiaoxiaochenjian@gmail.com>
Co-authored-by: 朱如洪 <zhu410289616@163.com>
Co-authored-by: pedoc <pedoc@qq.com>
Co-authored-by: XiaoYan Lin <linxiaoyan87@foxmail.com>
Co-authored-by: xiangshengjye <46069012+xiangshengjye@users.noreply.github.com>
Co-authored-by: tjpgt <602950305@qq.com>
Co-authored-by: Nick <joyouswind@gmail.com>
Co-authored-by: yogo-zhangyingzhe <100331270+yogo-zhangyingzhe@users.noreply.github.com>
Co-authored-by: Xiaofeng Wang <wasphin@gmail.com>
Co-authored-by: Dw9 <xweimvp@gmail.com>
Co-authored-by: waken <33921191+mc373906408@users.noreply.github.com>
Co-authored-by: Deepslient <1154547394@qq.com>
This commit is contained in:
xiongguangjie 2023-10-12 17:09:20 +08:00 committed by GitHub
parent b8729dc2a5
commit bc1d31e148
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
208 changed files with 5858 additions and 3106 deletions

View File

@ -1,6 +1,17 @@
name: Docker
on: [push, pull_request]
on:
push:
branches:
- "master"
- "feature/*"
- "release/*"
pull_request:
branches:
- "master"
- "feature/*"
- "release/*"
env:
# Use docker.io for Docker Hub if empty

View File

@ -24,7 +24,11 @@
##############################################################################
# jsoncpp
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json JSONCPP_SRC_LIST)
file(GLOB JSONCPP_SRC_LIST
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/include/json/*.h
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.h)
add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST})
target_compile_options(jsoncpp
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
@ -43,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server")
# TODO:
# movflv MP4
if(ENABLE_MP4)
message(STATUS "ENABLE_MP4 defined")
if (ENABLE_MP4 OR ENABLE_HLS_FMP4)
# MOV
set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov)
aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST)
aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST)
aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST)
add_library(mov STATIC ${MOV_SRC_LIST})
add_library(MediaServer::mov ALIAS mov)
target_compile_definitions(mov
PUBLIC -DENABLE_MP4)
target_compile_options(mov
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_include_directories(mov
PRIVATE
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
PUBLIC
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>")
PRIVATE
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
PUBLIC
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>")
# FLV
set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv)
aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST)
aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST)
aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST)
add_library(flv STATIC ${FLV_SRC_LIST})
add_library(MediaServer::flv ALIAS flv)
target_compile_options(flv
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_include_directories(flv
PRIVATE
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
PUBLIC
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
PRIVATE
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
PUBLIC
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
update_cached_list(MK_LINK_LIBRARIES
MediaServer::flv MediaServer::mov)
update_cached_list(MK_COMPILE_DEFINITIONS
ENABLE_MP4)
endif()
update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov)
if (ENABLE_MP4)
message(STATUS "ENABLE_MP4 defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_MP4)
endif ()
if (ENABLE_HLS_FMP4)
message(STATUS "ENABLE_HLS_FMP4 defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS_FMP4)
endif ()
endif ()
# mpeg ts
if(ENABLE_RTPPROXY OR ENABLE_HLS)
@ -104,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS)
update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg)
if(ENABLE_RTPPROXY)
message(STATUS "ENABLE_RTPPROXY defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY)
endif()
if(ENABLE_HLS)
message(STATUS "ENABLE_HLS defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS)
endif()
endif()

@ -1 +1 @@
Subproject commit 7e40c751659d5c1ec623699732284c12e0a4feb8
Subproject commit b11582c38e8dbbb8d93ca9ce33c9a0b0cd58f59a

16
AUTHORS
View File

@ -70,4 +70,18 @@ WuPeng <wp@zafu.edu.cn>
[ahaooahaz](https://github.com/AHAOAHA)
[TempoTian](https://github.com/TempoTian)
[Derek Liu](https://github.com/yjkhtddx)
[ljx0305](https://github.com/ljx0305)
[ljx0305](https://github.com/ljx0305)
[朱如洪 ](https://github.com/zhu410289616)
[lijin](https://github.com/1461521844lijin)
[PioLing](https://github.com/PioLing)
[BackT0TheFuture](https://github.com/BackT0TheFuture)
[perara](https://github.com/perara)
[codeRATny](https://github.com/codeRATny)
[dengjfzh](https://github.com/dengjfzh)
[百鸣](https://github.com/ixingqiao)
[fruit Juice](https://github.com/xuandu)
[tbago](https://github.com/tbago)
[Luosh](https://github.com/Luosh)
[linxiaoyan87](https://github.com/linxiaoyan)
[waken](https://github.com/mc373906408)
[Deepslient](https://github.com/Deepslient)

View File

@ -39,8 +39,10 @@ option(ENABLE_FAAC "Enable FAAC" OFF)
option(ENABLE_FFMPEG "Enable FFmpeg" OFF)
option(ENABLE_HLS "Enable HLS" ON)
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
option(ENABLE_JEMALLOC_DUMP "Enable jemalloc to dump malloc statistics" OFF)
option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF)
option(ENABLE_MP4 "Enable MP4" ON)
option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON)
option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON)
option(ENABLE_MYSQL "Enable MySQL" OFF)
option(ENABLE_OPENSSL "Enable OpenSSL" ON)
@ -200,8 +202,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
endif()
# mediakit runtime
update_cached_list(MK_LINK_LIBRARIES "")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
update_cached(MK_LINK_LIBRARIES "")
update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
if (DISABLE_REPORT)
update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT)
@ -334,7 +336,11 @@ if(ENABLE_JEMALLOC_STATIC)
if(NOT EXISTS ${DEP_ROOT_DIR})
file(MAKE_DIRECTORY ${DEP_ROOT_DIR})
endif()
if (ENABLE_JEMALLOC_DUMP)
set(ENABLE_JEMALLOC_STAT ON)
else ()
set(ENABLE_JEMALLOC_STAT OFF)
endif ()
include(Jemalloc)
include_directories(SYSTEM ${DEP_ROOT_DIR}/${JEMALLOC_NAME}/include/jemalloc)
link_directories(${DEP_ROOT_DIR}/${JEMALLOC_NAME}/lib)
@ -348,6 +354,12 @@ if(JEMALLOC_FOUND)
message(STATUS "found library: ${JEMALLOC_LIBRARIES}")
include_directories(${JEMALLOC_INCLUDE_DIR})
update_cached_list(MK_LINK_LIBRARIES ${JEMALLOC_LIBRARIES})
add_definitions(-DUSE_JEMALLOC)
message(STATUS "jemalloc will be used to avoid memory fragmentation")
if (ENABLE_JEMALLOC_DUMP)
add_definitions(-DENABLE_JEMALLOC_DUMP)
message(STATUS "jemalloc will save memory usage statistics when the program exits")
endif ()
endif()
# openssl
@ -449,11 +461,6 @@ if(ENABLE_API)
add_subdirectory(api)
endif()
# IOS
if(IOS)
return()
endif()
##############################################################################
if(ENABLE_PLAYER AND ENABLE_FFMPEG)
@ -461,13 +468,20 @@ if(ENABLE_PLAYER AND ENABLE_FFMPEG)
endif()
#MediaServer
add_subdirectory(server)
if(ENABLE_SERVER)
add_subdirectory(server)
endif()
# Android add_subdirectory
if(ENABLE_SERVER_LIB)
if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE)
set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE)
endif()
# IOS
if(IOS)
return()
endif()
#cppdemo
if (ENABLE_TESTS)
add_subdirectory(tests)

View File

@ -1,5 +1,7 @@
![logo](https://raw.githubusercontent.com/ZLMediaKit/ZLMediaKit/master/www/logo.png)
简体中文 | [English](./README_en.md)
# 一个基于C++11的高性能运营级流媒体服务框架
[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ZLMediaKit/ZLMediaKit/blob/master/LICENSE)
@ -44,7 +46,7 @@
## 功能清单
### 功能一览
<img width="800" alt="功能一览" src="https://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png">
<img width="800" alt="功能一览" src="https://github.com/ZLMediaKit/ZLMediaKit/assets/11495632/481ea769-5b27-495e-bf7d-31191e6af9d2">
- RTSP[S]
- RTSP[S] 服务器支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
@ -61,14 +63,16 @@
- RTMP[S] 发布服务器,支持录制发布流
- RTMP[S] 播放器支持RTMP代理支持生成静音音频
- RTMP[S] 推流客户端
- 支持http[s]-flv直播
- 支持http[s]-flv直播服务器
- 支持http[s]-flv直播播放器
- 支持websocket-flv直播
- 支持H264/H265/AAC/G711/OPUS编码其他编码能转发但不能转协议
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
- 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81)
- 支持[enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp)
- HLS
- 支持HLS文件生成自带HTTP文件服务器
- 支持HLS文件(mpegts/fmp4)生成自带HTTP文件服务器
- 通过cookie追踪技术可以模拟HLS播放为长连接可以实现HLS按需拉流、播放统计等业务
- 支持HLS播发器支持拉流HLS转rtsp/rtmp/mp4
- 支持H264/H265/AAC/G711/OPUS编码
@ -99,6 +103,7 @@
- 支持es/ps/ts/ehome rtp推流
- 支持es/ps rtp转推
- 支持GB28181主动拉流模式
- 支持双向语音对讲
- MP4点播与录制
- 支持录制为FLV/HLS/MP4
@ -119,6 +124,7 @@
- 支持datachannel
- 支持webrtc over tcp模式
- 优秀的nack、jitter buffer算法, 抗丢包能力卓越
- 支持whip/whep协议
- [SRT支持](./srt/srt.md)
- 其他
- 支持丰富的restful api以及web hook事件
@ -163,30 +169,36 @@ bash build_docker_images.sh
```
## 合作项目
- 视频管理平台
- [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台
- [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台
- [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器
- [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器
- [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器
- 播放器
- [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器支持本项目多种专属协议
- [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器
- [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器
- [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器
- 可视化管理网站
- [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR)
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
- 流媒体管理平台
- [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro)
- [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream)
- [Go实现的GB28181服务器](https://github.com/panjjo/gosip)
- [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
- [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome)
- 客户端
- [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen)
- WEB管理网站
- [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放
- SDK
- [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库
- [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk
- 其他项目(已停止更新)
- [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
- [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI)
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
- [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo)
- [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi)
- [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk)
- 播放器
- [基于wasm支持H265的播放器](https://github.com/numberwolf/h265web.js)
- [基于MSE的websocket-fmp4播放器](https://github.com/v354412101/wsPlayer)
- [全国产webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
## 授权协议
@ -198,9 +210,12 @@ bash build_docker_images.sh
## 联系方式
- 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程否则恕不邮件答复)
- QQ群两个qq群已满员(共4000人)后续将不再新建qq群用户可加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364)提问以支持本项目。
- 关注微信公众号:
- 请关注微信公众号获取最新消息推送:
<img src=https://user-images.githubusercontent.com/11495632/232451702-4c50bc72-84d8-4c94-af2b-57290088ba7a.png width=15% />
- 也可以自愿有偿加入知识星球咨询和获取资料:
<img src= https://user-images.githubusercontent.com/11495632/231946329-aa8517b0-3cf5-49cf-8c75-a93ed58cb9d2.png width=30% />
## 怎么提问?
@ -208,9 +223,7 @@ bash build_docker_images.sh
- 1、仔细看下readme、wiki如果有必要可以查看下issue.
- 2、如果您的问题还没解决可以提issue.
- 3、有些问题如果不具备参考性的无需在issue提的可以在qq群提.
- 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEQQ%E7%A7%81%E8%81%8A%E5%92%A8%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9F)).
- 5、如果需要获取更及时贴心的技术支持可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364).
- 3、如果需要获取更及时贴心的技术支持可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364).
## 特别感谢
@ -302,6 +315,24 @@ bash build_docker_images.sh
[TempoTian](https://github.com/TempoTian)
[Derek Liu](https://github.com/yjkhtddx)
[ljx0305](https://github.com/ljx0305)
[朱如洪 ](https://github.com/zhu410289616)
[lijin](https://github.com/1461521844lijin)
[PioLing](https://github.com/PioLing)
[BackT0TheFuture](https://github.com/BackT0TheFuture)
[perara](https://github.com/perara)
[codeRATny](https://github.com/codeRATny)
[dengjfzh](https://github.com/dengjfzh)
[百鸣](https://github.com/ixingqiao)
[fruit Juice](https://github.com/xuandu)
[tbago](https://github.com/tbago)
[Luosh](https://github.com/Luosh)
[linxiaoyan87](https://github.com/linxiaoyan)
[waken](https://github.com/mc373906408)
[Deepslient](https://github.com/Deepslient)
同时感谢JetBrains对开源项目的支持本项目使用CLion开发与调试
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
## 使用案例

View File

@ -1,138 +1,206 @@
![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/www/logo.png)
# A lightweight ,high performance and stable stream server and client framework based on C++11.
[简体中文](./README.md) | English
# An high-performance, enterprise-level streaming media service framework based on C++11.
[![license](http://img.shields.io/badge/license-MIT-green.svg)](https://github.com/xia-chu/ZLMediaKit/blob/master/LICENSE)
[![C++](https://img.shields.io/badge/language-c++-red.svg)](https://en.cppreference.com/)
[![platform](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/xia-chu/ZLMediaKit)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/xia-chu/ZLMediaKit/pulls)
[![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ZLMediaKit/ZLMediaKit/blob/master/LICENSE)
[![](https://img.shields.io/badge/language-c++-red.svg)](https://en.cppreference.com/)
[![](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
[![](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/ZLMediaKit/ZLMediaKit/pulls)
## 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/HTTP-TS/WebSocket-TS/HTTP-fMP4/Websocket-fMP4/MP4/WebRTC`),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.
[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/android.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/linux.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/macos.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/windows.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
## Features
[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/docker.yml/badge.svg)](https://hub.docker.com/r/zlmediakit/zlmediakit/tags)
[![](https://img.shields.io/docker/pulls/zlmediakit/zlmediakit)](https://hub.docker.com/r/zlmediakit/zlmediakit/tags)
## Project Features
- Developed with C++11, avoiding the use of raw pointers, providing stable and reliable code with superior performance.
- Supports multiple protocols (RTSP/RTMP/HLS/HTTP-FLV/WebSocket-FLV/GB28181/HTTP-TS/WebSocket-TS/HTTP-fMP4/WebSocket-fMP4/MP4/WebRTC), and protocol conversion.
- Developed with multiplexing/multithreading/asynchronous network IO models, providing excellent concurrency performance and supporting massive client connections.
- The code has undergone extensive stability and performance testing, and has been extensively used in production environments.
- Supports all major platforms, including linux, macos, ios, android, and windows.
- Supports multiple instruction set platforms, such as x86, arm, risc-v, mips, Loongson, and Shenwei.
- Provides ultra-fast startup, extremely low latency (within 500 milliseconds, and can be as low as 100 milliseconds), and excellent user experience.
- Provides a comprehensive standard [C API](https://github.com/ZLMediaKit/ZLMediaKit/tree/master/api/include) that can be used as an SDK or called by other languages.
- Provides a complete [MediaServer](https://github.com/ZLMediaKit/ZLMediaKit/tree/master/server) server, which can be deployed directly as a commercial server without additional development.
- Provides a complete [restful api](https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API) and [web hook](https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API), supporting rich business logic.
- Bridges the video surveillance protocol stack and the live streaming protocol stack, and provides comprehensive support for RTSP/RTMP.
- Fully supports H265/H264/AAC/G711/OPUS.
- Provides complete functions, including clustering, on-demand protocol conversion, on-demand push/pull streams, playback before publishing, and continuous publishing after disconnection.
- Provides ultimate performance, supporting 10W-level players on a single machine and 100Gb/s-level IO bandwidth capability.
- Provides ultimate user experience with [exclusive features](https://github.com/ZLMediaKit/ZLMediaKit/wiki/ZLMediakit%E7%8B%AC%E5%AE%B6%E7%89%B9%E6%80%A7%E4%BB%8B%E7%BB%8D).
- [Who is using zlmediakit?](https://github.com/ZLMediaKit/ZLMediaKit/issues/511)
- Fully supports IPv6 networks.
## Project Positioning
- Cross-platform streaming media solution for mobile and embedded systems.
- Commercial-grade streaming media server.
- Network programming secondary development SDK.
## Feature List
### Overview of Features
<img width="800" alt="Overview of Features" src="https://github.com/ZLMediaKit/ZLMediaKit/assets/11495632/481ea769-5b27-495e-bf7d-31191e6af9d2">
- RTSP[S]
- RTSP[S] server,support rtsp push.
- RTSP[S] player and pusher.
- RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` .
- Basic/Digest/Url Authentication.
- H265/H264/AAC/G711/OPUS codec.
- Recorded as mp4.
- Vod of mp4.
- RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show
- RTSP[S] player, supports RTSP proxy, supports generating silent audio
- RTSP[S] push client and server
- Supports four RTP transmission modes: `rtp over udp` `rtp over tcp` `rtp over http` `rtp multicast`
- Server/client fully supports Basic/Digest authentication, asynchronous configurable authentication interface
- Supports H265 encoding
- The server supports RTSP pushing (including `rtp over udp` and `rtp over tcp`)
- Supports H264/H265/AAC/G711/OPUS/MJPEG encoding. Other encodings can be forwarded but cannot be converted to protocol
- RTMP[S]
- RTMP[S] server,support player and pusher.
- RTMP[S] player and pusher.
- Support HTTP-FLV/WebSocket-FLV sever.
- H265/H264/AAC/G711/OPUS codec.
- Recorded as flv or mp4.
- Vod of mp4.
- support [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
- RTMP[S] playback server, supports RTSP/MP4/HLS to RTMP conversion
- RTMP[S] publishing server, supports recording and publishing streams
- RTMP[S] player, supports RTMP proxy, supports generating silent audio
- RTMP[S] push client
- Supports http[s]-flv live streaming server
- Supports http[s]-flv live streaming player
- Supports websocket-flv live streaming
- Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol
- Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
- Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81)
- Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp)
- HLS
- RTSP RTMP can be converted into HLS,built-in HTTP server.
- Play authentication based on cookie.
- Support HLS player, support streaming HLS proxy to RTSP / RTMP / MP4.
- Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server
- Through cookie tracking technology, it can simulate HLS playback as a long connection, which can achieve HLS on-demand pulling, playback statistics, and other businesses
- Supports HLS player and can pull HLS to rtsp/rtmp/mp4
- Supports H264/H265/AAC/G711/OPUS encoding
- TS
- Support HTTP-TS/WebSocket-TS sever.
- Supports http[s]-ts live streaming
- Supports ws[s]-ts live streaming
- Supports H264/H265/AAC/G711/OPUS encoding
- fMP4
- Support HTTP-fMP4/WebSocket-fMP4 sever.
- Supports http[s]-fmp4 live streaming
- Supports ws[s]-fmp4 live streaming
- Supports H264/H265/AAC/G711/OPUS/MJPEG encoding
- 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.
- WebRTC(experiential)
- Support webrtc push stream and transfer to other protocols
- Support webrtc play, support other protocol to webrtc
- Support simulcast
- Support rtx/nack
- Support transport-cc rtcp/rtp ext
- [SRT support](./srt/srt_en.md)
- HTTP[S] and WebSocket
- The server supports `directory index generation`, `file download`, `form submission requests`
- The client provides `file downloader (supports resume breakpoint)`, `interface requestor`, `file uploader`
- Complete HTTP API server, which can be used as a web backend development framework
- Supports cross-domain access
- Supports http client/server cookie
- Supports WebSocket server and client
- Supports http file access authentication
- GB28181 and RTP Streaming
- Supports UDP/TCP RTP (PS/TS/ES) streaming server, which can be converted to RTSP/RTMP/HLS and other protocols
- Supports RTSP/RTMP/HLS and other protocol conversion to RTP streaming client, supports TCP/UDP mode, provides corresponding RESTful API, supports active and passive modes
- Supports H264/H265/AAC/G711/OPUS encoding
- Supports ES/PS/TS/EHOME RTP streaming
- Supports ES/PS RTP forwarding
- Supports GB28181 active pull mode
- Supports two-way voice intercom
- MP4 VOD and Recording
- Supports recording as FLV/HLS/MP4
- Supports MP4 file playback for RTSP/RTMP/HTTP-FLV/WS-FLV, supports seek
- Supports H264/H265/AAC/G711/OPUS encoding
- WebRTC
- Supports WebRTC streaming and conversion to other protocols
- Supports WebRTC playback and conversion from other protocols to WebRTC
- Supports two-way echo testing
- Supports simulcast streaming
- Supports uplink and downlink RTX/NACK packet loss retransmission
- **Supports single-port, multi-threaded, and client network connection migration (unique in the open source community)**.
- Supports TWCC RTCP dynamic rate control
- Supports REMB/PLI/SR/RR RTCP
- Supports RTP extension parsing
- Supports GOP buffer and instant WebRTC playback
- Supports data channels
- Supports WebRTC over TCP mode
- Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance
- Supports WHIP/WHEP protocols
- [SRT support](./srt/srt.md)
- 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.
- Support TS / PS streaming push through RTP,and it can be converted to RTSP / RTMP / HLS / FLV.
- Support real-time online screenshot http api.
- Supports rich RESTful APIs and webhook events
- Supports simple Telnet debugging
- Supports hot reloading of configuration files
- Supports traffic statistics, stream authentication, and other events
- Supports virtual hosts for isolating different domain names
- Supports on-demand streaming and automatic shutdown of streams with no viewers
- Supports pre-play before streaming to increase the rate of timely stream openings
- Provides a complete and powerful C API SDK
- Supports FFmpeg stream proxy for any format
- Supports HTTP API for real-time screenshot generation and return
- Supports on-demand demultiplexing and protocol conversion, reducing CPU usage by only enabling it when someone is watching
- Supports cluster deployment in traceable mode, with RTSP/RTMP/HLS/HTTP-TS support for traceable mode and HLS support for edge stations and multiple sources for source stations (using round-robin tracing)
- Can reconnect to streaming after abnormal disconnection in RTSP/RTMP/WebRTC pushing within a timeout period, with no impact on the player.
## 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.
- Compiler with c++11 support, such as GCC 4.8+, Clang 3.3+, or VC2015+.
- CMake 3.1+.
- Linux (32-bit and 64-bit).
- Apple macOS (32-bit and 64-bit).
- Any hardware with x86, x86_64, ARM, or 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.
It is recommended to compile on Ubuntu or macOS. Compiling on Windows is cumbersome, and some features are not compiled by default.
### Before build
- **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.You can do it like this:**
### Before Building
- **You must use Git to clone the complete code. Do not download the source code by downloading the ZIP package. Otherwise, the submodule code will not be downloaded by default. You can do it like this:**
```
git clone https://github.com/xia-chu/ZLMediaKit.git
cd ZLMediaKit
git submodule update --init
```
### Build on linux
### Building on Linux
- My environment
- Ubuntu16.04 64 bit and gcc5.4
- cmake 3.5.1
- My Environment
- Ubuntu 16.04 (64-bit) with GCC 5.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,
# If it is on CentOS 6.x, you need to install a newer version of GCC and CMake first,
# and then compile manually according to the "build_for_linux.sh" script.
# If it is on a newer version of a system such as Ubuntu or Debian,
# step 4 can be manipulated directly.
# 1、Install GCC5.2 (this step can be skipped if the GCC version is higher than 4.7)
# 1. Install GCC 5.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
# 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 to download the CMake source file manually
cd cmake-3.10.0-rc4
./configure
make -j4
sudo make install
# 3、Switch to high version GCC
# 3. Switch to a higher version of GCC.
scl enable devtoolset-4 bash
# 4、build
# 4. Build.
cd ZLMediaKit
./build_for_linux.sh
```
### Build on macOS
### Building on macOS
- My environment
- macOS Sierra(10.12.1) + xcode8.3.1
- Homebrew 1.1.3
- cmake 3.8.0
- My Environment
- macOS Sierra (10.12.1) with Xcode 8.3.1.
- Homebrew 1.1.3.
- CMake 3.8.0.
- Guidance
```
@ -140,7 +208,7 @@ git submodule update --init
./build_for_mac.sh
```
### Build on iOS
### Building on iOS
- You can generate Xcode projects and recompile them , [learn more](https://github.com/leetal/ios-cmake):
```
@ -152,15 +220,16 @@ git submodule update --init
```
### Build on Android
### Building on Android
Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project.
Now you can open the Android Studio project in the `Android` folder. This is an `AAR` library and demo project.
- My environment
- macOS Sierra(10.12.1) + xcode8.3.1
- macOS Sierra (10.12.1) + Xcode 8.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)
- CMake 3.8.0
- [Android NDK r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip)
- Guidance
```
@ -168,23 +237,25 @@ git submodule update --init
export ANDROID_NDK_ROOT=/path/to/ndk
./build_for_android.sh
```
### Build on Windows
### Building 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)
- 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. Enter the ZLMediaKit directory and execute `git submodule update --init` to download the code for ZLToolKit.
2. Open the project with CMake GUI and generate the Visual Studio project file.
3. Find the project file (ZLMediaKit.sln), double-click to open it with VS2017.
4. Choose to compile the Release version. Find the target file and run the test cases.
```
## Usage
- As server
- As a server
```cpp
TcpServer::Ptr rtspSrv(new TcpServer());
TcpServer::Ptr rtmpSrv(new TcpServer());
@ -197,7 +268,7 @@ git submodule update --init
httpsSrv->start<HttpsSession>(mINI::Instance()[Config::Http::kSSLPort]);
```
- As player
- As a player
```cpp
MediaPlayer::Ptr player(new MediaPlayer());
weak_ptr<MediaPlayer> weakPlayer = player;
@ -210,7 +281,7 @@ git submodule update --init
auto viedoTrack = strongPlayer->getTrack(TrackVideo);
if (!viedoTrack) {
WarnL << "none video Track!";
WarnL << "No video Track!";
return;
}
viedoTrack->addDelegate([](const Frame::Ptr &frame) {
@ -222,14 +293,13 @@ git submodule update --init
ErrorL << "OnShutdown:" << ex.what();
});
//rtp transport over tcp
//RTP transport over TCP
(*player)[Client::kRtpType] = Rtsp::RTP_TCP;
player->play("rtsp://admin:jzan123456@192.168.0.122/");
```
- As proxy server
- As a proxy server
```cpp
//support rtmp and rtsp url
//just support H264+AAC
//Support RTMP and RTSP URLs, but only H264 + AAC codec is supported
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;
@ -241,7 +311,7 @@ git submodule update --init
}
```
- As puser
- As a pusher
```cpp
PlayerProxy::Ptr player(new PlayerProxy("app","stream"));
player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks");
@ -255,19 +325,179 @@ git submodule update --init
```
## Docker Image
You can pull a pre-built docker image from Docker Hub and run with
You can download the pre-compiled image from Docker Hub and start it:
```bash
#This image is pushed by the GitHub continuous integration automatic compilation to keep up with the latest code (master branch)
docker run -id -p 1935:1935 -p 8080:80 -p 8443:443 -p 8554:554 -p 10000:10000 -p 10000:10000/udp -p 8000:8000/udp -p 9000:9000/udp zlmediakit/zlmediakit:master
```
Dockerfile is also supplied to build images on Ubuntu 16.04
You can also compile the image based on the Dockerfile:
```bash
cd docker
docker build -t zlmediakit .
bash build_docker_images.sh
```
## Contact
- Email<1213642868@qq.com>
- QQ chat group542509000
## Collaborative Projects
- Visual management website
- [The latest web project with front-end and back-end separation, supporting webrtc playback](https://github.com/langmansh/AKStreamNVR)
- [Management web site based on ZLMediaKit master branch](https://gitee.com/kkkkk5G/MediaServerUI)
- [Management web site based on ZLMediaKit branch](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
- [A very beautiful visual background management system](https://github.com/MingZhuLiu/ZLMediaServerManagent)
- Media management platform
- [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro)
- [Powerful media control and management interface platform, supporting GB28181](https://github.com/chatop2020/AKStream)
- [GB28181 server implemented in C++](https://github.com/any12345com/BXC_SipServer)
- [GB28181 server implemented in Go](https://github.com/panjjo/gosip)
- [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http)
- [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome)
- Client
- [Complete C# wrapper library for c sdk](https://github.com/malegend/ZLMediaKit.Autogen)
- [Push client implemented based on C SDK](https://github.com/hctym1995/ZLM_ApiDemo)
- [Http API and Hook in C#](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi)
- [RESTful client in DotNetCore](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk)
- Player
- [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js)
- [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer)
- [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
- [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player)
## License
The self-owned code of this project is licensed under the permissive MIT License and can be freely applied to commercial and non-commercial projects while retaining copyright information.
However, this project also uses some scattered [open source code](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%BB%A3%E7%A0%81%E4%BE%9D%E8%B5%96%E4%B8%8E%E7%89%88%E6%9D%83%E5%A3%B0%E6%98%8E) , please replace or remove it for commercial use.
Any commercial disputes or infringement caused by using this project have nothing to do with the project and developers and shall be at your own legal risk.
When using the code of this project, the license agreement should also indicate the license of the third-party libraries that this project depends on.
## Contact Information
- Email: <1213642868@qq.com> (For project-related or streaming media-related questions, please follow the issue process. Otherwise, we will not reply to emails.)
- QQ groups: Both QQ groups with a total of 4000 members are full. We will not create new QQ groups in the future. Users can join the [Knowledge Planet](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364) to ask questions and support this project.
- Follow WeChat Official Account:
<img src=https://user-images.githubusercontent.com/11495632/232451702-4c50bc72-84d8-4c94-af2b-57290088ba7a.png width=15% />
## How to Ask Questions?
If you have any questions about the project, we recommend that you:
- 1. Carefully read the readme and wiki. If necessary, you can also check the issues.
- 2. If your question has not been resolved, you can raise an issue.
- 3. Some questions may not be suitable for issues, but can be raised in QQ groups.
- 4. We generally do not accept free technical consulting and support via QQ private chat. ([Why we don't encourage QQ private chat](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEQQ%E7%A7%81%E8%81%8A%E5%92%A8%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9F)).
- 5. If you need more timely and thoughtful technical support, you can join the [Knowledge Planet](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364) for a fee.
## Special Thanks
This project uses the [media-server](https://github.com/ireader/media-server) library developed by [Lao Chen](https://github.com/ireader). The reuse and de-multiplexing of ts/fmp4/mp4/ps container formats in this project depend on the media-server library. Lao Chen has provided invaluable help and support multiple times in implementing many functions of this project, and we would like to express our sincere gratitude to him!
## Acknowledgments
Thanks to all those who have supported this project in various ways, including but not limited to code contributions, problem feedback, and donations. The following list is not in any particular order:
[老陈](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)
[好心情](mailto:409257224@qq.com)
[浮沉](https://github.com/MingZhuLiu)
[Xiaofeng Wang](https://github.com/wasphin)
[doodoocoder](https://github.com/doodoocoder)
[qingci](https://github.com/Colibrow)
[swwheihei](https://github.com/swwheihei)
[KKKKK5G](https://gitee.com/kkkkk5G)
[Zhou Weimin](mailto:zhouweimin@supremind.com)
[Jim Jin](https://github.com/jim-king-2000)
[西瓜丶](mailto:392293307@qq.com)
[MingZhuLiu](https://github.com/MingZhuLiu)
[chengxiaosheng](https://github.com/chengxiaosheng)
[big panda](mailto:2381267071@qq.com)
[tanningzhong](https://github.com/tanningzhong)
[hctym1995](https://github.com/hctym1995)
[hewenyuan](https://gitee.com/kingyuanyuan)
[sunhui](mailto:sunhui200475@163.com)
[mirs](mailto:fangpengcheng@bilibili.com)
[Kevin Cheng](mailto:kevin__cheng@outlook.com)
[Liu Jiang](mailto:root@oopy.org)
[along](https://github.com/alongl)
[qingci](mailto:xpy66swsry@gmail.com)
[lyg1949](mailto:zh.ghlong@qq.com)
[zhlong](mailto:zh.ghlong@qq.com)
[大裤衩](mailto:3503207480@qq.com)
[droid.chow](mailto:droid.chow@gmail.com)
[陈晓林](https://github.com/musicwood)
[CharleyWangHZ](https://github.com/CharleyWangHZ)
[Johnny](https://github.com/johzzy)
[DoubleX69](https://github.com/DoubleX69)
[lawrencehj](https://github.com/lawrencehj)
[yangkun](mailto:xyyangkun@163.com)
[Xinghua Zhao](mailto:holychaossword@hotmail.com)
[hejilin](https://github.com/brokensword2018)
[rqb500](https://github.com/rqb500)
[Alex](https://github.com/alexliyu7352)
[Dw9](https://github.com/Dw9)
[明月惊鹊](mailto:mingyuejingque@gmail.com)
[cgm](mailto:2958580318@qq.com)
[hejilin](mailto:1724010622@qq.com)
[alexliyu7352](mailto:liyu7352@gmail.com)
[cgm](mailto:2958580318@qq.com)
[haorui wang](https://github.com/HaoruiWang)
[joshuafc](mailto:joshuafc@foxmail.com)
[JayChen0519](https://github.com/JayChen0519)
[zx](mailto:zuoxue@qq.com)
[wangcker](mailto:wangcker@163.com)
[WuPeng](mailto:wp@zafu.edu.cn)
[starry](https://github.com/starry)
[mtdxc](https://github.com/mtdxc)
[胡刚风](https://github.com/hugangfeng333)
[zhao85](https://github.com/zhao85)
[dreamisdream](https://github.com/dreamisdream)
[dingcan](https://github.com/dcan123)
[Haibo Chen](https://github.com/duiniuluantanqin)
[Leon](https://gitee.com/leon14631)
[custompal](https://github.com/custompal)
[PioLing](https://github.com/PioLing)
[KevinZang](https://github.com/ZSC714725)
[gongluck](https://github.com/gongluck)
[a-ucontrol](https://github.com/a-ucontrol)
[TalusL](https://github.com/TalusL)
[ahaooahaz](https://github.com/AHAOAHA)
[TempoTian](https://github.com/TempoTian)
[Derek Liu](https://github.com/yjkhtddx)
[ljx0305](https://github.com/ljx0305)
[朱如洪 ](https://github.com/zhu410289616)
[lijin](https://github.com/1461521844lijin)
[PioLing](https://github.com/PioLing)
[BackT0TheFuture](https://github.com/BackT0TheFuture)
[perara](https://github.com/perara)
[codeRATny](https://github.com/codeRATny)
[dengjfzh](https://github.com/dengjfzh)
[百鸣](https://github.com/ixingqiao)
[fruit Juice](https://github.com/xuandu)
[tbago](https://github.com/tbago)
[Luosh](https://github.com/Luosh)
[linxiaoyan87](https://github.com/linxiaoyan)
[waken](https://github.com/mc373906408)
[Deepslient](https://github.com/Deepslient)
Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion:
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
## Use Cases
This project has gained recognition from many companies and individual developers. According to the author's incomplete statistics, companies using this project include well-known Internet giants, leading cloud service companies in China, several well-known AI unicorn companies, as well as a series of small and medium-sized companies. Users can endorse this project by pasting their company name and relevant project information on the [issue page](https://github.com/ZLMediaKit/ZLMediaKit/issues/511). Thank you for your support!

View File

@ -30,13 +30,6 @@ file(GLOB API_SRC_LIST
set(LINK_LIBRARIES ${MK_LINK_LIBRARIES})
if(IOS)
add_library(mk_api STATIC ${API_SRC_LIST})
target_link_libraries(mk_api
PRIVATE ${LINK_LIBRARIES})
return()
endif ()
set(COMPILE_DEFINITIONS ${MK_COMPILE_DEFINITIONS})
if (MSVC)
@ -46,6 +39,8 @@ endif ()
if(ENABLE_API_STATIC_LIB)
add_library(mk_api STATIC ${API_SRC_LIST})
list(APPEND COMPILE_DEFINITIONS MediaKitApi_STATIC)
elseif(IOS)
add_library(mk_api STATIC ${API_SRC_LIST})
else()
add_library(mk_api SHARED ${API_SRC_LIST})
endif()
@ -74,8 +69,6 @@ generate_export_header(mk_api
STATIC_DEFINE MediaKitApi_STATIC
EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/mk_export.h")
add_subdirectory(tests)
file(GLOB API_HEADER_LIST include/*.h ${CMAKE_CURRENT_BINARY_DIR}/*.h)
install(FILES ${API_HEADER_LIST}
DESTINATION ${INSTALL_PATH_INCLUDE})
@ -83,3 +76,12 @@ install(TARGETS mk_api
ARCHIVE DESTINATION ${INSTALL_PATH_LIB}
LIBRARY DESTINATION ${INSTALL_PATH_LIB}
RUNTIME DESTINATION ${INSTALL_PATH_RUNTIME})
# IOS
if(IOS)
return()
endif()
if (ENABLE_TESTS)
add_subdirectory(tests)
endif()

View File

@ -166,6 +166,17 @@ typedef struct {
*/
void (API_CALL *on_mk_log)(int level, const char *file, int line, const char *function, const char *message);
/**
* rtp流失败回调mk_media_source_start_send_rtp/mk_media_start_send_rtp接口触发的rtp发送
* @param vhost
* @param app
* @param stream id
* @param ssrc ssrc的10进制打印atoi转换为整型
* @param err
* @param msg
*/
void(API_CALL *on_mk_media_send_rtp_stop)(const char *vhost, const char *app, const char *stream, const char *ssrc, int err, const char *msg);
} mk_events;

View File

@ -12,6 +12,7 @@
#define MK_EVENT_OBJECTS_H
#include "mk_common.h"
#include "mk_tcp.h"
#include "mk_track.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -61,19 +62,19 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
//MediaInfo对象的C映射
typedef struct mk_media_info_t *mk_media_info;
//MediaInfo::_param_strs
//MediaInfo::param_strs
API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx);
//MediaInfo::_schema
//MediaInfo::schema
API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx);
//MediaInfo::_vhost
//MediaInfo::vhost
API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx);
//MediaInfo::_app
//MediaInfo::app
API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx);
//MediaInfo::_streamid
//MediaInfo::stream
API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx);
//MediaInfo::_host
//MediaInfo::host
API_EXPORT const char* API_CALL mk_media_info_get_host(const mk_media_info ctx);
//MediaInfo::_port
//MediaInfo::port
API_EXPORT uint16_t API_CALL mk_media_info_get_port(const mk_media_info ctx);
@ -95,6 +96,13 @@ API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source
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);
// get track count from MediaSource
API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx);
// copy track reference by index from MediaSource, please use mk_track_unref to release it
API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index);
// MediaSource::broadcastMessage
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len);
/**
* ZLMediaKit中被称作为MediaSource
* 3RtmpMediaSourceRtspMediaSourceHlsMediaSource
@ -132,6 +140,12 @@ API_EXPORT void API_CALL mk_media_source_find(const char *schema,
int from_mp4,
void *user_data,
on_mk_media_source_find_cb cb);
API_EXPORT const mk_media_source API_CALL mk_media_source_find2(const char *schema,
const char *vhost,
const char *app,
const char *stream,
int from_mp4);
//MediaSource::for_each_media()
API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb, const char *schema,
const char *vhost, const char *app, const char *stream);

View File

@ -117,6 +117,108 @@ API_EXPORT uint64_t API_CALL mk_frame_get_pts(mk_frame frame);
*/
API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame);
//////////////////////////////////////////////////////////////////////
typedef struct mk_buffer_t *mk_buffer;
typedef struct mk_frame_merger_t *mk_frame_merger;
/**
*
* @param type 0: none, 1: h264_prefix/AnnexB(0x 00 00 00 01), 2: mp4_nal_size(avcC)
* @return
*/
API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type);
/**
*
* @param ctx
*/
API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx);
/**
* merger对象缓冲便
* @param ctx
*/
API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx);
/**
*
* @param user_data
* @param dts
* @param pts
* @param buffer buffer对象
* @param have_key_frame
*/
typedef void(API_CALL *on_mk_frame_merger)(void *user_data, uint64_t dts, uint64_t pts, mk_buffer buffer, int have_key_frame);
/**
* frame到merger对象并合并
* @param ctx
* @param frame
* @param cb
* @param user_data
*/
API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data);
/**
* flush merger对象缓冲api前需要确保先调用mk_frame_merger_input函数并且回调参数有效
* @param ctx
*/
API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx);
//////////////////////////////////////////////////////////////////////
typedef struct mk_mpeg_muxer_t *mk_mpeg_muxer;
/**
* mpeg-ps/ts
* @param user_data
* @param muxer
* @param frame
* @param size
* @param timestamp
* @param key_pos
*/
typedef void(API_CALL *on_mk_mpeg_muxer_frame)(void *user_data, mk_mpeg_muxer muxer, const char *frame, size_t size, uint64_t timestamp, int key_pos);
/**
* mpeg-ps/ts
* @param cb
* @param user_data
* @param is_ps ps
* @return
*/
API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps);
/**
* mpeg-ps/ts
* @param ctx
*/
API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx);
/**
* track
* @param ctx mk_mpeg_muxer对象
* @param track mk_track对象
*/
API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track);
/**
* track完毕后调用此函数
* track()ZLMediaKit不知道后续是否还要添加track3
* Track类型便(3)
* @param ctx
*/
API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx);
/**
* frame对象
* @param ctx mk_mpeg_muxer对象
* @param frame
* @return 10
*/
API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame);
#ifdef __cplusplus
}
#endif

View File

@ -58,7 +58,7 @@ API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, co
/**
*
* @param type 0:hls,1:MP4
* @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts
* @param vhost
* @param app
* @param stream id
@ -70,7 +70,7 @@ API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const cha
/**
*
* @param type 0:hls,1:MP4
* @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts
* @param vhost
* @param app
* @param stream id

View File

@ -82,11 +82,11 @@ API_EXPORT void API_CALL mk_tcp_session_send_buffer(const mk_tcp_session ctx, mk
API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx, const char *data, size_t len);
API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ctx, mk_buffer buffer);
//创建mk_tcp_session的引用
//创建mk_tcp_session的引用
API_EXPORT mk_tcp_session_ref API_CALL mk_tcp_session_ref_from(const mk_tcp_session ctx);
//删除mk_tcp_session的引用
//删除mk_tcp_session的引用
API_EXPORT void mk_tcp_session_ref_release(const mk_tcp_session_ref ref);
//根据弱引用获取mk_tcp_session如果mk_tcp_session已经销毁那么返回NULL
//根据强引用获取mk_tcp_session
API_EXPORT mk_tcp_session mk_tcp_session_from_ref(const mk_tcp_session_ref ref);
///////////////////////////////////////////自定义tcp服务/////////////////////////////////////////////

View File

@ -159,7 +159,7 @@ API_EXPORT void API_CALL mk_set_option(const char *key, const char *val) {
}
mINI::Instance()[key] = val;
//广播配置文件热加载
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
}
API_EXPORT const char * API_CALL mk_get_option(const char *key)

View File

@ -160,6 +160,13 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){
s_events.on_mk_log((int) ctx->_level, ctx->_file.data(), ctx->_line, ctx->_function.data(), log.data());
}
});
NoticeCenter::Instance().addListener(&s_tag, Broadcast::kBroadcastSendRtpStopped,[](BroadcastSendRtpStoppedArgs){
if (s_events.on_mk_media_send_rtp_stop) {
s_events.on_mk_media_send_rtp_stop(sender.getMediaTuple().vhost.c_str(), sender.getMediaTuple().app.c_str(),
sender.getMediaTuple().stream.c_str(), ssrc.c_str(), ex.getErrCode(), ex.what());
}
});
});
}

View File

@ -86,17 +86,17 @@ API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){
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();
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();
return parser->url().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();
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);
@ -106,7 +106,7 @@ API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,cons
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();
return parser->protocol().c_str();
}
API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){
assert(ctx && key);
@ -117,52 +117,52 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_
assert(ctx);
Parser *parser = (Parser *)ctx;
if(length){
*length = parser->Content().size();
*length = parser->content().size();
}
return parser->Content().c_str();
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();
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();
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();
return info->vhost.c_str();
}
API_EXPORT const char* API_CALL mk_media_info_get_host(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_host.c_str();
return info->host.c_str();
}
API_EXPORT uint16_t API_CALL mk_media_info_get_port(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_port;
return info->port;
}
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();
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();
return info->stream.c_str();
}
///////////////////////////////////////////MediaSource/////////////////////////////////////////////
@ -174,17 +174,17 @@ API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source
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();
return src->getMediaTuple().vhost.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();
return src->getMediaTuple().app.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();
return src->getMediaTuple().stream.c_str();
}
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){
assert(ctx);
@ -198,6 +198,32 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so
return src->totalReaderCount();
}
API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx) {
assert(ctx);
MediaSource *src = (MediaSource *)ctx;
return src->getTracks(false).size();
}
API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index) {
assert(ctx);
MediaSource *src = (MediaSource *)ctx;
auto tracks = src->getTracks(false);
if (index < 0 && index >= tracks.size()) {
return nullptr;
}
return (mk_track) new Track::Ptr(std::move(tracks[index]));
}
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) {
assert(ctx && msg && len);
MediaSource *src = (MediaSource *)ctx;
Any any;
Buffer::Ptr buffer = std::make_shared<BufferLikeString>(std::string(msg, len));
any.set(std::move(buffer));
return src->broadcastMessage(any);
}
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){
assert(ctx);
MediaSource *src = (MediaSource *)ctx;
@ -248,6 +274,16 @@ API_EXPORT void API_CALL mk_media_source_find(const char *schema,
cb(user_data, (mk_media_source)src.get());
}
API_EXPORT const mk_media_source API_CALL mk_media_source_find2(const char *schema,
const char *vhost,
const char *app,
const char *stream,
int from_mp4) {
assert(schema && vhost && app && stream);
auto src = MediaSource::find(schema, vhost, app, stream, from_mp4);
return (mk_media_source)src.get();
}
API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb, const char *schema,
const char *vhost, const char *app, const char *stream) {
assert(cb);

View File

@ -9,10 +9,12 @@
*/
#include "mk_frame.h"
#include "mk_track.h"
#include "Extension/Frame.h"
#include "Extension/H264.h"
#include "Extension/H265.h"
#include "Extension/AAC.h"
#include "Record/MPEG.h"
using namespace mediakit;
@ -178,3 +180,92 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) {
}
return ret;
}
API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type) {
return reinterpret_cast<mk_frame_merger>(new FrameMerger(type));
}
API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx) {
assert(ctx);
delete reinterpret_cast<FrameMerger *>(ctx);
}
API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx) {
assert(ctx);
reinterpret_cast<FrameMerger *>(ctx)->clear();
}
API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx) {
assert(ctx);
reinterpret_cast<FrameMerger *>(ctx)->flush();
}
API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data) {
assert(ctx && frame && cb);
reinterpret_cast<FrameMerger *>(ctx)->inputFrame(*((Frame::Ptr *) frame), [cb, user_data](uint64_t dts, uint64_t pts, const toolkit::Buffer::Ptr &buffer, bool have_key_frame) {
cb(user_data, dts, pts, (mk_buffer)(&buffer), have_key_frame);
});
}
//////////////////////////////////////////////////////////////////////
class MpegMuxerForC : public MpegMuxer {
public:
using onMuxer = std::function<void(const char *frame, size_t size, uint64_t timestamp, int key_pos)>;
MpegMuxerForC(bool is_ps) : MpegMuxer(is_ps) {
_cb = nullptr;
}
~MpegMuxerForC() { MpegMuxer::flush(); };
void setOnMuxer(onMuxer cb) {
_cb = std::move(cb);
}
private:
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
if (_cb) {
if (!buffer) {
_cb(nullptr, 0, timestamp, key_pos);
} else {
_cb(buffer->data(), buffer->size(), timestamp, key_pos);
}
}
}
private:
onMuxer _cb;
};
API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps){
assert(cb);
auto ret = new MpegMuxerForC(is_ps);
std::shared_ptr<void> ptr(user_data, [](void *) {});
ret->setOnMuxer([cb, ptr, ret](const char *frame, size_t size, uint64_t timestamp, int key_pos) {
cb(ptr.get(), reinterpret_cast<mk_mpeg_muxer>(ret), frame, size, timestamp, key_pos);
});
return reinterpret_cast<mk_mpeg_muxer>(ret);
}
API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx){
assert(ctx);
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
delete ptr;
}
API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track) {
assert(ctx && track);
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
ptr->addTrack(*((Track::Ptr *) track));
}
API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx) {
assert(ctx);
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
ptr->addTrackCompleted();
}
API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame){
assert(ctx && frame);
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
return ptr->inputFrame(*((Frame::Ptr *) frame));
}

View File

@ -108,7 +108,7 @@ API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,cons
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)->response().Url().c_str();
return (*obj)->response().status().c_str();
}
API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key){
@ -121,9 +121,9 @@ API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requ
assert(ctx);
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
if(length){
*length = (*obj)->response().Content().size();
*length = (*obj)->response().content().size();
}
return (*obj)->response().Content().c_str();
return (*obj)->response().content().c_str();
}
API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx){

View File

@ -22,7 +22,8 @@ public:
MediaHelper(const char *vhost, const char *app, const char *stream, float duration, const ProtocolOption &option) {
_poller = EventPollerPool::Instance().getPoller();
// 在poller线程中创建DevChannel(MultiMediaSourceMuxer)对象,确保严格的线程安全限制
_poller->sync([&]() { _channel = std::make_shared<DevChannel>(vhost, app, stream, duration, option); });
auto tuple = MediaTuple{vhost, app, stream};
_poller->sync([&]() { _channel = std::make_shared<DevChannel>(tuple, duration, option); });
}
~MediaHelper() = default;
@ -263,7 +264,7 @@ API_EXPORT void API_CALL mk_media_input_yuv(mk_media ctx, const char *yuv[3], in
}
API_EXPORT int API_CALL mk_media_input_aac(mk_media ctx, const void *data, int len, uint64_t dts, void *adts) {
assert(ctx && data && len > 0 && adts);
assert(ctx && data && len > 0);
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
return (*obj)->getChannel()->inputAAC((const char *) data, len, dts, (char *) adts);
}

View File

@ -47,21 +47,25 @@ static inline bool isRecording(Recorder::type type, const string &vhost, const s
return src->isRecording(type);
}
static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path, size_t max_second){
static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, size_t max_second) {
auto src = MediaSource::find(vhost, app, stream_id);
if (!src) {
WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id;
return false;
}
return src->setupRecord(type, true, customized_path, max_second);
bool ret;
src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, true, customized_path, max_second); });
return ret;
}
static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id){
static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id) {
auto src = MediaSource::find(vhost, app, stream_id);
if(!src){
if (!src) {
return false;
}
return src->setupRecord(type, false, "", 0);
bool ret;
src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, false, "", 0); });
return ret;
}
API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, const char *app, const char *stream){

View File

@ -139,7 +139,7 @@ API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx, const cha
API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ctx, mk_buffer buffer) {
assert(ctx && buffer);
try {
std::weak_ptr<Session> weak_session = ((SessionForC *) ctx)->shared_from_this();
std::weak_ptr<SocketHelper> weak_session = ((SessionForC *) ctx)->shared_from_this();
auto ref = mk_buffer_ref(buffer);
((SessionForC *) ctx)->async([weak_session, ref]() {
auto session_session = weak_session.lock();
@ -149,13 +149,13 @@ API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ct
mk_buffer_unref(ref);
});
} catch (std::exception &ex) {
WarnL << "can not got the strong pionter of this mk_tcp_session:" << ex.what();
WarnL << "can not got the strong pointer of this mk_tcp_session:" << ex.what();
}
}
API_EXPORT mk_tcp_session_ref API_CALL mk_tcp_session_ref_from(const mk_tcp_session ctx) {
auto ref = ((SessionForC *) ctx)->shared_from_this();
return (mk_tcp_session_ref)new std::shared_ptr<SessionForC>(std::dynamic_pointer_cast<SessionForC>(ref));
return (mk_tcp_session_ref)new std::shared_ptr<SessionForC>(std::static_pointer_cast<SessionForC>(ref));
}
API_EXPORT void mk_tcp_session_ref_release(const mk_tcp_session_ref ref) {
@ -271,7 +271,7 @@ void TcpClientForC::onRecv(const Buffer::Ptr &pBuf) {
}
}
void TcpClientForC::onErr(const SockException &ex) {
void TcpClientForC::onError(const SockException &ex) {
if(_events.on_mk_tcp_client_disconnect){
_events.on_mk_tcp_client_disconnect(_client,ex.getErrCode(),ex.what());
}

View File

@ -21,7 +21,7 @@ public:
TcpClientForC(mk_tcp_client_events *events) ;
~TcpClientForC() override ;
void onRecv(const toolkit::Buffer::Ptr &pBuf) override;
void onErr(const toolkit::SockException &ex) override;
void onError(const toolkit::SockException &ex) override;
void onManager() override;
void onConnect(const toolkit::SockException &ex) override;
void setClient(mk_tcp_client client);

View File

@ -143,7 +143,7 @@ public:
}
EventPoller::Ptr getPoller() {
return dynamic_pointer_cast<EventPoller>(getExecutor());
return static_pointer_cast<EventPoller>(getExecutor());
}
};

View File

@ -65,7 +65,7 @@ API_EXPORT mk_ini API_CALL mk_ini_default() {
static void emit_ini_file_reload(mk_ini ini) {
if (ini == mk_ini_default()) {
// 广播配置文件热加载
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
}
}

View File

@ -43,15 +43,3 @@ foreach(TEST_SRC ${TEST_SRC_LIST})
target_link_libraries(${exe_name} mk_api)
target_compile_options(${exe_name} PRIVATE ${COMPILE_OPTIONS_DEFAULT})
endforeach()

View File

@ -9,6 +9,7 @@
*/
#include <string.h>
#include <stdio.h>
#include "mk_mediakit.h"
typedef struct {

View File

@ -9,6 +9,7 @@
*/
#include <string.h>
#include <stdio.h>
#include "mk_mediakit.h"
#define LOG_LEV 4

View File

@ -1,21 +1,34 @@
# Download and build Jemalloc
set(JEMALLOC_VERSION 5.2.1)
set(JEMALLOC_VERSION 5.3.0)
set(JEMALLOC_NAME jemalloc-${JEMALLOC_VERSION})
set(JEMALLOC_TAR_PATH ${DEP_ROOT_DIR}/${JEMALLOC_NAME}.tar.bz2)
list(APPEND jemalloc_CONFIG_ARGS --disable-initial-exec-tls)
list(APPEND jemalloc_CONFIG_ARGS --without-export)
#list(APPEND jemalloc_CONFIG_ARGS --without-export)
if (ENABLE_JEMALLOC_STAT)
list(APPEND jemalloc_CONFIG_ARGS --enable-stats)
message(STATUS "Jemalloc stats enabled")
else ()
list(APPEND jemalloc_CONFIG_ARGS --disable-stats)
message(STATUS "Jemalloc stats disabled")
endif ()
list(APPEND jemalloc_CONFIG_ARGS --disable-libdl)
#list(APPEND jemalloc_CONFIG_ARGS --disable-cxx)
#list(APPEND jemalloc_CONFIG_ARGS --with-jemalloc-prefix=je_)
#list(APPEND jemalloc_CONFIG_ARGS --enable-debug)
if(NOT EXISTS ${JEMALLOC_TAR_PATH})
message(STATUS "Downloading ${JEMALLOC_NAME}...")
file(DOWNLOAD https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2
${JEMALLOC_TAR_PATH})
set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2)
message(STATUS "Downloading ${JEMALLOC_NAME} from ${JEMALLOC_URL}")
file(DOWNLOAD ${JEMALLOC_URL} ${JEMALLOC_TAR_PATH} SHOW_PROGRESS STATUS JEMALLOC_DOWNLOAD_STATUS LOG JEMALLOC_DOWNLOAD_LOG)
list(GET JEMALLOC_DOWNLOAD_STATUS 0 JEMALLOC_DOWNLOAD_STATUS_CODE)
if(NOT JEMALLOC_DOWNLOAD_STATUS_CODE EQUAL 0)
file(REMOVE ${JEMALLOC_TAR_PATH})
message(STATUS "${JEMALLOC_DOWNLOAD_LOG}")
message(FATAL_ERROR "${JEMALLOC_NAME} download failed! error is ${JEMALLOC_DOWNLOAD_STATUS}")
return()
endif ()
endif()
SET( DIR_CONTAINING_JEMALLOC ${DEP_ROOT_DIR}/${JEMALLOC_NAME} )

View File

@ -22,7 +22,7 @@ bin=/usr/bin/ffmpeg
#FFmpeg拉流再推流的命令模板通过该模板可以设置再编码的一些参数
cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
#FFmpeg生成截图的命令可以通过修改该配置改变截图分辨率或质量
snap=%s -i %s -y -f mjpeg -t 0.001 %s
snap=%s -i %s -y -f mjpeg -frames:v 1 %s
#FFmpeg日志的路径如果置空则不生成FFmpeg日志
#可以为相对(相对于本可执行程序目录)或绝对路径
log=./ffmpeg/ffmpeg.log
@ -32,18 +32,28 @@ restart_sec=0
#转协议相关开关如果addStreamProxy api和on_publish hook回复未指定转协议参数则采用这些配置项
[protocol]
#转协议时,是否开启帧级时间戳覆盖
modify_stamp=0
# 0:采用源视频流绝对时间戳,不做任何改变
# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理)
# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
modify_stamp=2
#转协议是否开启音频
enable_audio=1
#添加acc静音音频在关闭音频时此开关无效
add_mute_audio=1
#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
#此配置置1时此流如果无人观看将不触发on_none_reader hook回调
#而是将直接关闭流
auto_close=0
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
#置0关闭此特性(推流断开会导致立即断开播放器)
#此参数不应大于播放器超时时间;单位毫秒
continue_push_ms=15000
#是否开启转换为hls
#是否开启转换为hls(mpegts)
enable_hls=1
#是否开启转换为hls(fmp4)
enable_hls_fmp4=0
#是否开启MP4录制
enable_mp4=0
#是否开启转换为rtsp/webrtc
@ -121,7 +131,7 @@ segDur=2
segNum=3
#HLS切片从m3u8文件中移除后继续保留在磁盘上的个数
segRetain=5
#是否广播 ts 切片完成通知
#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
broadcastRecordTs=0
#直播hls文件删除延时单位秒issue: #913
deleteDelaySec=10
@ -132,9 +142,6 @@ deleteDelaySec=10
segKeep=0
[hook]
#在推流时如果url参数匹对admin_params那么可以不经过hook鉴权直接推流成功播放时亦然
#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
#是否启用hook事件启用后推拉流都将进行鉴权
enable=0
#播放器或推流器使用流量事件,置空则关闭
@ -147,7 +154,7 @@ on_play=https://127.0.0.1/index/hook/on_play
on_publish=https://127.0.0.1/index/hook/on_publish
#录制mp4切片完成事件
on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
# 录制 hls ts 切片完成事件
# 录制 hls ts(或fmp4) 切片完成事件
on_record_ts=https://127.0.0.1/index/hook/on_record_ts
#rtsp播放鉴权事件此事件中比对rtsp的用户名密码
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
@ -159,12 +166,16 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm
on_shell_login=https://127.0.0.1/index/hook/on_shell_login
#直播流注册或注销事件
on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed
#过滤on_stream_changed hook的协议类型可以选择只监听某些感兴趣的协议置空则不过滤协议
stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4
#无人观看流事件通过该事件可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
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
#服务器退出报告,当服务器正常退出时触发
on_server_exited=https://127.0.0.1/index/hook/on_server_exited
#server保活上报
on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive
#发送rtp(startSendRtp)被动关闭时回调
@ -230,6 +241,10 @@ forbidCacheSuffix=
#可以把http代理前真实客户端ip放在http头中https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
#切勿暴露此key否则可能导致伪造客户端ip
forwarded_ip_header=
#默认允许所有跨域请求
allow_cross_domains=1
#允许访问http api和http文件索引的ip地址范围白名单置空情况下不做限制
allow_ip_range=::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255
[multicast]
#rtp组播截止组播ip地址
@ -259,8 +274,6 @@ handshakeSecond=15
#rtmp超时时间如果该时间内未收到客户端的数据
#或者tcp发送缓存超过这个时间则会断开连接单位秒
keepAliveSecond=15
#在接收rtmp推流时是否重新生成时间戳(很多推流器的时间戳着实很烂)
modifyStamp=0
#rtmp服务器监听端口
port=1935
#rtmps服务器监听地址
@ -276,6 +289,9 @@ videoMtuSize=1400
rtpMaxSize=10
# rtp 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏
lowLatency=0
# H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
# 有些老的rtsp设备不支持stap-a rtp设置此配置为0可提高兼容性
h264_stap_a=1
[rtp_proxy]
#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
@ -357,6 +373,10 @@ port=554
sslport=0
#rtsp 转发是否使用低延迟模式当开启时不会缓存rtp包来提高并发可以降低一帧的延迟
lowLatency=0
#强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制)
#当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupported transport
#迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC
rtpTransportType=-1
[shell]
#调试telnet服务器接受最大bufffer大小
maxReqSize=1024

View File

@ -1,89 +1,89 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLvnz2zdgL2
uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGcS7y2aMha
0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dwoEC7+Pjl
dsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx0I1jVR76
juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ckTTTbZtSp
9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABAoIBADCWTh8P19vdnR3X
v5uPXLcgkL7WQt+g7Qbd91CKVaRWTsHvDilGVNA4Ntc85oyy3gPNHfa/YPdnU0bQ
6vtwGgLEKTWumY6rgdDhQcFMmLTlaV4QiFSw6q8MWMN6c/yZSmA7wMoXAIVs0/VB
ip44sb4Fpw5MBMCjxZjwL3fP09WJPlUqx09vVo7eH8rFwLBikmn982IzRigAx1I8
TX0wkdqvv33MSxBXPMQIrwPqjf2arxWFzb6vp6yolYbMZtgORF9gznWABRy3oY50
9jFkTkbxZFlSMVuF7nlM0WJj5Q9/IelBqpozODWUVvB+6inCqkxNLkbh0ISbpXWC
16gUZfUCgYEAxWo3FRNBrNXhVD5h2N4ApyUXkZ5UYIY5zbsHEJCrPjooh9uHu9kh
xXh5v11J/7TV9BfwLZ4qRbDBH4fq0DKEOXOZRLY5Lo4KbrYmlEDCabuJdmwwHeGh
S5K37F5z/+zPz9KWkKN+9Rg32xdLxh0969O77GnvuBrhzASpVsF6ZFMCgYEAtxf1
eVg4Kxzuy0AWs+CisSVQc+5CbZ9teKA5fli2EVSmL5dsrKatVTIDghudJgQTU6cr
zP9I20K11jeqIoK5saQXH3CzogN6aDuKssq4rDbvVSZ09Zry6N1WMz9GPe31zEYw
sdU1w7vUw+l3unFfWOP4oZm0MH+na61V1YohCRUCgYANlp0J/1RS8DndUZnskoNa
/eucY1iNeE+8QHZhBoQy+U/W4h56qJxxejRvHp28UxczAP7QNQXV3C++2t0nzYJa
bgGLwDs5YB+JtVH8fGSlYHo6w4GgXOp8SDIOvAWiBQvc0zL367kOZ8dYdkcJ8PNV
KzLROA1/D6KhJ2T8ir7A7wKBgQCjVVxGw8xXqZfc+W9HSD3aic8bnJDl+jNOSKEB
dWH2U+1sx0jLPGWketlmV/v4zenv1lHcrl/wObK9RysfXj8JmbiG86NMBI5OLc+t
b+sOtnMLIyNzdqb71Xfwf6HJ3V5IvNTzz6AG3KkRnFSSnlDQm45RmyyDl11jUV4h
APg3gQKBgBzFeuKWnaTZz1FQBr5Ytl9gtxBRMl+49jtkqyzErJYFHe0MTWeD/1xj
mEC/7UERYWhIQF1L4ah6c0QkecR3F1s9/IYK/QHsnSJFwRyFuMas6StCERsDq5oQ
GWpXAmw7JTa8OYwxVjORdXY25Iwv6rEr6iUYBWZrkhoWYBySWpSZ
MIIEowIBAAKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj3B9LM8ci
fN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2iKxfLXKEH
S283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE3fVS0hFI
8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9RwfGSxlF
MCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVeEuGxfJPf
JVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABAoIBAADFrCObAzBrRu46
hps50NeJR/ZAJibXE/NzxTSVPPc0EseXcqgA8t1Y0CYEpV77d4CrcCQNVJ6wDrHX
AQGtydxG17tbIMo0AUgkrVBSa5uvMCembzd8s0l93egyUkAWfsaqbKEJeJ/eer7D
N1Xqd2zWro2iYHuxZOuSM1I+AMPIQsmYJ71w6/h9YpQh436Vd+zNQ5k/nWpLHihT
VB2ECrJ36IbuiYo3UbSr9gQjyBSMkk/oUqO4jonkb6L7r0mqHXNeblycg99/m6i7
O5c5DQKMhzqibwvNNf6uvWCcLKfF5Kqzzf9DKR3/pYOBQrVTA24l4UFsfTdEKUNS
a8W3P8ECgYEA6CQOG15V9upc2nPzfFwgftGyomSMYH54PkSFdr2R4djyXkyil6Ik
efK3E+lKr9YnzwcLw3csPmVt3lqSgixQUMcyXXrhCttfk/qzSJkI+UZPQE+SrNeW
0c+blQOzVcfbNRu248iGFaRx+5qA6PMH4UZTgn7e6nXoPUgRp4ryI/MCgYEAyL24
R7uMSuPQBRJFU84Lu+Rv4lkKdCYSLuQtMZly74m11iG6e+EHJQx0C3eexrC8LhOV
Sm4xTlwVrYQ+IdW51bhAwwHcnzGUzpbESJSDK5ZTd/P5daz8yt8ZaGbUFxNEsxTr
ElKPRcjJH5CRuyYr24DYg+CpMGdlF0N6Pcx5IFECgYAedlzDiqWNOUPmBsE02IIL
IklmtfsVzoLI6QT6h/XUxTtI1JWhgE15EzijDEIYwOmIaUxJ4iGULos0Wn5PRrFj
aEBbs/xECHWKXaOZKzvaOje8ILUGqWPJNI0eCNZHs2o4leJyEaZGwMWUVroD16B5
F1luDmgCLGbFY+etLLaJsQKBgB40VbcNZDWcg59PuXi7pw5Vd/RB243QcKn3kUlG
QoICYYbfulSLbmzHq+pRzGUvEJGKRstVOzwEJQrfvA2RQA4FVFFDRXP6nN5c1xno
prf3PYXuAtoO9lZ8LTGFT2JNdufPPPOb0oz4gjKqqRLU0oKLp4hoVGzBEffnIkyM
KKmRAoGBAIGXh4gvxzEQMgGzfKfNuxKCT9SEhsg7NU++Iey3qn4G4t+jIWOt2Gi7
5+y49JWoGq6DL+2ZVVw6Cn6wd9tfzDKD5GhvIztK0z1+wqpFOL4M8bwqJDOKgsZ3
PCPASbxPgMyNCjRhvxBuscCr+dRFYDUrirOK9EUPyO9EoNTPPN9a
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIGAjCCBOqgAwIBAgIQAiXv68Xco/vd9YeB4g3HLjANBgkqhkiG9w0BAQsFADBu
MIIGBTCCBO2gAwIBAgIQDNIYeWoFoT3jxF2+HmEbTDANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
RFYgVExTIENBIC0gRzEwHhcNMjIwOTE4MDAwMDAwWhcNMjMwOTE4MjM1OTU5WjAh
RFYgVExTIENBIC0gRzIwHhcNMjMwOTI4MDAwMDAwWhcNMjQwOTI3MjM1OTU5WjAh
MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLv
nz2zdgL2uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGc
S7y2aMha0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dw
oEC7+PjldsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx
0I1jVR76juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ck
TTTbZtSp9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABo4IC5zCCAuMw
HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFPnRZrfz
q/QAf5u4Xp4eGWvhMdvfMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3
LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
AQEFAAOCAQ8AMIIBCgKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj
3B9LM8cifN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2i
KxfLXKEHS283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE
3fVS0hFI8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9
RwfGSxlFMCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVe
EuGxfJPfJVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABo4IC6jCCAuYw
HwYDVR0jBBgwFoAUeN+RkF/u3qz2xXXr1UxVU+8kSrYwHQYDVR0OBBYEFHmEMVp9
9EHIPWA2U1iLKogCosGFMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
b20wPgYDVR0gBDcwNTAzBgZngQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr
BgEFBQcDAQYIKwYBBQUHAwIwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx
LmNydDAJBgNVHRMEAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDa
PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYNQt3JvAAAEAwBHMEUCIEaO
G4ffzzaE6OMqiu6PUr+Y+wO2tsXCkGt1jt04Ix1qAiEAhNZwqFACieds1ZbY3r/p
wlF3iFbhqp+kNfPzon7kwc8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gD
wzvWTAAAAYNQt3JVAAAEAwBHMEUCIBOErqyKvihAEKItLWG/Plgtxh/hCTMsE+t5
+MfsAQLCAiEA76d50S4iy1wxya+8IUASVlKStaHNqBkJAS+Oadxs2sMAdwCzc3cH
4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTalmgAAAYNQt3LIAAAEAwBIMEYCIQC/
kfFCpwF76sw/Qx3sxR8b3srW+Ds0k/6VrIIDZcYV5gIhAKkLmuyeDvzulp0y4f0t
GDgIN/OoURq6CuHA67UJlsWzMA0GCSqGSIb3DQEBCwUAA4IBAQB0BwVxPRihSdPJ
FUPLQ+ClHy9O/UisnRD7NadQQtbcMXn6L9Lwd0f2la0ytLQAKHADOZDA08KfQ5qW
B19OeQOlTwp2nhY2ZvoLEG+paeh0gYxIgD76APnd/m3g2H7GeW144ymjPcZRoldj
ZKYSdzStJJIFYXzL3FR9wjkMc4xOEes/IY5PFtj8OT8CFf7zl0R7L2Vcw9RGYi9u
vLjGwwJW9kXTX8UlKXFyjJN0ZyrmxBQHq5uNtigx8xy6HtMnPsc58tp1IqitIELp
HIur2XrRPBJA5XtpDg3AE8bXhRTM8oFMPL0UoSFWyWRYGgBo1Msc10dpXPtmbgIc
pPW8w+2c
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcy
LmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDu
zdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYravqonAAAEAwBHMEUC
IQDX+gqsd7I0yzjkhgp2YrccUlTx4wkFptFvmQxeChImRgIgJdgJa2Uamd790BCI
/CZwSqmRlor5eU8exAixdcopYpcAdwBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZ
u7+rOdiEcwAAAYravqqCAAAEAwBIMEYCIQCP6rkKg2FlF92CyMbVMk3ESh/9gVaM
tRsv5I//i5IVigIhAINHERhy7812wR47fwmvqWDjxyOB1ZodU7WA9D5L/1bVAHYA
2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGK2r6qQQAABAMARzBF
AiAiz3bp/j4SlnVxKg1HZY+YdUboi+kaKf5G8X6aFLIqUgIhAPPCm5UN05p7Oqrc
sP/wdHDB7O/2AbUksYSLhidmwfmhMA0GCSqGSIb3DQEBCwUAA4IBAQBmaG51jU1E
MsgT1VzutQUXglEvJGVf54cA+0TSfjfnP1n9ALdKjGxHL3KBh4UkPx5zdE5//FUX
dacua6BQEWSCmMtYL0CFieFnLGXh0mgkfvRaP6+3xe6TkJ4kuyJkMS9YMDpVl80F
2GLlE09EsZ3Xk9+SCpmWOPLOCDFURbwpc5ht+acROfzYJQyCY0L8EGbyL5/q9oMn
ugRGh4oyGvXgKvFIPzpZkaOmb0b63/uBc5JkiyQhuFdYaS2cLOwupXmCtIHL4Od6
OU8/8smT8NEkD7d3lUijtc84q2TihW7ebT7RtOco49PDvFP/7w28QjxM8Ohv9/Gz
Xyta8ICQVwmK
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
-----END CERTIFICATE-----
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
-----END CERTIFICATE-----

View File

@ -128,4 +128,4 @@ WORKDIR /opt/zlm
VOLUME [ "/opt/zlm/conf/","/opt/zlm/log/","opt/zlm/ffmpeg/"]
COPY --from=build /opt/build /
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH TZ=Asia/Shanghai
CMD ./MediaServer -c ./conf/config.ini
CMD ["./MediaServer", "-c" , "./conf/config.ini"]

View File

@ -41,4 +41,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
make
ENV PATH /opt/media/ZLMediaKit/release/linux/Release/:$PATH
CMD MediaServer
CMD ["MediaServer"]

View File

@ -60,4 +60,4 @@ RUN apt-get update && \
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
CMD ["MediaServer"]

View File

@ -42,4 +42,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
make
ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH
CMD MediaServer
CMD ["MediaServer"]

View File

@ -60,4 +60,4 @@ RUN apt-get update && \
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
CMD ["MediaServer"]

View File

@ -83,4 +83,4 @@ COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/MediaServer /opt/
COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/config.ini /opt/media/conf/
COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/
ENV PATH /opt/media/bin:$PATH
CMD ["sh","-c","./MediaServer -s default.pem -c ../conf/config.ini -l 0"]
CMD ["./MediaServer","-s", "default.pem", "-c", "../conf/config.ini", "-l","0"]

View File

@ -33,9 +33,9 @@ SDLAudioDevice::SDLAudioDevice() {
SDLAudioDevice *_this = (SDLAudioDevice *) userdata;
_this->onReqPCM((char *) stream, len);
};
if (SDL_OpenAudio(&wanted_spec, &_audio_config) < 0) {
throw std::runtime_error("SDL_OpenAudio failed");
}
if (SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &_audio_config, SDL_AUDIO_ALLOW_ANY_CHANGE) < 0) {
throw std::runtime_error("SDL_OpenAudioDevice failed");
}
InfoL << "actual audioSpec, " << "freq:" << _audio_config.freq
<< ", format:" << hex << _audio_config.format << dec

View File

@ -56,8 +56,7 @@ int main(int argc, char *argv[]) {
if (argc < 3) {
ErrorL << "\r\n测试方法:./test_player rtxp_url rtp_type\r\n"
<< "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n"
<< endl;
<< "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n";
return 0;
}

View File

@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "4626d766-16b5-4255-89ba-f7614de2398c",
"_postman_id": "39e8a1df-cc8e-4e3f-bf5e-197c86e7bf0f",
"name": "ZLMediaKit",
"description": "媒体服务器",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
@ -25,7 +25,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -51,7 +51,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -77,7 +77,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -103,7 +103,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -129,7 +129,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -155,7 +155,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "api.apiDebug",
@ -186,7 +186,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -212,7 +212,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -262,7 +262,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -314,7 +314,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -366,7 +366,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "local_port",
@ -404,7 +404,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "id",
@ -435,7 +435,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "local_port",
@ -473,7 +473,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -516,7 +516,13 @@
{
"key": "enable_hls",
"value": null,
"description": "是否转hls",
"description": "是否转hls-ts",
"disabled": true
},
{
"key": "enable_hls_fmp4",
"value": null,
"description": "是否转hls-fmp4",
"disabled": true
},
{
@ -582,7 +588,13 @@
{
"key": "modify_stamp",
"value": null,
"description": "是否重新计算时间戳",
"description": "是否修改原始时间戳默认值2取值范围0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
"disabled": true
},
{
"key": "auto_close",
"value": null,
"description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)",
"disabled": true
}
]
@ -609,7 +621,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "key",
@ -627,7 +639,7 @@
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/addStreamPusherProxy?secret={{ZLMediaKit_secret}}&schema=rtmp&vhost={{defaultVhost}}&app=live&stream=test&dst_url=rtmp://127.0.0.1/live/push",
"raw": "{{ZLMediaKit_URL}}/index/api/addStreamPusherProxy?secret={{ZLMediaKit_secret}}&schema=rtmp&vhost={{defaultVhost}}&app=live&stream=test&dst_url=rtmp://192.168.1.104/live/push",
"host": [
"{{ZLMediaKit_URL}}"
],
@ -640,7 +652,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -664,7 +676,7 @@
},
{
"key": "dst_url",
"value": "rtmp://127.0.0.1/live/push",
"value": "rtmp://192.168.1.104/live/push",
"description": "推流地址需要与schema字段协议一致"
},
{
@ -696,7 +708,7 @@
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/delStreamPusherProxy?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test",
"raw": "{{ZLMediaKit_URL}}/index/api/delStreamPusherProxy?secret={{ZLMediaKit_secret}}&key=rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491",
"host": [
"{{ZLMediaKit_URL}}"
],
@ -709,11 +721,11 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "key",
"value": "__defaultVhost__/live/test",
"value": "rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491",
"description": "addStreamPusherProxy接口返回的key"
}
]
@ -740,7 +752,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "src_url",
@ -797,7 +809,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "key",
@ -827,7 +839,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -873,7 +885,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -900,6 +912,56 @@
},
"response": []
},
{
"name": "广播webrtc datachannel消息(broadcastMessage)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/broadcastMessage?secret={{ZLMediaKit_secret}}&schema=rtsp&vhost={{defaultVhost}}&app=live&stream=test&msg=Hello zlmediakit123",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"broadcastMessage"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
"value": "rtsp",
"description": "协议,例如 rtsp或rtmp目前仅支持rtsp协议"
},
{
"key": "vhost",
"value": "{{defaultVhost}}",
"description": "虚拟主机例如__defaultVhost__"
},
{
"key": "app",
"value": "live",
"description": "应用名,例如 live"
},
{
"key": "stream",
"value": "test",
"description": "流id例如 test"
},
{
"key": "msg",
"value": "Hello ZLMediakit"
}
]
}
},
"response": []
},
{
"name": "获取流信息(getMediaInfo)",
"request": {
@ -919,7 +981,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -965,7 +1027,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1016,7 +1078,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1062,7 +1124,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "type",
@ -1120,7 +1182,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1166,7 +1228,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1212,7 +1274,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "type",
@ -1258,7 +1320,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "type",
@ -1304,7 +1366,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "url",
@ -1345,7 +1407,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1363,7 +1425,7 @@
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/openRtpServer?secret={{ZLMediaKit_secret}}&port=0&enable_tcp=1&stream_id=test",
"raw": "{{ZLMediaKit_URL}}/index/api/openRtpServer?secret={{ZLMediaKit_secret}}&port=0&tcp_mode=1&stream_id=test",
"host": [
"{{ZLMediaKit_URL}}"
],
@ -1376,7 +1438,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "port",
@ -1422,7 +1484,7 @@
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/connectRtpServer?secret={{ZLMediaKit_secret}}&dst_url=127.0.0.1&dst_port=10000&stream_id=test",
"raw": "{{ZLMediaKit_URL}}/index/api/connectRtpServer?secret={{ZLMediaKit_secret}}&dst_url=0&dst_port=1&stream_id=test",
"host": [
"{{ZLMediaKit_URL}}"
],
@ -1435,7 +1497,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "dst_url",
@ -1476,7 +1538,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1507,7 +1569,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1543,7 +1605,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1574,7 +1636,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1605,7 +1667,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -1631,7 +1693,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1734,7 +1796,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1822,7 +1884,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1874,6 +1936,64 @@
}
},
"response": []
},
{
"name": "获取拉流代理信息(getProxyInfo)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/getProxyInfo?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"getProxyInfo"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
},
{
"key": "key",
"value": "__defaultVhost__/live/test"
}
]
}
},
"response": []
},
{
"name": "获取推流代理信息(getProxyPusherInfo)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/getProxyPusherInfo?secret={{ZLMediaKit_secret}}&key=rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"getProxyPusherInfo"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
},
{
"key": "key",
"value": "rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491"
}
]
}
},
"response": []
}
],
"event": [

View File

@ -34,11 +34,17 @@ if(ENABLE_SERVER_LIB)
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_link_libraries(MediaServer
PRIVATE ${MK_LINK_LIBRARIES})
update_cached(MK_LINK_LIBRARIES MediaServer)
update_cached_list(MK_LINK_LIBRARIES MediaServer)
return()
endif()
add_executable(MediaServer ${MediaServer_SRC_LIST})
# IOS
if(IOS)
add_library(MediaServer STATIC ${MediaServer_SRC_LIST})
else()
add_executable(MediaServer ${MediaServer_SRC_LIST})
endif()
target_compile_definitions(MediaServer
PRIVATE ${COMPILE_DEFINITIONS})
target_compile_options(MediaServer

View File

@ -11,6 +11,7 @@
#include "FFmpegSource.h"
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Common/MultiMediaSourceMuxer.h"
#include "Util/File.h"
#include "System.h"
#include "Thread/WorkThreadPool.h"
@ -39,7 +40,7 @@ onceToken token([]() {
//ffmpeg日志保存路径
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s";
mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -t 0.001 %s";
mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -frames:v 1 %s";
mINI::Instance()[kRestartSec] = 0;
});
}
@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){
_enable_mp4 = enable_mp4;
}
void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) {
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd);
GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url, const string &dst_url, int timeout_ms, const onPlay &cb) {
GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin);
GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd);
GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog);
_src_url = src_url;
_dst_url = dst_url;
@ -91,122 +92,114 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key);
if (cmd_it != mINI::Instance().end()) {
ffmpeg_cmd = cmd_it->second;
} else{
} else {
WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")";
}
}
char cmd[2048] = {0};
char cmd[2048] = { 0 };
snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data());
auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log);
_process.run(cmd, log_file);
InfoL << cmd;
if (is_local_ip(_media_info._host)) {
//推流给自己的,通过判断流是否注册上来判断是否正常
if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
if (is_local_ip(_media_info.host)) {
// 推流给自己的,通过判断流是否注册上来判断是否正常
if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) {
cb(SockException(Err_other, "本服务只支持rtmp/rtsp推流"));
return;
}
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
findAsync(timeout_ms,[cb,weakSelf,timeout_ms](const MediaSource::Ptr &src){
findAsync(timeout_ms, [cb, weakSelf, timeout_ms](const MediaSource::Ptr &src) {
auto strongSelf = weakSelf.lock();
if(!strongSelf){
//自己已经销毁
if (!strongSelf) {
// 自己已经销毁
return;
}
if(src){
//推流给自己成功
if (src) {
// 推流给自己成功
cb(SockException());
strongSelf->onGetMediaSource(src);
strongSelf->startTimer(timeout_ms);
return;
}
//推流失败
if(!strongSelf->_process.wait(false)){
//ffmpeg进程已经退出
cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
if (!strongSelf->_process.wait(false)) {
// ffmpeg进程已经退出
cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
return;
}
//ffmpeg进程还在线但是等待推流超时
cb(SockException(Err_other,"等待超时"));
// ffmpeg进程还在线但是等待推流超时
cb(SockException(Err_other, "等待超时"));
});
} else{
//推流给其他服务器的通过判断FFmpeg进程是否在线判断是否成功
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
_timer = std::make_shared<Timer>(timeout_ms / 1000.0f,[weakSelf,cb,timeout_ms](){
_timer = std::make_shared<Timer>(timeout_ms / 1000.0f, [weakSelf, cb, timeout_ms]() {
auto strongSelf = weakSelf.lock();
if(!strongSelf){
//自身已经销毁
if (!strongSelf) {
// 自身已经销毁
return false;
}
//FFmpeg还在线那么我们认为推流成功
if(strongSelf->_process.wait(false)){
// FFmpeg还在线那么我们认为推流成功
if (strongSelf->_process.wait(false)) {
cb(SockException());
strongSelf->startTimer(timeout_ms);
return false;
}
//ffmpeg进程已经退出
cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
// ffmpeg进程已经退出
cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
return false;
},_poller);
}, _poller);
}
}
void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSource::Ptr &src)> &cb) {
auto src = MediaSource::find(_media_info._schema,
_media_info._vhost,
_media_info._app,
_media_info._streamid);
if(src || !maxWaitMS){
auto src = MediaSource::find(_media_info.schema, _media_info.vhost, _media_info.app, _media_info.stream);
if (src || !maxWaitMS) {
cb(src);
return;
}
void *listener_tag = this;
//若干秒后执行等待媒体注册超时回调
auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){
//取消监听该事件
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
// 若干秒后执行等待媒体注册超时回调
auto onRegistTimeout = _poller->doDelayTask(maxWaitMS, [cb, listener_tag]() {
// 取消监听该事件
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
cb(nullptr);
return 0;
});
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
auto onRegist = [listener_tag,weakSelf,cb,onRegistTimeout](BroadcastMediaChangedArgs) {
auto onRegist = [listener_tag, weakSelf, cb, onRegistTimeout](BroadcastMediaChangedArgs) {
auto strongSelf = weakSelf.lock();
if(!strongSelf) {
//本身已经销毁,取消延时任务
if (!strongSelf) {
// 本身已经销毁,取消延时任务
onRegistTimeout->cancel();
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
return;
}
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) {
//不是自己感兴趣的事件,忽略之
if (!bRegist || sender.getSchema() != strongSelf->_media_info.schema ||
!equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) {
// 不是自己感兴趣的事件,忽略之
return;
}
//查找的流终于注册上了;取消延时任务,防止多次回调
// 查找的流终于注册上了;取消延时任务,防止多次回调
onRegistTimeout->cancel();
//取消事件监听
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
// 取消事件监听
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
//切换到自己的线程再回复
strongSelf->_poller->async([weakSelf,cb](){
auto strongSelf = weakSelf.lock();
if(!strongSelf) {
return;
// 切换到自己的线程再回复
strongSelf->_poller->async([weakSelf, cb]() {
if (auto strongSelf = weakSelf.lock()) {
// 再找一遍媒体源,一般能找到
strongSelf->findAsync(0, cb);
}
//再找一遍媒体源,一般能找到
strongSelf->findAsync(0,cb);
}, false);
};
//监听媒体注册事件
// 监听媒体注册事件
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
}
@ -223,50 +216,50 @@ void FFmpegSource::startTimer(int timeout_ms) {
return false;
}
bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000;
if (is_local_ip(strongSelf->_media_info._host)) {
//推流给自己的我们通过检查是否已经注册来判断FFmpeg是否工作正常
if (is_local_ip(strongSelf->_media_info.host)) {
// 推流给自己的我们通过检查是否已经注册来判断FFmpeg是否工作正常
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
//同步查找流
// 同步查找流
if (!src || needRestart) {
if(needRestart){
if (needRestart) {
strongSelf->_replay_ticker.resetTime();
if(strongSelf->_process.wait(false)){
//FFmpeg进程还在运行超时就关闭它
if (strongSelf->_process.wait(false)) {
// FFmpeg进程还在运行超时就关闭它
strongSelf->_process.kill(2000);
}
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
}
//流不在线,重新拉流, 这里原先是10秒超时实际发现10秒不够改成20秒了
if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){
//上次重试时间超过10秒那么再重试FFmpeg拉流
// 流不在线,重新拉流, 这里原先是10秒超时实际发现10秒不够改成20秒了
if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) {
// 上次重试时间超过10秒那么再重试FFmpeg拉流
strongSelf->_replay_ticker.resetTime();
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
}
}
});
} else {
//推流给其他服务器的我们通过判断FFmpeg进程是否在线如果FFmpeg推流中断那么它应该会自动退出
// 推流给其他服务器的我们通过判断FFmpeg进程是否在线如果FFmpeg推流中断那么它应该会自动退出
if (!strongSelf->_process.wait(false) || needRestart) {
if(needRestart){
if (needRestart) {
strongSelf->_replay_ticker.resetTime();
if(strongSelf->_process.wait(false)){
//FFmpeg进程还在运行超时就关闭它
if (strongSelf->_process.wait(false)) {
// FFmpeg进程还在运行超时就关闭它
strongSelf->_process.kill(2000);
}
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
}
//ffmpeg不在线重新拉流
// ffmpeg不在线重新拉流
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) {
if(!ex){
//没有错误
if (!ex) {
// 没有错误
return;
}
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
//自身已经销毁
// 自身已经销毁
return;
}
//上次重试时间超过10秒那么再重试FFmpeg拉流
// 上次重试时间超过10秒那么再重试FFmpeg拉流
strongSelf->startTimer(10 * 1000);
});
}
@ -296,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{
return MediaOriginType::ffmpeg_pull;
}
string FFmpegSource::getOriginUrl(MediaSource &sender) const{
string FFmpegSource::getOriginUrl(MediaSource &sender) const {
return _src_url;
}
std::shared_ptr<SockInfo> FFmpegSource::getOriginSock(MediaSource &sender) const {
return nullptr;
}
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
auto listener = src->getListener(true);
if (listener.lock().get() != this) {
auto muxer = src->getMuxer();
auto listener = muxer ? muxer->getDelegate() : nullptr;
if (listener && listener.get() != this) {
//防止多次进入onGetMediaSource函数导致无限递归调用的bug
setDelegate(listener);
src->setListener(shared_from_this());
muxer->setDelegate(shared_from_this());
if (_enable_hls) {
src->setupRecord(Recorder::type_hls, true, "", 0);
}
@ -320,14 +310,14 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
}
void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) {
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap);
GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin);
GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap);
GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog);
Ticker ticker;
WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){
WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url, save_path, cb, ticker]() {
auto elapsed_ms = ticker.elapsedTime();
if (elapsed_ms > timeout_sec * 1000) {
//超时,后台线程负载太高,当代太久才启动该任务
// 超时,后台线程负载太高,当代太久才启动该任务
cb(false, "wait work poller schedule snap task timeout");
return;
}
@ -348,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float
return 0;
});
//等待FFmpeg进程退出
// 等待FFmpeg进程退出
process->wait(true);
// FFmpeg进程退出了可以取消定时器了
delayTask->cancel();
//执行回调函数
// 执行回调函数
bool success = process->exit_code() == 0 && File::fileSize(save_path.data());
cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : "");
});
}

View File

@ -20,6 +20,7 @@
namespace FFmpeg {
extern const std::string kSnap;
extern const std::string kBin;
}
class FFmpegSnap {
@ -79,8 +80,6 @@ private:
mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override;
//获取媒体源url或者文件路径
std::string getOriginUrl(mediakit::MediaSource &sender) const override;
// 获取媒体源客户端相关信息
std::shared_ptr<toolkit::SockInfo> getOriginSock(mediakit::MediaSource &sender) const override;
private:
bool _enable_hls = false;

View File

@ -108,7 +108,7 @@ static int cloneFunc(void *ptr) {
#endif
void Process::run(const string &cmd, string &log_file) {
void Process::run(const string &cmd, string log_file) {
kill(2000);
#ifdef _WIN32
STARTUPINFO si = { 0 };

View File

@ -26,7 +26,7 @@ class Process {
public:
Process();
~Process();
void run(const std::string &cmd, std::string &log_file);
void run(const std::string &cmd, std::string log_file);
void kill(int max_delay,bool force = false);
bool wait(bool block = true);
int exit_code();

View File

@ -22,10 +22,11 @@
#include <map>
#include <iostream>
#include "Common/JemallocUtil.h"
#include "Common/macros.h"
#include "System.h"
#include "Util/logger.h"
#include "Util/uv_errno.h"
#include "System.h"
#include "Common/macros.h"
using namespace std;
using namespace toolkit;
@ -55,6 +56,16 @@ string System::execute(const string &cmd) {
static constexpr int MAX_STACK_FRAMES = 128;
static void save_jemalloc_stats() {
string jemalloc_status = JemallocUtil::get_malloc_stats();
if (jemalloc_status.empty()) {
return;
}
ofstream out(StrPrinter << exeDir() << "/jemalloc.json", ios::out | ios::binary | ios::trunc);
out << jemalloc_status;
out.flush();
}
static void sig_crash(int sig) {
signal(sig, SIG_DFL);
void *array[MAX_STACK_FRAMES];
@ -126,6 +137,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
exit(0);
});
signal(SIGTERM,[](int) {
WarnL << "收到主动退出信号,关闭父进程与子进程";
kill(pid, SIGINT);
exit(0);
});
do {
int status = 0;
if (waitpid(pid, &status, 0) >= 0) {
@ -143,6 +160,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
}
void System::systemSetup(){
#ifdef ENABLE_JEMALLOC_DUMP
//Save memory report when program exits
atexit(save_jemalloc_stats);
#endif //ENABLE_JEMALLOC_DUMP
#if !defined(_WIN32)
struct rlimit rlim,rlim_new;
if (getrlimit(RLIMIT_CORE, &rlim)==0) {

View File

@ -130,7 +130,7 @@ static HttpApi toApi(const function<void(API_ARGS_JSON_ASYNC)> &cb) {
//参数解析成json对象然后处理
Json::Value args;
Json::Reader reader;
reader.parse(parser.Content(), args);
reader.parse(parser.content(), args);
cb(sender, headerOut, HttpAllArgs<decltype(args)>(parser, args), val, invoker);
};
@ -152,7 +152,7 @@ static HttpApi toApi(const function<void(API_ARGS_STRING_ASYNC)> &cb) {
Json::Value val;
val["code"] = API::Success;
cb(sender, headerOut, HttpAllArgs<string>(parser, (string &)parser.Content()), val, invoker);
cb(sender, headerOut, HttpAllArgs<string>(parser, (string &)parser.content()), val, invoker);
};
}
@ -191,13 +191,13 @@ void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYN
static ApiArgsType getAllArgs(const Parser &parser) {
ApiArgsType allArgs;
if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
auto contentArgs = parser.parseArgs(parser.Content());
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) {
try {
stringstream ss(parser.Content());
stringstream ss(parser.content());
Value jsonArgs;
ss >> jsonArgs;
auto keys = jsonArgs.getMemberNames();
@ -231,7 +231,7 @@ static inline void addHttpListener(){
GET_CONFIG(bool, api_debug, API::kApiDebug);
//注册监听kBroadcastHttpRequest事件
NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
auto it = s_map_api.find(parser.Url());
auto it = s_map_api.find(parser.url());
if (it == s_map_api.end()) {
return;
}
@ -247,15 +247,15 @@ static inline void addHttpListener(){
size = body->remainSize();
}
LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__);
log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n";
LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__);
log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n";
log << "# header:\r\n";
for (auto &pr : parser.getHeader()) {
log << pr.first << " : " << pr.second << "\r\n";
}
auto &content = parser.Content();
auto &content = parser.content();
log << "# content:\r\n" << (content.size() > 4 * 1024 ? content.substr(0, 4 * 1024) : content) << "\r\n";
if (size > 0 && size < 4 * 1024) {
@ -321,12 +321,16 @@ static void fillSockInfo(Value& val, SockInfo* info) {
val["identifier"] = info->getIdentifier();
}
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item) {
item[VHOST_KEY] = tuple.vhost;
item["app"] = tuple.app;
item["stream"] = tuple.stream;
}
Value makeMediaSourceJson(MediaSource &media){
Value item;
item["schema"] = media.getSchema();
item[VHOST_KEY] = media.getVhost();
item["app"] = media.getApp();
item["stream"] = media.getId();
dumpMediaTuple(media.getMediaTuple(), item);
item["createStamp"] = (Json::UInt64) media.getCreateStamp();
item["aliveSecond"] = (Json::UInt64) media.getAliveSecond();
item["bytesSpeed"] = media.getBytesSpeed();
@ -533,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
if (s_proxyMap.find(key) != s_proxyMap.end()) {
//已经在拉流了
cb(SockException(Err_success), key);
cb(SockException(Err_other, "This stream already exists"), key);
return;
}
//添加拉流代理
@ -584,7 +588,8 @@ void installWebApi() {
//获取线程负载
//测试url http://127.0.0.1/index/api/getThreadsLoad
api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){
api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
Value val;
auto vec = EventPollerPool::Instance().getExecutorLoad();
@ -602,7 +607,8 @@ void installWebApi() {
//获取后台工作线程负载
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
Value val;
auto vec = WorkThreadPool::Instance().getExecutorLoad();
@ -648,6 +654,10 @@ void installWebApi() {
continue;
#endif
}
if (pr.first == FFmpeg::kBin) {
WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api.";
continue;
}
if (ini[pr.first] == pr.second) {
continue;
}
@ -656,7 +666,7 @@ void installWebApi() {
++changed;
}
if (changed > 0) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
ini.dumpFile(g_ini_file);
}
val["changed"] = changed;
@ -785,25 +795,40 @@ void installWebApi() {
throw ApiRetException("can not find the stream", API::NotFound);
}
src->getPlayerList(
[=](const std::list<std::shared_ptr<void>> &info_list) mutable {
[=](const std::list<toolkit::Any> &info_list) mutable {
val["code"] = API::Success;
auto &data = val["data"];
data = Value(arrayValue);
for (auto &info : info_list) {
auto obj = static_pointer_cast<Value>(info);
data.append(std::move(*obj));
auto &obj = info.get<Value>();
data.append(std::move(obj));
}
invoker(200, headerOut, val.toStyledString());
},
[](std::shared_ptr<void> &&info) -> std::shared_ptr<void> {
[](toolkit::Any &&info) -> toolkit::Any {
auto obj = std::make_shared<Value>();
auto session = static_pointer_cast<Session>(info);
fillSockInfo(*obj, session.get());
(*obj)["typeid"] = toolkit::demangle(typeid(*session).name());
return obj;
auto &sock = info.get<SockInfo>();
fillSockInfo(*obj, &sock);
(*obj)["typeid"] = toolkit::demangle(typeid(sock).name());
toolkit::Any ret;
ret.set(obj);
return ret;
});
});
api_regist("/index/api/broadcastMessage", [](API_ARGS_MAP) {
CHECK_SECRET();
CHECK_ARGS("schema", "vhost", "app", "stream", "msg");
auto src = MediaSource::find(allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]);
if (!src) {
throw ApiRetException("can not find the stream", API::NotFound);
}
Any any;
Buffer::Ptr buffer = std::make_shared<BufferLikeString>(allArgs["msg"]);
any.set(std::move(buffer));
src->broadcastMessage(any);
});
//测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){
CHECK_SECRET();
@ -1420,12 +1445,55 @@ void installWebApi() {
});
});
api_regist("/index/api/getProxyPusherInfo", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
CHECK_ARGS("key");
decltype(s_proxyPusherMap.end()) it;
{
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
it = s_proxyPusherMap.find(allArgs["key"]);
}
if (it == s_proxyPusherMap.end()) {
throw ApiRetException("can not find pusher", API::NotFound);
}
auto pusher = it->second;
val["data"]["status"] = pusher->getStatus();
val["data"]["liveSecs"] = pusher->getLiveSecs();
val["data"]["rePublishCount"] = pusher->getRePublishCount();
invoker(200, headerOut, val.toStyledString());
});
api_regist("/index/api/getProxyInfo", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
CHECK_ARGS("key");
decltype(s_proxyMap.end()) it;
{
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
it = s_proxyMap.find(allArgs["key"]);
}
if (it == s_proxyMap.end()) {
throw ApiRetException("can not find the proxy", API::NotFound);
}
auto proxy = it->second;
val["data"]["status"] = proxy->getStatus();
val["data"]["liveSecs"] = proxy->getLiveSecs();
val["data"]["rePullCount"] = proxy->getRePullCount();
invoker(200, headerOut, val.toStyledString());
});
// 删除录像文件夹
// http://127.0.0.1/index/api/deleteRecordDirectroy?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01-01
api_regist("/index/api/deleteRecordDirectory", [](API_ARGS_MAP) {
CHECK_SECRET();
CHECK_ARGS("vhost", "app", "stream");
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["customized_path"]);
auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"]};
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]);
auto period = allArgs["period"];
record_path = record_path + period + "/";
int result = File::delete_file(record_path.data());
@ -1442,7 +1510,8 @@ void installWebApi() {
api_regist("/index/api/getMp4RecordFile", [](API_ARGS_MAP){
CHECK_SECRET();
CHECK_ARGS("vhost", "app", "stream");
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["customized_path"]);
auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"]};
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]);
auto period = allArgs["period"];
//判断是获取mp4文件列表还是获取文件夹列表
@ -1519,7 +1588,7 @@ void installWebApi() {
}
//找到截图
auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg");
auto tm = findSubString(path.data() + scan_path.size(), nullptr, ".jpeg");
if (atoll(tm.data()) + expire_sec < time(NULL)) {
//截图已经过期,改名,以便再次请求时,可以返回老截图
rename(path.data(), new_snap.data());
@ -1593,7 +1662,7 @@ void installWebApi() {
CHECK_ARGS("app", "stream");
return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/"
<< _args["stream"] << "?" << _args.getParser().Params() + "&session=" + _session_id;
<< _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id;
}
private:
@ -1628,18 +1697,21 @@ void installWebApi() {
});
});
static constexpr char delete_webrtc_url [] = "/index/api/delete_webrtc";
static auto whip_whep_func = [](const char *type, API_ARGS_STRING_ASYNC) {
auto offer = allArgs.getArgs();
CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty");
WebRtcPluginManager::Instance().getAnswerSdp(static_cast<Session&>(sender), type,
WebRtcArgsImp(allArgs, sender.getIdentifier()),
[invoker, offer, headerOut](const WebRtcInterface &exchanger) mutable {
auto &session = static_cast<Session&>(sender);
auto location = std::string("http") + (session.overSsl() ? "s" : "") + "://" + allArgs["host"] + delete_webrtc_url;
WebRtcPluginManager::Instance().getAnswerSdp(session, type, WebRtcArgsImp(allArgs, sender.getIdentifier()),
[invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable {
// 设置跨域
headerOut["Access-Control-Allow-Origin"] = "*";
try {
// 设置返回类型
headerOut["Content-Type"] = "application/sdp";
headerOut["Location"] = location + "?id=" + exchanger.getIdentifier() + "&token=" + exchanger.deleteRandStr();
invoker(201, headerOut, exchangeSdp(exchanger, offer));
} catch (std::exception &ex) {
headerOut["Content-Type"] = "text/plain";
@ -1650,6 +1722,22 @@ void installWebApi() {
api_regist("/index/api/whip", [](API_ARGS_STRING_ASYNC) { whip_whep_func("push", API_ARGS_VALUE, invoker); });
api_regist("/index/api/whep", [](API_ARGS_STRING_ASYNC) { whip_whep_func("play", API_ARGS_VALUE, invoker); });
api_regist(delete_webrtc_url, [](API_ARGS_MAP_ASYNC) {
CHECK_ARGS("id", "token");
CHECK(allArgs.getParser().method() == "DELETE", "http method is not DELETE: " + allArgs.getParser().method());
auto obj = WebRtcTransportManager::Instance().getItem(allArgs["id"]);
if (!obj) {
invoker(404, headerOut, "id not found");
return;
}
if (obj->deleteRandStr() != allArgs["token"]) {
invoker(401, headerOut, "token incorrect");
return;
}
obj->safeShutdown(SockException(Err_shutdown, "deleted by http api"));
invoker(200, headerOut, "");
});
#endif
#if defined(ENABLE_VERSION)

View File

@ -44,6 +44,8 @@ typedef enum {
OtherFailed = -1,//业务代码执行失败,
Success = 0//执行成功
} ApiErr;
extern const std::string kSecret;
}//namespace API
class ApiRetException: public std::runtime_error {
@ -219,21 +221,29 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) {
throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
}
//检查http参数中是否附带secret密钥的宏127.0.0.1的ip不检查密钥
// 检查http参数中是否附带secret密钥的宏127.0.0.1的ip不检查密钥
// 同时检测是否在ip白名单内
#define CHECK_SECRET() \
if(sender.get_peer_ip() != "127.0.0.1"){ \
do { \
auto ip = sender.get_peer_ip(); \
if (!HttpFileManager::isIPAllowed(ip)) { \
throw AuthException("Your ip is not allowed to access the service."); \
} \
CHECK_ARGS("secret"); \
if(api_secret != allArgs["secret"]){ \
if (api_secret != allArgs["secret"]) { \
throw AuthException("secret错误"); \
} \
}
} while(false);
void installWebApi();
void unInstallWebApi();
uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc);
#if defined(ENABLE_RTPPROXY)
uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, bool only_audio);
void connectRtpServer(const std::string &stream_id, const std::string &dst_url, uint16_t dst_port, const std::function<void(const toolkit::SockException &ex)> &cb);
bool closeRtpServer(const std::string &stream_id);
#endif
Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
void getStatisticJson(const std::function<void(Json::Value &val)> &cb);
void addStreamProxy(const std::string &vhost, const std::string &app, const std::string &stream, const std::string &url, int retry_count,

View File

@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report";
const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm";
const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth";
const string kOnStreamChanged = HOOK_FIELD "on_stream_changed";
const string kStreamChangedSchemas = HOOK_FIELD "stream_changed_schemas";
const string kOnStreamNotFound = HOOK_FIELD "on_stream_not_found";
const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4";
const string kOnRecordTs = HOOK_FIELD "on_record_ts";
@ -44,10 +45,10 @@ 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 kOnServerExited = HOOK_FIELD "on_server_exited";
const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive";
const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped";
const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout";
const string kAdminParams = HOOK_FIELD "admin_params";
const string kAliveInterval = HOOK_FIELD "alive_interval";
const string kRetry = HOOK_FIELD "retry";
const string kRetryDelay = HOOK_FIELD "retry_delay";
@ -69,13 +70,14 @@ static onceToken token([]() {
mINI::Instance()[kOnStreamNoneReader] = "";
mINI::Instance()[kOnHttpAccess] = "";
mINI::Instance()[kOnServerStarted] = "";
mINI::Instance()[kOnServerExited] = "";
mINI::Instance()[kOnServerKeepalive] = "";
mINI::Instance()[kOnSendRtpStopped] = "";
mINI::Instance()[kOnRtpServerTimeout] = "";
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
mINI::Instance()[kAliveInterval] = 30.0;
mINI::Instance()[kRetry] = 1;
mINI::Instance()[kRetryDelay] = 3.0;
mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4";
});
} // namespace Hook
@ -100,14 +102,14 @@ static void parse_http_response(const SockException &ex, const Parser &res, cons
fun(Json::nullValue, errStr, should_retry);
return;
}
if (res.Url() != "200") {
auto errStr = StrPrinter << "[bad http status code]:" << res.Url() << endl;
if (res.status() != "200") {
auto errStr = StrPrinter << "[bad http status code]:" << res.status() << endl;
fun(Json::nullValue, errStr, should_retry);
return;
}
Value result;
try {
stringstream ss(res.Content());
stringstream ss(res.content());
ss >> result;
} catch (std::exception &ex) {
auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl;
@ -164,12 +166,16 @@ string getVhost(const HttpArgs &value) {
return val != value.end() ? val->second : "";
}
static atomic<uint64_t> s_hook_index { 0 };
void do_http_hook(const string &url, const ArgsType &body, const function<void(const Value &, const string &)> &func, uint32_t retry) {
GET_CONFIG(string, mediaServerId, General::kMediaServerId);
GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
GET_CONFIG(float, retry_delay, Hook::kRetryDelay);
const_cast<ArgsType &>(body)["mediaServerId"] = mediaServerId;
const_cast<ArgsType &>(body)["hook_index"] = s_hook_index++;
auto requester = std::make_shared<HttpRequester>();
requester->setMethod("POST");
auto bodyStr = to_string(body);
@ -213,13 +219,13 @@ void do_http_hook(const string &url, const ArgsType &body, const function<void(c
do_http_hook(url, body, func, hook_retry);
}
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item);
static ArgsType make_json(const MediaInfo &args) {
ArgsType body;
body["schema"] = args._schema;
body[VHOST_KEY] = args._vhost;
body["app"] = args._app;
body["stream"] = args._streamid;
body["params"] = args._param_strs;
body["schema"] = args.schema;
dumpMediaTuple(args, body);
body["params"] = args.param_strs;
return body;
}
@ -238,6 +244,18 @@ static void reportServerStarted() {
do_http_hook(hook_server_started, body, nullptr);
}
static void reportServerExited() {
GET_CONFIG(bool, hook_enable, Hook::kEnable);
GET_CONFIG(string, hook_server_exited, Hook::kOnServerExited);
if (!hook_enable || hook_server_exited.empty()) {
return;
}
const ArgsType body;
// 执行hook
do_http_hook(hook_server_exited, body, nullptr);
}
// 服务器定时保活定时器
static Timer::Ptr g_keepalive_timer;
static void reportServerKeepalive() {
@ -263,12 +281,12 @@ static const string kEdgeServerParam = "edge=1";
static string getPullUrl(const string &origin_fmt, const MediaInfo &info) {
char url[1024] = { 0 };
if ((ssize_t)origin_fmt.size() > snprintf(url, sizeof(url), origin_fmt.data(), info._app.data(), info._streamid.data())) {
if ((ssize_t)origin_fmt.size() > snprintf(url, sizeof(url), origin_fmt.data(), info.app.data(), info.stream.data())) {
WarnL << "get origin url failed, origin_fmt:" << origin_fmt;
return "";
}
// 告知源站这是来自边沿站的拉流请求,如果未找到流请立即返回拉流失败
return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info._vhost + '&' + info._param_strs;
return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info.vhost + '&' + info.param_strs;
}
static void pullStreamFromOrigin(const vector<string> &urls, size_t index, size_t failed_cnt, const MediaInfo &args, const function<void()> &closePlayer) {
@ -280,10 +298,10 @@ static void pullStreamFromOrigin(const vector<string> &urls, size_t index, size_
InfoL << "pull stream from origin, failed_cnt: " << failed_cnt << ", timeout_sec: " << timeout_sec << ", url: " << url;
ProtocolOption option;
option.enable_hls = option.enable_hls || (args._schema == HLS_SCHEMA);
option.enable_hls = option.enable_hls || (args.schema == HLS_SCHEMA);
option.enable_mp4 = false;
addStreamProxy(args._vhost, args._app, args._streamid, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec, [=](const SockException &ex, const string &key) mutable {
addStreamProxy(args.vhost, args.app, args.stream, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec, [=](const SockException &ex, const string &key) mutable {
if (!ex) {
return;
}
@ -317,11 +335,10 @@ static mINI jsonToMini(const Value &obj) {
void installWebHook() {
GET_CONFIG(bool, hook_enable, Hook::kEnable);
GET_CONFIG(string, hook_adminparams, Hook::kAdminParams);
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
if (!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_publish.empty()) {
invoker("", ProtocolOption());
return;
}
@ -346,7 +363,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
GET_CONFIG(string, hook_play, Hook::kOnPlay);
if (!hook_enable || args._param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_play.empty()) {
invoker("");
return;
}
@ -360,7 +377,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
if (!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_flowreport.empty()) {
return;
}
auto body = make_json(args);
@ -379,7 +396,7 @@ void installWebHook() {
// 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm);
if (!hook_enable || args._param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_rtsp_realm.empty()) {
// 无需认证
invoker("");
return;
@ -427,23 +444,37 @@ void installWebHook() {
// 监听rtsp、rtmp源注册或注销事件
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged);
if (!hook_enable || hook_stream_chaned.empty()) {
GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged);
if (!hook_enable || hook_stream_changed.empty()) {
return;
}
GET_CONFIG_FUNC(std::set<std::string>, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) {
std::set<std::string> ret;
auto vec = split(str, "/");
for (auto &schema : vec) {
trim(schema);
if (!schema.empty()) {
ret.emplace(schema);
}
}
return ret;
});
if (!stream_changed_set.empty() && stream_changed_set.find(sender.getSchema()) == stream_changed_set.end()) {
// 该协议注册注销事件被忽略
return;
}
ArgsType body;
if (bRegist) {
body = makeMediaSourceJson(sender);
body["regist"] = bRegist;
} else {
body["schema"] = sender.getSchema();
body[VHOST_KEY] = sender.getVhost();
body["app"] = sender.getApp();
body["stream"] = sender.getId();
dumpMediaTuple(sender.getMediaTuple(), body);
body["regist"] = bRegist;
}
// 执行hook
do_http_hook(hook_stream_chaned, body, nullptr);
do_http_hook(hook_stream_changed, body, nullptr);
});
GET_CONFIG_FUNC(vector<string>, origin_urls, Cluster::kOriginUrl, [](const string &str) {
@ -467,7 +498,7 @@ void installWebHook() {
return;
}
if (start_with(args._param_strs, kEdgeServerParam)) {
if (start_with(args.param_strs, kEdgeServerParam)) {
// 源站收到来自边沿站的溯源请求,流不存在时立即返回拉流失败
closePlayer();
return;
@ -503,9 +534,7 @@ void installWebHook() {
body["file_name"] = info.file_name;
body["folder"] = info.folder;
body["url"] = info.url;
body["app"] = info.app;
body["stream"] = info.stream;
body[VHOST_KEY] = info.vhost;
dumpMediaTuple(info, body);
return body;
};
@ -532,7 +561,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) {
GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin);
if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_shell_login.empty()) {
invoker("");
return;
}
@ -561,9 +590,7 @@ void installWebHook() {
ArgsType body;
body["schema"] = sender.getSchema();
body[VHOST_KEY] = sender.getVhost();
body["app"] = sender.getApp();
body["stream"] = sender.getId();
dumpMediaTuple(sender.getMediaTuple(), body);
weak_ptr<MediaSource> weakSrc = sender.shared_from_this();
// 执行hook
do_http_hook(hook_stream_none_reader, body, [weakSrc](const Value &obj, const string &err) {
@ -577,16 +604,14 @@ void installWebHook() {
});
});
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) {
GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped);
if (!hook_enable || hook_send_rtp_stopped.empty()) {
return;
}
ArgsType body;
body[VHOST_KEY] = sender.getVhost();
body["app"] = sender.getApp();
body["stream"] = sender.getStreamId();
dumpMediaTuple(sender.getMediaTuple(), body);
body["ssrc"] = ssrc;
body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource());
body["originTypeStr"] = getOriginTypeString(sender.getOriginType(MediaSource::NullMediaSource()));
@ -614,15 +639,14 @@ void installWebHook() {
// 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) {
GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess);
if (sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams) {
// 如果是本机或超级管理员访问那么不做访问鉴权权限有效期1个小时
invoker("", "", 60 * 60);
return;
}
if (!hook_enable || hook_http_access.empty()) {
// 未开启http文件访问鉴权那么允许访问但是每次访问都要鉴权
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
invoker("", "", 0);
if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) {
invoker("Your ip is not allowed to access the service.", "", 0);
} else {
invoker("", "", 0);
}
return;
}
@ -632,7 +656,7 @@ void installWebHook() {
body["id"] = sender.getIdentifier();
body["path"] = path;
body["is_dir"] = is_dir;
body["params"] = parser.Params();
body["params"] = parser.params();
for (auto &pr : parser.getHeader()) {
body[string("header.") + pr.first] = pr.second;
}
@ -650,7 +674,7 @@ void installWebHook() {
});
});
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) {
GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout);
if (!hook_enable || rtp_server_timeout.empty()) {
return;
@ -676,3 +700,7 @@ void unInstallWebHook() {
g_keepalive_timer.reset();
NoticeCenter::Instance().delListener(&web_hook_tag);
}
void onProcessExited() {
reportServerExited();
}

View File

@ -31,6 +31,7 @@ extern const std::string kTimeoutSec;
void installWebHook();
void unInstallWebHook();
void onProcessExited();
/**
* http hook请求
* @param url

View File

@ -179,6 +179,29 @@ public:
throw ExitException();
});
#endif
(*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/
"log-slice",/*该选项全称,每个选项必须有全称不得为null或空字符串*/
Option::ArgRequired,/*该选项后面必须跟值*/
"100",/*该选项默认值*/
true,/*该选项是否必须赋值如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
"最大保存日志切片个数",/*该选项说明文字*/
nullptr);
(*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/
"log-size",/*该选项全称,每个选项必须有全称不得为null或空字符串*/
Option::ArgRequired,/*该选项后面必须跟值*/
"256",/*该选项默认值*/
true,/*该选项是否必须赋值如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
"单个日志切片最大容量,单位MB",/*该选项说明文字*/
nullptr);
(*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/
"log-dir",/*该选项全称,每个选项必须有全称不得为null或空字符串*/
Option::ArgRequired,/*该选项后面必须跟值*/
(exeDir() + "log/").data(),/*该选项默认值*/
true,/*该选项是否必须赋值如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
"日志保存文件夹路径",/*该选项说明文字*/
nullptr);
}
~CMD_main() override{}
@ -213,9 +236,11 @@ int start_main(int argc,char *argv[]) {
//设置日志
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
#if !defined(ANDROID)
auto fileChannel = std::make_shared<FileChannel>("FileChannel", exeDir() + "log/", logLevel);
auto fileChannel = std::make_shared<FileChannel>("FileChannel", cmd_main["log-dir"], logLevel);
// 日志最多保存天数
fileChannel->setMaxDay(cmd_main["max_day"]);
fileChannel->setFileMaxCount(cmd_main["log-slice"]);
fileChannel->setFileMaxSize(cmd_main["log-size"]);
Logger::Instance().add(fileChannel);
#endif // !defined(ANDROID)
@ -326,6 +351,14 @@ int start_main(int argc,char *argv[]) {
#endif //defined(ENABLE_SRT)
try {
auto &secret = mINI::Instance()[API::kSecret];
if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) {
// 使用默认secret被禁止启动
secret = makeRandStr(32, true);
mINI::Instance().dumpFile(g_ini_file);
WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret
<< ", saved config file: " << g_ini_file;
}
//rtsp服务器端口默认554
if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort); }
//rtsps服务器端口默认322
@ -363,8 +396,7 @@ int start_main(int argc,char *argv[]) {
#endif//defined(ENABLE_SRT)
} catch (std::exception &ex) {
WarnL << "端口占用或无权限:" << ex.what() << endl;
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl;
ErrorL << "Start server failed: " << ex.what();
sleep(1);
#if !defined(_WIN32)
if (pid != getpid() && kill_parent_if_failed) {
@ -384,9 +416,15 @@ int start_main(int argc,char *argv[]) {
static semaphore sem;
signal(SIGINT, [](int) {
InfoL << "SIGINT:exit";
signal(SIGINT, SIG_IGN);// 设置退出信号
signal(SIGINT, SIG_IGN); // 设置退出信号
sem.post();
});// 设置退出信号
}); // 设置退出信号
signal(SIGTERM,[](int) {
WarnL << "SIGTERM:exit";
signal(SIGTERM, SIG_IGN);
sem.post();
});
#if !defined(_WIN32)
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
@ -395,6 +433,8 @@ int start_main(int argc,char *argv[]) {
}
unInstallWebApi();
unInstallWebHook();
onProcessExited();
//休眠1秒再退出防止资源释放顺序错误
InfoL << "程序退出中,请等待...";
sleep(1);

View File

@ -436,6 +436,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
av_dict_set(&dict, "zerolatency", "1", 0);
av_dict_set(&dict, "strict", "-2", 0);
#ifdef AV_CODEC_CAP_TRUNCATED
if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) {
/* we do not send complete frames */
_context->flags |= AV_CODEC_FLAG_TRUNCATED;
@ -443,6 +444,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
// 此时业务层应该需要合帧
_do_merger = true;
}
#endif
int ret = avcodec_open2(_context.get(), codec, &dict);
av_dict_free(&dict);

View File

@ -47,10 +47,8 @@ public:
using Ptr = std::shared_ptr<DevChannel>;
//fDuration<=0为直播否则为点播
DevChannel(
const std::string &vhost, const std::string &app, const std::string &stream_id, float duration = 0,
const ProtocolOption &option = ProtocolOption())
: MultiMediaSourceMuxer(vhost, app, stream_id, duration, option) {}
DevChannel(const MediaTuple& tuple, float duration = 0, const ProtocolOption &option = ProtocolOption())
: MultiMediaSourceMuxer(tuple, duration, option) {}
~DevChannel() override = default;
/**

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "JemallocUtil.h"
#include "Util/logger.h"
#ifdef USE_JEMALLOC
#include <iostream>
#include <jemalloc/jemalloc.h>
#endif
namespace mediakit {
void set_profile_active(bool active) {
#ifdef USE_JEMALLOC
int err = mallctl("prof.active", nullptr, nullptr, (void *)&active, sizeof(active));
if (err != 0) {
WarnL << "mallctl failed with: " << err;
}
#endif
}
void JemallocUtil::enable_profiling() {
set_profile_active(true);
}
void JemallocUtil::disable_profiling() {
set_profile_active(false);
}
void JemallocUtil::dump(const std::string &file_name) {
#ifdef USE_JEMALLOC
auto *c_str = file_name.c_str();
int err = mallctl("prof.dump", nullptr, nullptr, &c_str, sizeof(const char *));
if (err != 0) {
std::cerr << "mallctl failed with: " << err << std::endl;
}
#endif
}
std::string JemallocUtil::get_malloc_stats() {
#ifdef USE_JEMALLOC
std::string res;
malloc_stats_print([](void *opaque, const char *s) { ((std::string *)opaque)->append(s); }, &res, "J");
return res;
#else
return "";
#endif
}
void JemallocUtil::some_malloc_stats(const std::function<void(const char *, uint64_t)> &fn) {
#ifdef USE_JEMALLOC
constexpr std::array<const char *, 8> STATS = {
"stats.allocated", "stats.active", "stats.metadata", "stats.metadata_thp",
"stats.resident", "stats.mapped", "stats.retained", "stats.zero_reallocs",
};
for (const char *stat : STATS) {
size_t value;
size_t len = sizeof(value);
auto err = mallctl(stat, &value, &len, nullptr, 0);
if (err != 0) {
ErrorL << "Failed reading " << stat << ": " << err;
continue;
}
fn(stat, value);
}
#endif
}
} // namespace mediakit

30
src/Common/JemallocUtil.h Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_JEMALLOCUTIL_H
#define ZLMEDIAKIT_JEMALLOCUTIL_H
#include <functional>
#include <string>
namespace mediakit {
class JemallocUtil {
public:
JemallocUtil() = default;
~JemallocUtil() = default;
static void enable_profiling();
static void disable_profiling();
static void dump(const std::string &file_name);
static std::string get_malloc_stats();
static void some_malloc_stats(const std::function<void(const char *, uint64_t)> &fn);
};
} // namespace mediakit
#endif // ZLMEDIAKIT_JEMALLOCUTIL_H

View File

@ -15,8 +15,10 @@
#include "MediaSource.h"
#include "Common/config.h"
#include "Common/Parser.h"
#include "Common/MultiMediaSourceMuxer.h"
#include "Record/MP4Reader.h"
#include "PacketCache.h"
using namespace std;
using namespace toolkit;
@ -53,12 +55,14 @@ string getOriginTypeString(MediaOriginType type){
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
ProtocolOption::ProtocolOption() {
GET_CONFIG(bool, s_modify_stamp, Protocol::kModifyStamp);
GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp);
GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio);
GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio);
GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose);
GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls);
GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4);
GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4);
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() {
modify_stamp = s_modify_stamp;
enable_audio = s_enabel_audio;
add_mute_audio = s_add_mute_audio;
auto_close = s_auto_close;
continue_push_ms = s_continue_push_ms;
enable_hls = s_enable_hls;
enable_hls_fmp4 = s_enable_hls_fmp4;
enable_mp4 = s_enable_mp4;
enable_rtsp = s_enable_rtsp;
enable_rtmp = s_enable_rtmp;
@ -105,7 +111,7 @@ ProtocolOption::ProtocolOption() {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct MediaSourceNull : public MediaSource {
MediaSourceNull() : MediaSource("schema", "vhost", "app", "stream") {};
MediaSourceNull() : MediaSource("schema", MediaTuple{"vhost", "app", "stream"}) {};
int readerCount() override { return 0; }
};
@ -114,38 +120,21 @@ MediaSource &MediaSource::NullMediaSource() {
return *s_null;
}
MediaSource::MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id){
MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple(tuple) {
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost) {
_vhost = DEFAULT_VHOST;
} else {
_vhost = vhost.empty() ? DEFAULT_VHOST : vhost;
if (!enableVhost || _tuple.vhost.empty()) {
_tuple.vhost = DEFAULT_VHOST;
}
_schema = schema;
_app = app;
_stream_id = stream_id;
_create_stamp = time(NULL);
}
MediaSource::~MediaSource() {
unregist();
}
const string& MediaSource::getSchema() const {
return _schema;
}
const string& MediaSource::getVhost() const {
return _vhost;
}
const string& MediaSource::getApp() const {
//获取该源的id
return _app;
}
const string& MediaSource::getId() const {
return _stream_id;
try {
unregist();
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}
}
std::shared_ptr<void> MediaSource::getOwnership() {
@ -187,20 +176,8 @@ void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
_listener = listener;
}
std::weak_ptr<MediaSourceEvent> MediaSource::getListener(bool next) const{
if (!next) {
return _listener;
}
auto listener = dynamic_pointer_cast<MediaSourceEventInterceptor>(_listener.lock());
if (!listener) {
//不是MediaSourceEventInterceptor对象或者对象已经销毁
return _listener;
}
//获取被拦截的对象
auto next_obj = listener->getDelegate();
//有则返回之
return next_obj ? next_obj : _listener;
std::weak_ptr<MediaSourceEvent> MediaSource::getListener() const {
return _listener;
}
int MediaSource::totalReaderCount(){
@ -292,6 +269,11 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() {
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl());
}
std::shared_ptr<MultiMediaSourceMuxer> MediaSource::getMuxer() {
auto listener = _listener.lock();
return listener ? listener->getMuxer(*this) : nullptr;
}
void MediaSource::onReaderChanged(int size) {
try {
weak_ptr<MediaSource> weak_self = shared_from_this();
@ -424,7 +406,7 @@ static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, con
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &session, bool retry,
const function<void(const MediaSource::Ptr &src)> &cb){
auto src = find_l(info._schema, info._vhost, info._app, info._streamid, true);
auto src = find_l(info.schema, info.vhost, info.app, info.stream, true);
if (src || !retry) {
cb(src);
return;
@ -459,10 +441,8 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &s
weak_ptr<Session> weak_session = session;
auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) {
if (!bRegist ||
sender.getSchema() != info._schema ||
sender.getVhost() != info._vhost ||
sender.getApp() != info._app ||
sender.getId() != info._streamid) {
sender.getSchema() != info.schema ||
!equalMediaTuple(sender.getMediaTuple(), info)) {
//不是自己感兴趣的事件,忽略之
return;
}
@ -489,7 +469,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &s
});
};
//广播未找到流,此时可以立即去拉流,这样还来得及
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), close_player);
NOTICE_EMIT(BroadcastNotFoundStreamArgs, Broadcast::kBroadcastNotFoundStream, info, *session, close_player);
}
void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr<Session> &session, const function<void (const Ptr &)> &cb) {
@ -519,7 +499,7 @@ void MediaSource::emitEvent(bool regist){
listener->onRegist(*this, regist);
}
//触发广播
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this);
NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this);
InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl();
}
@ -527,7 +507,7 @@ void MediaSource::regist() {
{
//减小互斥锁临界区
lock_guard<recursive_mutex> lock(s_media_source_mtx);
auto &ref = s_media_source_map[_schema][_vhost][_app][_stream_id];
auto &ref = s_media_source_map[_schema][_tuple.vhost][_tuple.app][_tuple.stream];
auto src = ref.lock();
if (src) {
if (src.get() == this) {
@ -570,7 +550,7 @@ bool MediaSource::unregist() {
{
//减小互斥锁临界区
lock_guard<recursive_mutex> lock(s_media_source_mtx);
erase_media_source(ret, this, s_media_source_map, _schema, _vhost, _app, _stream_id);
erase_media_source(ret, this, s_media_source_map, _schema, _tuple.vhost, _tuple.app, _tuple.stream);
}
if (ret) {
@ -579,34 +559,37 @@ bool MediaSource::unregist() {
return ret;
}
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b) {
return a.vhost == b.vhost && a.app == b.app && a.stream == b.stream;
}
/////////////////////////////////////MediaInfo//////////////////////////////////////
void MediaInfo::parse(const std::string &url_in){
_full_url = url_in;
full_url = url_in;
auto url = url_in;
auto pos = url.find("?");
if (pos != string::npos) {
_param_strs = url.substr(pos + 1);
param_strs = url.substr(pos + 1);
url.erase(pos);
}
auto schema_pos = url.find("://");
if (schema_pos != string::npos) {
_schema = url.substr(0, schema_pos);
schema = url.substr(0, schema_pos);
} else {
schema_pos = -3;
}
auto split_vec = split(url.substr(schema_pos + 3), "/");
if (split_vec.size() > 0) {
splitUrl(split_vec[0], _host, _port);
_vhost = _host;
if (_vhost == "localhost" || isIP(_vhost.data())) {
splitUrl(split_vec[0], host, port);
vhost = host;
if (vhost == "localhost" || isIP(vhost.data())) {
//如果访问的是localhost或ip那么则为默认虚拟主机
_vhost = DEFAULT_VHOST;
vhost = DEFAULT_VHOST;
}
}
if (split_vec.size() > 1) {
_app = split_vec[1];
app = split_vec[1];
}
if (split_vec.size() > 2) {
string stream_id;
@ -616,18 +599,18 @@ void MediaInfo::parse(const std::string &url_in){
if (stream_id.back() == '/') {
stream_id.pop_back();
}
_streamid = stream_id;
stream = stream_id;
}
auto params = Parser::parseArgs(_param_strs);
auto params = Parser::parseArgs(param_strs);
if (params.find(VHOST_KEY) != params.end()) {
_vhost = params[VHOST_KEY];
vhost = params[VHOST_KEY];
}
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost || _vhost.empty()) {
if (!enableVhost || vhost.empty()) {
//如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
_vhost = DEFAULT_VHOST;
vhost = DEFAULT_VHOST;
}
}
@ -663,7 +646,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
GET_CONFIG(string, record_app, Record::kAppName);
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
//如果mp4点播, 无人观看时我们强制关闭点播
bool is_mp4_vod = sender.getApp() == record_app;
bool is_mp4_vod = sender.getMediaTuple().app == record_app;
weak_ptr<MediaSource> weak_sender = sender.shared_from_this();
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() {
@ -679,8 +662,15 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
}
if (!is_mp4_vod) {
//直播时触发无人观看事件,让开发者自行选择是否关闭
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
auto muxer = strong_sender->getMuxer();
if (muxer && muxer->getOption().auto_close) {
// 此流被标记为无人观看自动关闭流
WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl();
strong_sender->close(false);
} else {
// 直播时触发无人观看事件,让开发者自行选择是否关闭
NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender);
}
} else {
//这个是mp4点播我们自动关闭
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
@ -794,6 +784,11 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed");
}
std::shared_ptr<MultiMediaSourceMuxer> MediaSourceEventInterceptor::getMuxer(MediaSource &sender) {
auto listener = _listener.lock();
return listener ? listener->getMuxer(sender) : nullptr;
}
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
auto listener = _listener.lock();
if (!listener) {

View File

@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t {
std::string getOriginTypeString(MediaOriginType type);
class MediaSource;
class MultiMediaSourceMuxer;
class MediaSourceEvent {
public:
friend class MediaSource;
@ -88,6 +89,8 @@ public:
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }
// 获取所有track相关信息
virtual std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
// 获取MultiMediaSourceMuxer对象
virtual std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) { return nullptr; }
class SendRtpArgs {
public:
@ -136,17 +139,30 @@ class ProtocolOption {
public:
ProtocolOption();
//时间戳修复这一路流标志位
bool modify_stamp;
enum {
kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变
kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理)
kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
};
// 时间戳类型
int modify_stamp;
//转协议是否开启音频
bool enable_audio;
//添加静音音频,在关闭音频时,此开关无效
bool add_mute_audio;
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
// 此配置置1时此流如果无人观看将不触发on_none_reader hook回调
// 而是将直接关闭流
bool auto_close;
//断连续推延时,单位毫秒,默认采用配置文件
uint32_t continue_push_ms;
//是否开启转换为hls
//是否开启转换为hls(mpegts)
bool enable_hls;
//是否开启转换为hls(fmp4)
bool enable_hls_fmp4;
//是否开启MP4录制
bool enable_mp4;
//是否开启转换为rtsp/webrtc
@ -179,15 +195,20 @@ public:
//hls录制保存路径
std::string hls_save_path;
// 支持通过on_publish返回值替换stream_id
std::string stream_replace;
template <typename MAP>
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
GET_OPT_VALUE(modify_stamp);
GET_OPT_VALUE(enable_audio);
GET_OPT_VALUE(add_mute_audio);
GET_OPT_VALUE(auto_close);
GET_OPT_VALUE(continue_push_ms);
GET_OPT_VALUE(enable_hls);
GET_OPT_VALUE(enable_hls_fmp4);
GET_OPT_VALUE(enable_mp4);
GET_OPT_VALUE(enable_rtsp);
GET_OPT_VALUE(enable_rtmp);
@ -205,6 +226,7 @@ public:
GET_OPT_VALUE(mp4_save_path);
GET_OPT_VALUE(hls_save_path);
GET_OPT_VALUE(stream_replace);
}
private:
@ -244,6 +266,7 @@ public:
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
float getLossRate(MediaSource &sender, TrackType type) override;
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
private:
std::weak_ptr<MediaSourceEvent> _listener;
@ -252,26 +275,24 @@ private:
/**
* url获取媒体相关信息
*/
class MediaInfo {
class MediaInfo: public MediaTuple {
public:
~MediaInfo() = default;
MediaInfo() = default;
MediaInfo(const std::string &url) { parse(url); }
void parse(const std::string &url);
std::string shortUrl() const { return _vhost + "/" + _app + "/" + _streamid; }
std::string getUrl() const { return _schema + "://" + shortUrl(); }
std::string getUrl() const { return schema + "://" + shortUrl(); }
public:
uint16_t _port = 0;
std::string _full_url;
std::string _schema;
std::string _host;
std::string _vhost;
std::string _app;
std::string _streamid;
std::string _param_strs;
uint16_t port = 0;
std::string full_url;
std::string schema;
std::string host;
std::string param_strs;
};
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b);
/**
* rtsp/rtmp的直播流都源自该对象
*/
@ -280,23 +301,21 @@ public:
static MediaSource& NullMediaSource();
using Ptr = std::shared_ptr<MediaSource>;
MediaSource(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &stream_id);
MediaSource(const std::string &schema, const MediaTuple& tuple);
virtual ~MediaSource();
////////////////获取MediaSource相关信息////////////////
// 获取协议类型
const std::string& getSchema() const;
// 虚拟主机
const std::string& getVhost() const;
// 应用名
const std::string& getApp() const;
// 流id
const std::string& getId() const;
const std::string& getSchema() const {
return _schema;
}
std::string shortUrl() const { return _vhost + "/" + _app + "/" + _stream_id; }
const MediaTuple& getMediaTuple() const {
return _tuple;
}
std::string getUrl() const { return _schema + "://" + shortUrl(); }
std::string getUrl() const { return _schema + "://" + _tuple.shortUrl(); }
//获取对象所有权
std::shared_ptr<void> getOwnership();
@ -321,19 +340,21 @@ public:
// 设置监听者
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
// 获取监听者
std::weak_ptr<MediaSourceEvent> getListener(bool next = false) const;
std::weak_ptr<MediaSourceEvent> getListener() const;
// 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数
virtual int readerCount() = 0;
// 观看者个数,包括(hls/rtsp/rtmp)
virtual int totalReaderCount();
// 获取播放器列表
virtual void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) {
virtual void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) {
assert(cb);
cb(std::list<std::shared_ptr<void>>());
cb(std::list<toolkit::Any>());
}
virtual bool broadcastMessage(const toolkit::Any &data) { return false; }
// 获取媒体源类型
MediaOriginType getOriginType() const;
// 获取媒体源url或者文件路径
@ -363,13 +384,15 @@ public:
float getLossRate(mediakit::TrackType type);
// 获取所在线程
toolkit::EventPoller::Ptr getOwnerPoller();
// 获取MultiMediaSourceMuxer对象
std::shared_ptr<MultiMediaSourceMuxer> getMuxer();
////////////////static方法查找或生成MediaSource////////////////
// 同步查找流
static Ptr find(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &id, bool from_mp4 = false);
static Ptr find(const MediaInfo &info, bool from_mp4 = false) {
return find(info._schema, info._vhost, info._app, info._streamid, from_mp4);
return find(info.schema, info.vhost, info.app, info.stream, from_mp4);
}
// 忽略schema同步查找流可能返回rtmp/rtsp/hls类型
@ -394,15 +417,13 @@ private:
protected:
toolkit::BytesSpeed _speed[TrackMax];
MediaTuple _tuple;
private:
std::atomic_flag _owned { false };
time_t _create_stamp;
toolkit::Ticker _ticker;
std::string _schema;
std::string _vhost;
std::string _app;
std::string _stream_id;
std::weak_ptr<MediaSourceEvent> _listener;
// 对象个数统计
toolkit::ObjectStatistic<MediaSource> _statistic;

View File

@ -25,7 +25,7 @@ namespace {
class MediaSourceForMuxer : public MediaSource {
public:
MediaSourceForMuxer(const MultiMediaSourceMuxer::Ptr &muxer)
: MediaSource("muxer", muxer->getVhost(), muxer->getApp(), muxer->getStreamId()) {
: MediaSource("muxer", muxer->getMediaTuple()) {
MediaSource::setListener(muxer);
}
int readerCount() override { return 0; }
@ -33,10 +33,11 @@ public:
} // namespace
static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, const vector<Track::Ptr> &tracks, Recorder::type type, const ProtocolOption &option){
auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId(), option);
auto recorder = Recorder::createRecorder(type, sender.getMediaTuple(), option);
for (auto &track : tracks) {
recorder->addTrack(track);
}
recorder->addTrackCompleted();
return recorder;
}
@ -70,16 +71,12 @@ static string getTrackInfoStr(const TrackSource *track_src){
return std::move(codec_info);
}
const std::string &MultiMediaSourceMuxer::getVhost() const {
return _vhost;
const ProtocolOption &MultiMediaSourceMuxer::getOption() const {
return _option;
}
const std::string &MultiMediaSourceMuxer::getApp() const {
return _app;
}
const std::string &MultiMediaSourceMuxer::getStreamId() const {
return _stream_id;
const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const {
return _tuple;
}
std::string MultiMediaSourceMuxer::shortUrl() const {
@ -87,37 +84,44 @@ std::string MultiMediaSourceMuxer::shortUrl() const {
if (!ret.empty()) {
return ret;
}
return _vhost + "/" + _app + "/" + _stream_id;
return _tuple.shortUrl();
}
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec, const ProtocolOption &option) {
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) {
if (!option.stream_replace.empty()) {
// 支持在on_publish hook中替换stream_id
_tuple.stream = option.stream_replace;
}
_poller = EventPollerPool::Instance().getPoller();
_create_in_poller = _poller->isCurrentThread();
_vhost = vhost;
_app = app;
_stream_id = stream;
_option = option;
if (dur_sec > 0.01) {
// 点播
_stamp[TrackVideo].setPlayBack();
_stamp[TrackAudio].setPlayBack();
}
if (option.enable_rtmp) {
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, option, std::make_shared<TitleMeta>(dur_sec));
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(_tuple, option, std::make_shared<TitleMeta>(dur_sec));
}
if (option.enable_rtsp) {
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, option, std::make_shared<TitleSdp>(dur_sec));
_rtsp = std::make_shared<RtspMediaSourceMuxer>(_tuple, option, std::make_shared<TitleSdp>(dur_sec));
}
if (option.enable_hls) {
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream, option));
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option));
}
if (option.enable_hls_fmp4) {
_hls_fmp4 = dynamic_pointer_cast<HlsFMP4Recorder>(Recorder::createRecorder(Recorder::type_hls_fmp4, _tuple, option));
}
if (option.enable_mp4) {
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream, option);
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
}
if (option.enable_ts) {
_ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream, option);
_ts = dynamic_pointer_cast<TSMediaSourceMuxer>(Recorder::createRecorder(Recorder::type_ts, _tuple, option));
}
#if defined(ENABLE_MP4)
if (option.enable_fmp4) {
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream, option);
_fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option));
}
#endif
//音频相关设置
enableAudio(option.enable_audio);
@ -138,14 +142,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven
if (_ts) {
_ts->setListener(self);
}
#if defined(ENABLE_MP4)
if (_fmp4) {
_fmp4->setListener(self);
}
#endif
auto hls = _hls;
if (hls) {
hls->setListener(self);
if (_hls_fmp4) {
_hls_fmp4->setListener(self);
}
if (_hls) {
_hls->setListener(self);
}
}
@ -158,15 +162,13 @@ std::weak_ptr<MultiMediaSourceMuxer::Listener> MultiMediaSourceMuxer::getTrackLi
}
int MultiMediaSourceMuxer::totalReaderCount() const {
auto hls = _hls;
return (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) +
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->readerCount() : 0) +
#endif
(_mp4 ? _option.mp4_as_player : 0) +
(hls ? hls->readerCount() : 0) +
(_hls ? _hls->readerCount() : 0) +
(_hls_fmp4 ? _hls_fmp4->readerCount() : 0) +
(_ring ? _ring->readerCount() : 0);
}
@ -194,6 +196,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
//此函数可能跨线程调用
bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread(), "Can only call setupRecord in it's owner poller");
onceToken token(nullptr, [&]() {
if (_option.mp4_as_player && type == Recorder::type_mp4) {
//开启关闭mp4录制触发观看人数变化相关事件
@ -229,19 +232,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
}
return true;
}
case Recorder::type_hls_fmp4: {
if (start && !_hls_fmp4) {
//开始录制
_option.hls_save_path = custom_path;
auto hls = dynamic_pointer_cast<HlsFMP4Recorder>(makeRecorder(sender, getTracks(), type, _option));
if (hls) {
//设置HlsMediaSource的事件监听器
hls->setListener(shared_from_this());
}
_hls_fmp4 = hls;
} else if (!start && _hls_fmp4) {
//停止录制
_hls_fmp4 = nullptr;
}
return true;
}
case Recorder::type_fmp4: {
if (start && !_fmp4) {
auto fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(makeRecorder(sender, getTracks(), type, _option));
if (fmp4) {
fmp4->setListener(shared_from_this());
}
_fmp4 = fmp4;
} else if (!start && _fmp4) {
_fmp4 = nullptr;
}
return true;
}
case Recorder::type_ts: {
if (start && !_ts) {
auto ts = dynamic_pointer_cast<TSMediaSourceMuxer>(makeRecorder(sender, getTracks(), type, _option));
if (ts) {
ts->setListener(shared_from_this());
}
_ts = ts;
} else if (!start && _ts) {
_ts = nullptr;
}
return true;
}
default : return false;
}
}
//此函数可能跨线程调用
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
switch (type){
case Recorder::type_hls :
return !!_hls;
case Recorder::type_mp4 :
return !!_mp4;
default:
return false;
switch (type) {
case Recorder::type_hls: return !!_hls;
case Recorder::type_mp4: return !!_mp4;
case Recorder::type_hls_fmp4: return !!_hls_fmp4;
case Recorder::type_fmp4: return !!_fmp4;
case Recorder::type_ts: return !!_ts;
default: return false;
}
}
@ -252,10 +295,11 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE
auto ring = _ring;
auto ssrc = args.ssrc;
auto tracks = getTracks(false);
auto rtp_sender = std::make_shared<RtpSender>(getOwnerPoller(sender));
auto poller = getOwnerPoller(sender);
auto rtp_sender = std::make_shared<RtpSender>(poller);
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
rtp_sender->startSend(args, [ssrc, weak_self, rtp_sender, cb, tracks, ring](uint16_t local_port, const SockException &ex) mutable {
rtp_sender->startSend(args, [ssrc, weak_self, rtp_sender, cb, tracks, ring, poller](uint16_t local_port, const SockException &ex) mutable {
cb(local_port, ex);
auto strong_self = weak_self.lock();
if (!strong_self || ex) {
@ -272,12 +316,12 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex;
strong_self->_rtp_sender.erase(ssrc);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
});
}
});
auto reader = ring->attach(EventPoller::getCurrentPoller());
auto reader = ring->attach(poller);
reader->setReadCB([rtp_sender](const Frame::Ptr &frame) {
rtp_sender->inputFrame(frame);
});
@ -319,7 +363,7 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
try {
auto ret = listener->getOwnerPoller(sender);
if (ret != _poller) {
WarnL << "OwnerPoller changed:" << shortUrl();
WarnL << "OwnerPoller changed " << _poller->getThreadName() << " -> " << ret->getThreadName() << " : " << shortUrl();
_poller = ret;
}
return ret;
@ -329,8 +373,11 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
}
}
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
std::shared_ptr<MultiMediaSourceMuxer> MultiMediaSourceMuxer::getMuxer(MediaSource &sender) {
return shared_from_this();
}
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
bool ret = false;
if (_rtmp) {
ret = _rtmp->addTrack(track) ? true : ret;
@ -341,20 +388,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
if (_ts) {
ret = _ts->addTrack(track) ? true : ret;
}
#if defined(ENABLE_MP4)
if (_fmp4) {
ret = _fmp4->addTrack(track) ? true : ret;
}
#endif
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls;
if (hls) {
ret = hls->addTrack(track) ? true : ret;
if (_hls) {
ret = _hls->addTrack(track) ? true : ret;
}
auto mp4 = _mp4;
if (mp4) {
ret = mp4->addTrack(track) ? true : ret;
if (_hls_fmp4) {
ret = _hls_fmp4->addTrack(track) ? true : ret;
}
if (_mp4) {
ret = _mp4->addTrack(track) ? true : ret;
}
return ret;
}
@ -364,16 +408,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
setMediaListener(getDelegate());
if (_rtmp) {
_rtmp->onAllTrackReady();
_rtmp->addTrackCompleted();
}
if (_rtsp) {
_rtsp->onAllTrackReady();
_rtsp->addTrackCompleted();
}
if (_ts) {
_ts->addTrackCompleted();
}
if (_mp4) {
_mp4->addTrackCompleted();
}
#if defined(ENABLE_MP4)
if (_fmp4) {
_fmp4->onAllTrackReady();
_fmp4->addTrackCompleted();
}
#endif
if (_hls) {
_hls->addTrackCompleted();
}
if (_hls_fmp4) {
_hls_fmp4->addTrackCompleted();
}
auto listener = _track_listener.lock();
if (listener) {
listener->onAllTrackReady();
@ -385,6 +440,11 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
createGopCacheIfNeed();
}
#endif
auto tracks = getTracks(false);
if (tracks.size() >= 2) {
// 音频时间戳同步于视频,因为音频时间戳被修改后不影响播放
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
}
InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this);
}
@ -416,29 +476,25 @@ void MultiMediaSourceMuxer::resetTracks() {
if (_ts) {
_ts->resetTracks();
}
#if defined(ENABLE_MP4)
if (_fmp4) {
_fmp4->resetTracks();
}
#endif
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls;
if (hls) {
hls->resetTracks();
if (_hls_fmp4) {
_hls_fmp4->resetTracks();
}
auto mp4 = _mp4;
if (mp4) {
mp4->resetTracks();
if (_hls) {
_hls->resetTracks();
}
if (_mp4) {
_mp4->resetTracks();
}
}
bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
auto frame = frame_in;
if (_option.modify_stamp) {
//开启了时间戳覆盖
frame = std::make_shared<FrameStamp>(frame, _stamp[frame->getTrackType()],true);
if (_option.modify_stamp != ProtocolOption::kModifyStampOff) {
// 时间戳不采用原始的绝对时间戳
frame = std::make_shared<FrameStamp>(frame, _stamp[frame->getTrackType()], _option.modify_stamp);
}
bool ret = false;
@ -452,29 +508,28 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
ret = _ts->inputFrame(frame) ? true : ret;
}
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
auto hls = _hls;
if (hls) {
ret = hls->inputFrame(frame) ? true : ret;
}
auto mp4 = _mp4;
if (mp4) {
ret = mp4->inputFrame(frame) ? true : ret;
if (_hls) {
ret = _hls->inputFrame(frame) ? true : ret;
}
#if defined(ENABLE_MP4)
if (_hls_fmp4) {
ret = _hls_fmp4->inputFrame(frame) ? true : ret;
}
if (_mp4) {
ret = _mp4->inputFrame(frame) ? true : ret;
}
if (_fmp4) {
ret = _fmp4->inputFrame(frame) ? true : ret;
}
#endif
if (_ring) {
if (frame->getTrackType() == TrackVideo) {
// 视频时遇到第一帧配置帧或关键帧则标记为gop开始处
auto video_key_pos = frame->keyFrame() || frame->configFrame();
_ring->write(frame, video_key_pos && !_video_key_pos);
_video_key_pos = video_key_pos;
if (!frame->dropAble()) {
_video_key_pos = video_key_pos;
}
} else {
// 没有视频时设置is_key为true目的是关闭gop缓存
_ring->write(frame, !haveVideo());
@ -488,15 +543,14 @@ bool MultiMediaSourceMuxer::isEnabled(){
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
//无人观看时,每次检查是否真的无人观看
//有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能)
auto hls = _hls;
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
(_rtsp ? _rtsp->isEnabled() : false) ||
(_ts ? _ts->isEnabled() : false) ||
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->isEnabled() : false) ||
#endif
(_ring ? (bool)_ring->readerCount() : false) ||
(hls ? hls->isEnabled() : false) || _mp4;
(_hls ? _hls->isEnabled() : false) ||
(_hls_fmp4 ? _hls_fmp4->isEnabled() : false) ||
_mp4;
if (_is_enable) {
//无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍所以刷新计数器无意义且浪费cpu

View File

@ -37,7 +37,7 @@ public:
virtual void onAllTrackReady() = 0;
};
MultiMediaSourceMuxer(const std::string &vhost, const std::string &app, const std::string &stream, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption());
MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption());
~MultiMediaSourceMuxer() override = default;
/**
@ -131,9 +131,13 @@ public:
*/
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
const std::string& getVhost() const;
const std::string& getApp() const;
const std::string& getStreamId() const;
/**
*
*/
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
const ProtocolOption &getOption() const;
const MediaTuple &getMediaTuple() const;
std::string shortUrl() const;
protected:
@ -164,25 +168,19 @@ private:
bool _is_enable = false;
bool _create_in_poller = false;
bool _video_key_pos = false;
std::string _vhost;
std::string _app;
std::string _stream_id;
MediaTuple _tuple;
ProtocolOption _option;
toolkit::Ticker _last_check;
Stamp _stamp[2];
std::weak_ptr<Listener> _track_listener;
#if defined(ENABLE_RTPPROXY)
std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender;
#endif //ENABLE_RTPPROXY
#if defined(ENABLE_MP4)
FMP4MediaSourceMuxer::Ptr _fmp4;
#endif
RtmpMediaSourceMuxer::Ptr _rtmp;
RtspMediaSourceMuxer::Ptr _rtsp;
TSMediaSourceMuxer::Ptr _ts;
MediaSinkInterface::Ptr _mp4;
HlsRecorder::Ptr _hls;
HlsFMP4Recorder::Ptr _hls_fmp4;
toolkit::EventPoller::Ptr _poller;
RingType::Ptr _ring;

View File

@ -10,19 +10,22 @@
#include <cinttypes>
#include "Parser.h"
#include "strCoding.h"
#include "macros.h"
#include "Network/sockutil.h"
#include "Common/macros.h"
using namespace std;
using namespace toolkit;
namespace mediakit{
namespace mediakit {
string FindField(const char* buf, const char* start, const char *end ,size_t bufSize) {
if(bufSize <=0 ){
bufSize = strlen(buf);
string findSubString(const char *buf, const char *start, const char *end, size_t buf_size) {
if (buf_size <= 0) {
buf_size = strlen(buf);
}
const char *msg_start = buf, *msg_end = buf + bufSize;
auto msg_start = buf;
auto msg_end = buf + buf_size;
size_t len = 0;
if (start != NULL) {
len = strlen(start);
@ -41,126 +44,147 @@ string FindField(const char* buf, const char* start, const char *end ,size_t buf
return string(msg_start, msg_end);
}
void Parser::Parse(const char *buf) {
//解析
const char *start = buf;
Clear();
void Parser::parse(const char *buf, size_t size) {
clear();
auto ptr = buf;
while (true) {
auto line = FindField(start, NULL, "\r\n");
if (line.size() == 0) {
break;
auto next_line = strchr(ptr, '\n');
auto offset = 1;
CHECK(next_line && next_line > ptr);
if (*(next_line - 1) == '\r') {
next_line -= 1;
offset = 2;
}
if (start == buf) {
_strMethod = FindField(line.data(), NULL, " ");
auto strFullUrl = FindField(line.data(), " ", " ");
auto args_pos = strFullUrl.find('?');
if (args_pos != string::npos) {
_strUrl = strFullUrl.substr(0, args_pos);
_params = strFullUrl.substr(args_pos + 1);
_mapUrlArgs = parseArgs(_params);
} else {
_strUrl = strFullUrl;
if (ptr == buf) {
auto blank = strchr(ptr, ' ');
CHECK(blank > ptr && blank < next_line);
_method = std::string(ptr, blank - ptr);
auto next_blank = strchr(blank + 1, ' ');
CHECK(next_blank && next_blank < next_line);
_url.assign(blank + 1, next_blank);
auto pos = _url.find('?');
if (pos != string::npos) {
_params = _url.substr(pos + 1);
_url_args = parseArgs(_params);
_url = _url.substr(0, pos);
}
_strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL);
_protocol = std::string(next_blank + 1, next_line);
} else {
auto field = FindField(line.data(), NULL, ": ");
auto value = FindField(line.data(), ": ", NULL);
if (field.size() != 0) {
_mapHeaders.emplace_force(field, value);
auto pos = strchr(ptr, ':');
CHECK(pos > ptr && pos < next_line);
std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
std::string value;
if (pos[1] == ' ') {
value.assign(pos + 2, next_line);
} else {
value.assign(pos + 1, next_line);
}
_headers.emplace_force(trim(std::move(key)), trim(std::move(value)));
}
start = start + line.size() + 2;
if (strncmp(start, "\r\n", 2) == 0) { //协议解析完毕
_strContent = FindField(start, "\r\n", NULL);
ptr = next_line + offset;
if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕
_content.assign(ptr + 2, buf + size);
break;
}
}
}
const string &Parser::Method() const {
return _strMethod;
const string &Parser::method() const {
return _method;
}
const string &Parser::Url() const {
return _strUrl;
const string &Parser::url() const {
return _url;
}
string Parser::FullUrl() const {
const std::string &Parser::status() const {
return url();
}
string Parser::fullUrl() const {
if (_params.empty()) {
return _strUrl;
return _url;
}
return _strUrl + "?" + _params;
return _url + "?" + _params;
}
const string &Parser::Tail() const {
return _strTail;
const string &Parser::protocol() const {
return _protocol;
}
const std::string &Parser::statusStr() const {
return protocol();
}
static std::string kNull;
const string &Parser::operator[](const char *name) const {
auto it = _mapHeaders.find(name);
if (it == _mapHeaders.end()) {
return _strNull;
auto it = _headers.find(name);
if (it == _headers.end()) {
return kNull;
}
return it->second;
}
const string &Parser::Content() const {
return _strContent;
const string &Parser::content() const {
return _content;
}
void Parser::Clear() {
_strMethod.clear();
_strUrl.clear();
void Parser::clear() {
_method.clear();
_url.clear();
_params.clear();
_strTail.clear();
_strContent.clear();
_mapHeaders.clear();
_mapUrlArgs.clear();
_protocol.clear();
_content.clear();
_headers.clear();
_url_args.clear();
}
const string &Parser::Params() const {
const string &Parser::params() const {
return _params;
}
void Parser::setUrl(string url) {
this->_strUrl = std::move(url);
_url = std::move(url);
}
void Parser::setContent(string content) {
this->_strContent = std::move(content);
_content = std::move(content);
}
StrCaseMap &Parser::getHeader() const {
return _mapHeaders;
return _headers;
}
StrCaseMap &Parser::getUrlArgs() const {
return _mapUrlArgs;
return _url_args;
}
StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) {
StrCaseMap ret;
auto arg_vec = split(str, pair_delim);
for (string &key_val : arg_vec) {
for (auto &key_val : arg_vec) {
if (key_val.empty()) {
//忽略
// 忽略
continue;
}
auto key = trim(FindField(key_val.data(), NULL, key_delim));
if (!key.empty()) {
auto val = trim(FindField(key_val.data(), key_delim, NULL));
ret.emplace_force(key, val);
auto pos = key_val.find(key_delim);
if (pos != string::npos) {
auto key = trim(std::string(key_val, 0, pos));
auto val = trim(key_val.substr(pos + strlen(key_delim)));
ret.emplace_force(std::move(key), std::move(val));
} else {
trim(key_val);
if (!key_val.empty()) {
ret.emplace_force(key_val, "");
ret.emplace_force(std::move(key_val), "");
}
}
}
return ret;
}
std::string Parser::merge_url(const string &base_url, const string &path) {
//以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径
std::string Parser::mergeUrl(const string &base_url, const string &path) {
// 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径
if (base_url.empty()) {
return path;
}
@ -234,43 +258,45 @@ std::string Parser::merge_url(const string &base_url, const string &path) {
}
return final_url.str();
}
void RtspUrl::parse(const string &strUrl) {
auto schema = FindField(strUrl.data(), nullptr, "://");
auto schema = findSubString(strUrl.data(), nullptr, "://");
bool is_ssl = strcasecmp(schema.data(), "rtsps") == 0;
//查找"://"与"/"之间的字符串,用于提取用户名密码
auto middle_url = FindField(strUrl.data(), "://", "/");
// 查找"://"与"/"之间的字符串,用于提取用户名密码
auto middle_url = findSubString(strUrl.data(), "://", "/");
if (middle_url.empty()) {
middle_url = FindField(strUrl.data(), "://", nullptr);
middle_url = findSubString(strUrl.data(), "://", nullptr);
}
auto pos = middle_url.rfind('@');
if (pos == string::npos) {
//并没有用户名密码
// 并没有用户名密码
return setup(is_ssl, strUrl, "", "");
}
//包含用户名密码
// 包含用户名密码
auto user_pwd = middle_url.substr(0, pos);
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
auto url = StrPrinter << "rtsp://" << suffix << endl;
if (user_pwd.find(":") == string::npos) {
return setup(is_ssl, url, user_pwd, "");
}
auto user = FindField(user_pwd.data(), nullptr, ":");
auto pwd = FindField(user_pwd.data(), ":", nullptr);
auto user = findSubString(user_pwd.data(), nullptr, ":");
auto pwd = findSubString(user_pwd.data(), ":", nullptr);
return setup(is_ssl, url, user, pwd);
}
void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const string &passwd) {
auto ip = FindField(url.data(), "://", "/");
auto ip = findSubString(url.data(), "://", "/");
if (ip.empty()) {
ip = split(FindField(url.data(), "://", NULL), "?")[0];
ip = split(findSubString(url.data(), "://", NULL), "?")[0];
}
uint16_t port = is_ssl ? 322 : 554;
splitUrl(ip, ip, port);
_url = std::move(url);
_user = std::move(user);
_passwd = std::move(passwd);
_user = strCoding::UrlDecode(std::move(user));
_passwd = strCoding::UrlDecode(std::move(passwd));
_host = std::move(ip);
_port = port;
_is_ssl = is_ssl;
@ -289,7 +315,7 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
CHECK(!url.empty(), "empty url");
auto pos = url.rfind(':');
if (pos == string::npos || url.back() == ']') {
//没有冒号,未指定端口;或者是纯粹的ipv6地址
// 没有冒号,未指定端口;或者是纯粹的ipv6地址
host = url;
checkHost(host);
return;
@ -299,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
host = url.substr(0, pos);
checkHost(host);
}
#if 0
//测试代码
static onceToken token([](){
@ -312,4 +337,4 @@ static onceToken token([](){
});
#endif
}//namespace mediakit
} // namespace mediakit

View File

@ -17,15 +17,13 @@
namespace mediakit {
//从字符串中提取子字符串
std::string FindField(const char *buf, const char *start, const char *end, size_t bufSize = 0);
//把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
void splitUrl(const std::string &url, std::string &host, uint16_t& port);
// 从字符串中提取子字符串
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
void splitUrl(const std::string &url, std::string &host, uint16_t &port);
struct StrCaseCompare {
bool operator()(const std::string &__x, const std::string &__y) const {
return strcasecmp(__x.data(), __y.data()) < 0;
}
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }
};
class StrCaseMap : public std::multimap<std::string, std::string, StrCaseCompare> {
@ -42,84 +40,87 @@ public:
return it->second;
}
template<typename V>
void emplace(const std::string &k, V &&v) {
template <typename K, typename V>
void emplace(K &&k, V &&v) {
auto it = find(k);
if (it != end()) {
return;
}
Super::emplace(k, std::forward<V>(v));
Super::emplace(std::forward<K>(k), std::forward<V>(v));
}
template<typename V>
void emplace_force(const std::string k, V &&v) {
Super::emplace(k, std::forward<V>(v));
template <typename K, typename V>
void emplace_force(K &&k, V &&v) {
Super::emplace(std::forward<K>(k), std::forward<V>(v));
}
};
//rtsp/http/sip解析类
// rtsp/http/sip解析类
class Parser {
public:
Parser() = default;
~Parser() = default;
//解析信令
void Parse(const char *buf);
// 解析http/rtsp/sip请求需要确保buf以\0结尾
void parse(const char *buf, size_t size);
//获取命令字
const std::string &Method() const;
// 获取命令字如GET/POST
const std::string &method() const;
//获取中间url不包含?后面的参数
const std::string &Url() const;
// 请求时获取中间url不包含?后面的参数
const std::string &url() const;
// 回复时获取状态码如200/404
const std::string &status() const;
//获取中间url包含?后面的参数
std::string FullUrl() const;
// 获取中间url包含?后面的参数
std::string fullUrl() const;
//获取命令协议名
const std::string &Tail() const;
// 请求时获取协议名如HTTP/1.1
const std::string &protocol() const;
// 回复时,获取状态字符串,如 OK/Not Found
const std::string &statusStr() const;
//根据header key名获取请求header value值
// 根据header key名获取请求header value值
const std::string &operator[](const char *name) const;
//获取http body或sdp
const std::string &Content() const;
// 获取http body或sdp
const std::string &content() const;
//清空,为了重用
void Clear();
// 清空,为了重用
void clear();
//获取?后面的参数
const std::string &Params() const;
// 获取?后面的参数
const std::string &params() const;
//重新设置url
// 重新设置url
void setUrl(std::string url);
//重新设置content
// 重新设置content
void setContent(std::string content);
//获取header列表
// 获取header列表
StrCaseMap &getHeader() const;
//获取url参数列表
// 获取url参数列表
StrCaseMap &getUrlArgs() const;
//解析?后面的参数
// 解析?后面的参数
static StrCaseMap parseArgs(const std::string &str, const char *pair_delim = "&", const char *key_delim = "=");
static std::string merge_url(const std::string &base_url, const std::string &path);
static std::string mergeUrl(const std::string &base_url, const std::string &path);
private:
std::string _strMethod;
std::string _strUrl;
std::string _strTail;
std::string _strContent;
std::string _strNull;
std::string _method;
std::string _url;
std::string _protocol;
std::string _content;
std::string _params;
mutable StrCaseMap _mapHeaders;
mutable StrCaseMap _mapUrlArgs;
mutable StrCaseMap _headers;
mutable StrCaseMap _url_args;
};
//解析rtsp url的工具类
class RtspUrl{
// 解析rtsp url的工具类
class RtspUrl {
public:
bool _is_ssl;
uint16_t _port;
@ -134,9 +135,9 @@ public:
void parse(const std::string &url);
private:
void setup(bool,const std::string &, const std::string &, const std::string &);
void setup(bool, const std::string &, const std::string &, const std::string &);
};
}//namespace mediakit
} // namespace mediakit
#endif //ZLMEDIAKIT_PARSER_H
#endif // ZLMEDIAKIT_PARSER_H

View File

@ -10,7 +10,7 @@
#include "Stamp.h"
//时间戳最大允许跳变3秒主要是防止网络抖动导致的跳变
// 时间戳最大允许跳变3秒主要是防止网络抖动导致的跳变
#define MAX_DELTA_STAMP (3 * 1000)
#define STAMP_LOOP_DELTA (60 * 1000)
#define MAX_CTS 500
@ -25,51 +25,52 @@ int64_t DeltaStamp::relativeStamp(int64_t stamp) {
return _relative_stamp;
}
int64_t DeltaStamp::relativeStamp(){
int64_t DeltaStamp::relativeStamp() {
return _relative_stamp;
}
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
if(!_last_stamp){
//第一次计算时间戳增量,时间戳增量为0
if(stamp){
if (!_last_stamp) {
// 第一次计算时间戳增量,时间戳增量为0
if (stamp) {
_last_stamp = stamp;
}
return 0;
}
int64_t ret = stamp - _last_stamp;
if(ret >= 0){
//时间戳增量为正,返回之
if (ret >= 0) {
// 时间戳增量为正,返回之
_last_stamp = stamp;
//在直播情况下时间戳增量不得大于MAX_DELTA_STAMP
return ret < MAX_DELTA_STAMP ? ret : 0;
// 在直播情况下时间戳增量不得大于MAX_DELTA_STAMP否则强制相对时间戳加1
return ret < MAX_DELTA_STAMP ? ret : 1;
}
//时间戳增量为负,说明时间戳回环了或回退了
// 时间戳增量为负,说明时间戳回环了或回退了
_last_stamp = stamp;
//如果时间戳回退不多,那么返回负值
return -ret < MAX_CTS ? ret : 0;
// 如果时间戳回退不多那么返回负值否则返回加1
return -ret < MAX_DELTA_STAMP ? ret : 1;
}
void Stamp::setPlayBack(bool playback) {
_playback = playback;
}
void Stamp::syncTo(Stamp &other){
void Stamp::syncTo(Stamp &other) {
_sync_master = &other;
}
//限制dts回退
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
// 限制dts回退
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
if (_playback) {
//回放允许时间戳回退
// 回放允许时间戳回退
return;
}
if (dts_out < _last_dts_out) {
// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out;
// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out;
dts_out = _last_dts_out;
pts_out = _last_pts_out;
return;
@ -78,35 +79,35 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,
_last_pts_out = pts_out;
}
//音视频时间戳同步
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
// 音视频时间戳同步
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
revise_l2(dts, pts, dts_out, pts_out, modifyStamp);
if (!_sync_master || modifyStamp || _playback) {
//自动生成时间戳或回放或同步完毕
// 自动生成时间戳或回放或同步完毕
return;
}
if (_sync_master && _sync_master->_last_dts_in) {
//音视频dts当前时间差
// 音视频dts当前时间差
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
if (ABS(dts_diff) < 5000) {
//如果绝对时间戳小于5秒那么说明他们的起始时间戳是一致的那么强制同步
// 如果绝对时间戳小于5秒那么说明他们的起始时间戳是一致的那么强制同步
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
}
//下次不用再强制同步
// 下次不用再强制同步
_sync_master = nullptr;
}
}
//求取相对时间戳
void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
// 求取相对时间戳
void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
if (!pts) {
//没有播放时间戳,使其赋值为解码时间戳
// 没有播放时间戳,使其赋值为解码时间戳
pts = dts;
}
if (_playback) {
//这是点播
// 这是点播
dts_out = dts;
pts_out = pts;
_relative_stamp = dts_out;
@ -114,13 +115,13 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o
return;
}
//pts和dts的差值
// pts和dts的差值
int64_t pts_dts_diff = pts - dts;
if (_last_dts_in != dts) {
//时间戳发生变更
// 时间戳发生变更
if (modifyStamp) {
//内部自己生产时间戳
// 内部自己生产时间戳
_relative_stamp = _ticker.elapsedTime();
} else {
_relative_stamp += deltaStamp(dts);
@ -131,7 +132,7 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o
//////////////以下是播放时间戳的计算//////////////////
if (ABS(pts_dts_diff) > MAX_CTS) {
//如果差值太大,则认为由于回环导致时间戳错乱了
// 如果差值太大,则认为由于回环导致时间戳错乱了
pts_dts_diff = 0;
}
@ -146,156 +147,157 @@ int64_t Stamp::getRelativeStamp() const {
return _relative_stamp;
}
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts){
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts) {
bool ret = false;
if (pts == _last_pts) {
//pts未变说明dts也不会变返回上次dts
// pts未变说明dts也不会变返回上次dts
if (_last_dts) {
dts = _last_dts;
ret = true;
}
} else {
//pts变了尝试计算dts
// pts变了尝试计算dts
ret = getDts_l(pts, dts);
if (ret) {
//获取到了dts保存本次结果
// 获取到了dts保存本次结果
_last_dts = dts;
}
}
if (!ret) {
//pts排序列队长度还不知道也就是不知道有没有B帧
//那么先强制dts == pts这样可能导致有B帧的情况下起始画面有几帧回退
// pts排序列队长度还不知道也就是不知道有没有B帧
// 那么先强制dts == pts这样可能导致有B帧的情况下起始画面有几帧回退
dts = pts;
}
//记录上次pts
// 记录上次pts
_last_pts = pts;
return ret;
}
//该算法核心思想是对pts进行排序排序好的pts就是dts。
//排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts){
if(_sorter_max_size == 1){
//没有B帧dts就等于pts
// 该算法核心思想是对pts进行排序排序好的pts就是dts。
// 排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts) {
if (_sorter_max_size == 1) {
// 没有B帧dts就等于pts
dts = pts;
return true;
}
if(!_sorter_max_size){
//尚未计算出pts排序列队长度(也就是P帧间B帧个数)
if(pts > _last_max_pts){
//pts时间戳增加了那么说明这帧画面不是B帧(说明是P帧或关键帧)
if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){
//已经出现多次非B帧的情况那么我们就能知道P帧间B帧的个数
if (!_sorter_max_size) {
// 尚未计算出pts排序列队长度(也就是P帧间B帧个数)
if (pts > _last_max_pts) {
// pts时间戳增加了那么说明这帧画面不是B帧(说明是P帧或关键帧)
if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) {
// 已经出现多次非B帧的情况那么我们就能知道P帧间B帧的个数
_sorter_max_size = _frames_since_last_max_pts;
//我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
// 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
_dts_pts_offset = (pts - _last_max_pts);
//除以2防止dts大于pts
// 除以2防止dts大于pts
_dts_pts_offset /= 2;
}
//遇到P帧或关键帧连续B帧计数清零
// 遇到P帧或关键帧连续B帧计数清零
_frames_since_last_max_pts = 0;
//记录上次非B帧的pts时间戳(同时也是dts)用于统计连续B帧时间戳增量
// 记录上次非B帧的pts时间戳(同时也是dts)用于统计连续B帧时间戳增量
_last_max_pts = pts;
}
//如果pts时间戳小于上一个P帧那么断定这个是B帧,我们记录B帧连续个数
// 如果pts时间戳小于上一个P帧那么断定这个是B帧,我们记录B帧连续个数
++_frames_since_last_max_pts;
}
//pts放入排序缓存列队缓存列队最大等于连续B帧个数
// pts放入排序缓存列队缓存列队最大等于连续B帧个数
_pts_sorter.emplace(pts);
if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){
//如果启用了pts排序(意味着存在B帧)并且pts排序缓存列队长度大于连续B帧个数
//意味着后续的pts都会比最早的pts大那么说明可以取出最早的pts了这个pts将当做该帧的dts基准
if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) {
// 如果启用了pts排序(意味着存在B帧)并且pts排序缓存列队长度大于连续B帧个数
// 意味着后续的pts都会比最早的pts大那么说明可以取出最早的pts了这个pts将当做该帧的dts基准
auto it = _pts_sorter.begin();
//由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
//那么我们加上时间戳偏移量基本等于该帧的dts
// 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
// 那么我们加上时间戳偏移量基本等于该帧的dts
dts = *it + _dts_pts_offset;
if(dts > pts){
//dts不能大于pts(基本不可能到达这个逻辑)
if (dts > pts) {
// dts不能大于pts(基本不可能到达这个逻辑)
dts = pts;
}
//pts排序缓存出列
// pts排序缓存出列
_pts_sorter.erase(it);
return true;
}
//排序缓存尚未满
// 排序缓存尚未满
return false;
}
void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
update(rtp_stamp, ntp_stamp_ms);
}
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
if (ntp_stamp_ms == 0) {
//实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0
if (!ntp_stamp_ms || !rtp_stamp) {
// 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0
WarnL << "Invalid sender report rtcp, ntp_stamp_ms = " << ntp_stamp_ms << ", rtp_stamp = " << rtp_stamp;
return;
}
update(rtp_stamp, ntp_stamp_ms * 1000);
}
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_us) {
_last_rtp_stamp = rtp_stamp;
_last_ntp_stamp_ms = ntp_stamp_ms;
_last_ntp_stamp_us = ntp_stamp_us;
}
uint64_t NtpStamp::getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate) {
if (rtp_stamp == _last_rtp_stamp) {
return _last_ntp_stamp_ms;
return _last_ntp_stamp_us / 1000;
}
return getNtpStamp_l(rtp_stamp, sample_rate);
return getNtpStampUS(rtp_stamp, sample_rate) / 1000;
}
uint64_t NtpStamp::getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate) {
if (!_last_ntp_stamp_ms) {
//尚未收到sender report rtcp包那么赋值为本地系统时间戳吧
update(rtp_stamp, getCurrentMillisecond(true));
uint64_t NtpStamp::getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate) {
if (!_last_ntp_stamp_us) {
// 尚未收到sender report rtcp包那么赋值为本地系统时间戳吧
update(rtp_stamp, getCurrentMicrosecond(true));
}
//rtp时间戳正增长
// rtp时间戳正增长
if (rtp_stamp >= _last_rtp_stamp) {
auto diff = static_cast<int>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000.0f));
if (diff < MAX_DELTA_STAMP) {
//时间戳正常增长
update(rtp_stamp, _last_ntp_stamp_ms + diff);
return _last_ntp_stamp_ms;
auto diff_us = static_cast<int64_t>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000000.0f));
if (diff_us < MAX_DELTA_STAMP * 1000) {
// 时间戳正常增长
update(rtp_stamp, _last_ntp_stamp_us + diff_us);
return _last_ntp_stamp_us;
}
//时间戳大幅跳跃
uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000;
if (_last_rtp_stamp < loop_delta && rtp_stamp > UINT32_MAX - loop_delta) {
//应该是rtp时间戳溢出+乱序
uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate;
return _last_ntp_stamp_ms + diff - max_rtp_ms;
// 时间戳大幅跳跃
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
if (_last_rtp_stamp < loop_delta_hz && rtp_stamp > UINT32_MAX - loop_delta_hz) {
// 应该是rtp时间戳溢出+乱序
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
return _last_ntp_stamp_us + diff_us - max_rtp_us;
}
//不明原因的时间戳大幅跳跃,直接返回上次值
// 不明原因的时间戳大幅跳跃,直接返回上次值
WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp;
update(rtp_stamp, _last_ntp_stamp_ms);
return _last_ntp_stamp_ms;
update(rtp_stamp, _last_ntp_stamp_us);
return _last_ntp_stamp_us;
}
//rtp时间戳负增长
auto diff = static_cast<int>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000.0f));
if (diff < MAX_DELTA_STAMP) {
//正常范围的时间戳回退说明收到rtp乱序了
return _last_ntp_stamp_ms - diff;
// rtp时间戳负增长
auto diff_us = static_cast<int64_t>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000000.0f));
if (diff_us < MAX_DELTA_STAMP * 1000) {
// 正常范围的时间戳回退说明收到rtp乱序了
return _last_ntp_stamp_us - diff_us;
}
//时间戳大幅度回退
uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000;
if (rtp_stamp < loop_delta && _last_rtp_stamp > UINT32_MAX - loop_delta) {
//确定是时间戳溢出
uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate;
update(rtp_stamp, _last_ntp_stamp_ms + (max_rtp_ms - diff));
return _last_ntp_stamp_ms;
// 时间戳大幅度回退
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
if (rtp_stamp < loop_delta_hz && _last_rtp_stamp > UINT32_MAX - loop_delta_hz) {
// 确定是时间戳溢出
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
update(rtp_stamp, _last_ntp_stamp_us + (max_rtp_us - diff_us));
return _last_ntp_stamp_us;
}
//不明原因的时间戳回退,直接返回上次值
// 不明原因的时间戳回退,直接返回上次值
WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp;
update(rtp_stamp, _last_ntp_stamp_ms);
return _last_ntp_stamp_ms;
update(rtp_stamp, _last_ntp_stamp_us);
return _last_ntp_stamp_us;
}
}//namespace mediakit
} // namespace mediakit

View File

@ -125,12 +125,12 @@ public:
uint64_t getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate);
private:
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms);
uint64_t getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate);
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_us);
uint64_t getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate);
private:
uint32_t _last_rtp_stamp = 0;
uint64_t _last_ntp_stamp_ms = 0;
uint64_t _last_ntp_stamp_us = 0;
};
}//namespace mediakit

View File

@ -9,6 +9,7 @@
*/
#include "Common/config.h"
#include "MediaSource.h"
#include "Util/NoticeCenter.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
@ -30,7 +31,7 @@ bool loadIniConfig(const char *ini_path) {
}
try {
mINI::Instance().parseFile(ini);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
return true;
} catch (std::exception &) {
InfoL << "dump ini file to:" << ini;
@ -98,9 +99,11 @@ namespace Protocol {
const string kModifyStamp = PROTOCOL_FIELD "modify_stamp";
const string kEnableAudio = PROTOCOL_FIELD "enable_audio";
const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio";
const string kAutoClose = PROTOCOL_FIELD "auto_close";
const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms";
const string kEnableHls = PROTOCOL_FIELD "enable_hls";
const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4";
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp";
@ -120,12 +123,14 @@ const string kTSDemand = PROTOCOL_FIELD "ts_demand";
const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand";
static onceToken token([]() {
mINI::Instance()[kModifyStamp] = 0;
mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative;
mINI::Instance()[kEnableAudio] = 1;
mINI::Instance()[kAddMuteAudio] = 1;
mINI::Instance()[kContinuePushMS] = 15000;
mINI::Instance()[kAutoClose] = 0;
mINI::Instance()[kEnableHls] = 1;
mINI::Instance()[kEnableHlsFmp4] = 0;
mINI::Instance()[kEnableMP4] = 0;
mINI::Instance()[kEnableRtsp] = 1;
mINI::Instance()[kEnableRtmp] = 1;
@ -159,6 +164,8 @@ const string kNotFound = HTTP_FIELD "notFound";
const string kDirMenu = HTTP_FIELD "dirMenu";
const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix";
const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header";
const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains";
const string kAllowIPRange = HTTP_FIELD "allow_ip_range";
static onceToken token([]() {
mINI::Instance()[kSendBufSize] = 64 * 1024;
@ -186,6 +193,8 @@ static onceToken token([]() {
<< endl;
mINI::Instance()[kForbidCacheSuffix] = "";
mINI::Instance()[kForwardedIpHeader] = "";
mINI::Instance()[kAllowCrossDomains] = 1;
mINI::Instance()[kAllowIPRange] = "::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255";
});
} // namespace Http
@ -206,6 +215,7 @@ const string kHandshakeSecond = RTSP_FIELD "handshakeSecond";
const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond";
const string kDirectProxy = RTSP_FIELD "directProxy";
const string kLowLatency = RTSP_FIELD"lowLatency";
const string kRtpTransportType = RTSP_FIELD"rtpTransportType";
static onceToken token([]() {
// 默认Md5方式认证
@ -214,18 +224,17 @@ static onceToken token([]() {
mINI::Instance()[kKeepAliveSecond] = 15;
mINI::Instance()[kDirectProxy] = 1;
mINI::Instance()[kLowLatency] = 0;
mINI::Instance()[kRtpTransportType] = -1;
});
} // namespace Rtsp
////////////RTMP服务器配置///////////
namespace Rtmp {
#define RTMP_FIELD "rtmp."
const string kModifyStamp = RTMP_FIELD "modifyStamp";
const string kHandshakeSecond = RTMP_FIELD "handshakeSecond";
const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond";
static onceToken token([]() {
mINI::Instance()[kModifyStamp] = false;
mINI::Instance()[kHandshakeSecond] = 15;
mINI::Instance()[kKeepAliveSecond] = 15;
});
@ -239,15 +248,15 @@ const string kVideoMtuSize = RTP_FIELD "videoMtuSize";
const string kAudioMtuSize = RTP_FIELD "audioMtuSize";
// rtp包最大长度限制单位是KB
const string kRtpMaxSize = RTP_FIELD "rtpMaxSize";
const string kLowLatency = RTP_FIELD "lowLatency";
const string kH264StapA = RTP_FIELD "h264_stap_a";
static onceToken token([]() {
mINI::Instance()[kVideoMtuSize] = 1400;
mINI::Instance()[kAudioMtuSize] = 600;
mINI::Instance()[kRtpMaxSize] = 10;
mINI::Instance()[kLowLatency] = 0;
mINI::Instance()[kH264StapA] = 1;
});
} // namespace Rtp

View File

@ -99,7 +99,7 @@ extern const std::string kBroadcastStreamNoneReader;
// rtp推流被动停止时触发
extern const std::string kBroadcastSendRtpStopped;
#define BroadcastSendRtpStopped MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
#define BroadcastSendRtpStoppedArgs MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
// 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播
extern const std::string kBroadcastReloadConfig;
@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig;
// rtp server 超时
extern const std::string KBroadcastRtpServerTimeout;
#define BroadcastRtpServerTimeout uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
#define ReloadConfigTag ((void *)(0xFF))
#define RELOAD_KEY(arg, key) \
@ -190,11 +190,17 @@ extern const std::string kModifyStamp;
extern const std::string kEnableAudio;
//添加静音音频,在关闭音频时,此开关无效
extern const std::string kAddMuteAudio;
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
// 此配置置1时此流如果无人观看将不触发on_none_reader hook回调
// 而是将直接关闭流
extern const std::string kAutoClose;
//断连续推延时,单位毫秒,默认采用配置文件
extern const std::string kContinuePushMS;
//是否开启转换为hls
//是否开启转换为hls(mpegts)
extern const std::string kEnableHls;
//是否开启转换为hls(fmp4)
extern const std::string kEnableHlsFmp4;
//是否开启MP4录制
extern const std::string kEnableMP4;
//是否开启转换为rtsp/webrtc
@ -246,6 +252,10 @@ extern const std::string kDirMenu;
extern const std::string kForbidCacheSuffix;
// 可以把http代理前真实客户端ip放在http头中https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
extern const std::string kForwardedIpHeader;
// 是否允许所有跨域请求
extern const std::string kAllowCrossDomains;
// 允许访问http api和http文件索引的ip地址范围白名单置空情况下不做限制
extern const std::string kAllowIPRange;
} // namespace Http
////////////SHELL配置///////////
@ -271,6 +281,11 @@ extern const std::string kDirectProxy;
// rtsp 转发是否使用低延迟模式当开启时不会缓存rtp包来提高并发可以降低一帧的延迟
extern const std::string kLowLatency;
//强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制)
//当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupport Transport
//迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC
extern const std::string kRtpTransportType;
} // namespace Rtsp
////////////RTMP服务器配置///////////
@ -291,6 +306,8 @@ extern const std::string kAudioMtuSize;
extern const std::string kRtpMaxSize;
// rtp 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏
extern const std::string kLowLatency;
//H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
extern const std::string kH264StapA;
} // namespace Rtp
////////////组播配置///////////

View File

@ -32,14 +32,6 @@
#define __LITTLE_ENDIAN LITTLE_ENDIAN
#endif
#ifndef PACKED
#if !defined(_WIN32)
#define PACKED __attribute__((packed))
#else
#define PACKED
#endif //! defined(_WIN32)
#endif
#ifndef CHECK
#define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
#endif // CHECK
@ -67,6 +59,7 @@
#define HLS_SCHEMA "hls"
#define TS_SCHEMA "ts"
#define FMP4_SCHEMA "fmp4"
#define HLS_FMP4_SCHEMA "hls.fmp4"
#define SRT_SCHEMA "srt"
#define DEFAULT_VHOST "__defaultVhost__"

View File

@ -13,7 +13,7 @@
#include <iostream>
#include <string>
#include <cstdint>
namespace mediakit {
class strCoding {

View File

@ -246,7 +246,7 @@ AACTrack::AACTrack(const string &aac_cfg) {
onReady();
}
const string &AACTrack::getAacCfg() const {
const string &AACTrack::getConfig() const {
return _cfg;
}
@ -342,7 +342,7 @@ Sdp::Ptr AACTrack::getSdp() {
WarnL << getCodecName() << " Track未准备好";
return nullptr;
}
return std::make_shared<AACSdp>(getAacCfg(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024);
return std::make_shared<AACSdp>(getConfig(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024);
}
}//namespace mediakit

View File

@ -44,7 +44,7 @@ public:
/**
* aac
*/
const std::string &getAacCfg() const;
const std::string &getConfig() const;
bool ready() override;
CodecId getCodecId() const override;

View File

@ -16,12 +16,9 @@ using namespace toolkit;
namespace mediakit {
static string getAacCfg(const RtmpPacket &thiz) {
static string getConfig(const RtmpPacket &thiz) {
string ret;
if (thiz.getMediaType() != FLV_CODEC_AAC) {
return ret;
}
if (!thiz.isCfgFrame()) {
if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) {
return ret;
}
if (thiz.buffer.size() < 4) {
@ -33,8 +30,8 @@ static string getAacCfg(const RtmpPacket &thiz) {
}
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
if (pkt->isCfgFrame()) {
_aac_cfg = getAacCfg(*pkt);
if (pkt->isConfigFrame()) {
_aac_cfg = getConfig(*pkt);
if (!_aac_cfg.empty()) {
onGetAAC(nullptr, 0, 0);
}
@ -82,7 +79,7 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) {
void AACRtmpEncoder::makeConfigPacket() {
if (_track && _track->ready()) {
//从track中和获取aac配置信息
_aac_cfg = _track->getAacCfg();
_aac_cfg = _track->getConfig();
}
if (!_aac_cfg.empty()) {
@ -93,51 +90,45 @@ void AACRtmpEncoder::makeConfigPacket() {
bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
if (_aac_cfg.empty()) {
if (frame->prefixSize()) {
//包含adts头,从adts头获取aac配置信息
_aac_cfg = makeAacConfig((uint8_t *) (frame->data()), frame->prefixSize());
// 包含adts头,从adts头获取aac配置信息
_aac_cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize());
}
makeConfigPacket();
}
if(_aac_cfg.empty()){
if (_aac_cfg.empty()) {
return false;
}
auto rtmpPkt = RtmpPacket::create();
//header
uint8_t is_config = false;
rtmpPkt->buffer.push_back(_audio_flv_flags);
rtmpPkt->buffer.push_back(!is_config);
//aac data
rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
rtmpPkt->body_size = rtmpPkt->buffer.size();
rtmpPkt->chunk_id = CHUNK_AUDIO;
rtmpPkt->stream_index = STREAM_MEDIA;
rtmpPkt->time_stamp = frame->dts();
rtmpPkt->type_id = MSG_AUDIO;
RtmpCodec::inputRtmp(rtmpPkt);
auto pkt = RtmpPacket::create();
// header
pkt->buffer.push_back(_audio_flv_flags);
pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw);
// aac data
pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
pkt->body_size = pkt->buffer.size();
pkt->chunk_id = CHUNK_AUDIO;
pkt->stream_index = STREAM_MEDIA;
pkt->time_stamp = frame->dts();
pkt->type_id = MSG_AUDIO;
RtmpCodec::inputRtmp(pkt);
return true;
}
void AACRtmpEncoder::makeAudioConfigPkt() {
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
auto rtmpPkt = RtmpPacket::create();
//header
uint8_t is_config = true;
rtmpPkt->buffer.push_back(_audio_flv_flags);
rtmpPkt->buffer.push_back(!is_config);
//aac config
rtmpPkt->buffer.append(_aac_cfg);
rtmpPkt->body_size = rtmpPkt->buffer.size();
rtmpPkt->chunk_id = CHUNK_AUDIO;
rtmpPkt->stream_index = STREAM_MEDIA;
rtmpPkt->time_stamp = 0;
rtmpPkt->type_id = MSG_AUDIO;
RtmpCodec::inputRtmp(rtmpPkt);
auto pkt = RtmpPacket::create();
// header
pkt->buffer.push_back(_audio_flv_flags);
pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header);
// aac config
pkt->buffer.append(_aac_cfg);
pkt->body_size = pkt->buffer.size();
pkt->chunk_id = CHUNK_AUDIO;
pkt->stream_index = STREAM_MEDIA;
pkt->time_stamp = 0;
pkt->type_id = MSG_AUDIO;
RtmpCodec::inputRtmp(pkt);
}
}//namespace mediakit

View File

@ -64,7 +64,7 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) {
if (!aacTrack || !aacTrack->ready()) {
WarnL << "该aac track无效!";
} else {
_aac_cfg = aacTrack->getAacCfg();
_aac_cfg = aacTrack->getConfig();
}
obtainFrame();
}

View File

@ -34,10 +34,10 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
return false;
}
auto payload = rtp->getPayload();
auto stamp = rtp->getStampMS();
auto stamp = rtp->getStamp();
auto seq = rtp->getSeq();
if (_frame->_dts != stamp || _frame->_buffer.size() > _max_frame_size) {
if (_last_stamp != stamp || _frame->_buffer.size() > _max_frame_size) {
//时间戳发生变化或者缓存超过MAX_FRAME_SIZE则清空上帧数据
if (!_frame->_buffer.empty()) {
//有有效帧,则输出
@ -46,7 +46,8 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
//新的一帧数据
obtainFrame();
_frame->_dts = stamp;
_frame->_dts = rtp->getStampMS();
_last_stamp = stamp;
_drop_flag = false;
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) {
//时间戳未发生变化但是seq却不连续说明中间rtp丢包了那么整帧应该废弃

View File

@ -50,6 +50,7 @@ private:
private:
bool _drop_flag = false;
uint16_t _last_seq = 0;
uint64_t _last_stamp = 0;
size_t _max_frame_size;
CodecId _codec;
FrameImp::Ptr _frame;

View File

@ -45,9 +45,9 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
case CodecOpus : return std::make_shared<OpusTrack>();
case CodecAAC : {
string aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";");
string aac_cfg_str = findSubString(track->_fmtp.data(), "config=", ";");
if (aac_cfg_str.empty()) {
aac_cfg_str = FindField(track->_fmtp.data(), "config=", nullptr);
aac_cfg_str = findSubString(track->_fmtp.data(), "config=", nullptr);
}
if (aac_cfg_str.empty()) {
//如果sdp中获取不到aac config信息那么在rtp也无法获取那么忽略该Track
@ -67,8 +67,8 @@ 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(track->_fmtp, ";", "=");
auto sps_pps = map["sprop-parameter-sets"];
string base64_SPS = FindField(sps_pps.data(), NULL, ",");
string base64_PPS = FindField(sps_pps.data(), ",", NULL);
string base64_SPS = findSubString(sps_pps.data(), NULL, ",");
string base64_PPS = findSubString(sps_pps.data(), ",", NULL);
auto sps = decodeBase64(base64_SPS);
auto pps = decodeBase64(base64_PPS);
if (sps.empty() || pps.empty()) {
@ -201,17 +201,20 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){
}
if (val.type() != AMF_NULL) {
auto type_id = val.as_integer();
auto type_id = (RtmpVideoCodec)val.as_integer();
switch (type_id) {
case FLV_CODEC_H264 : return CodecH264;
case FLV_CODEC_H265 : return CodecH265;
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
case RtmpVideoCodec::h264: return CodecH264;
case RtmpVideoCodec::fourcc_hevc:
case RtmpVideoCodec::h265: return CodecH265;
case RtmpVideoCodec::fourcc_av1: return CodecAV1;
case RtmpVideoCodec::fourcc_vp9: return CodecVP9;
default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid;
}
}
return CodecInvalid;
}
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
Track::Ptr Factory::getTrackByCodecId(CodecId codecId, int sample_rate, int channels, int sample_bit) {
switch (codecId){
case CodecH264 : return std::make_shared<H264Track>();
case CodecH265 : return std::make_shared<H265Track>();
@ -243,13 +246,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
}
if (val.type() != AMF_NULL) {
auto type_id = val.as_integer();
auto type_id = (RtmpAudioCodec)val.as_integer();
switch (type_id) {
case FLV_CODEC_AAC : return CodecAAC;
case FLV_CODEC_G711A : return CodecG711A;
case FLV_CODEC_G711U : return CodecG711U;
case FLV_CODEC_OPUS : return CodecOpus;
default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid;
case RtmpAudioCodec::aac : return CodecAAC;
case RtmpAudioCodec::g711a : return CodecG711A;
case RtmpAudioCodec::g711u : return CodecG711U;
case RtmpAudioCodec::opus : return CodecOpus;
default : WarnL << "暂不支持该音频Amf:" << (int)type_id; return CodecInvalid;
}
}
@ -291,13 +294,13 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
}
AMFValue Factory::getAmfByCodecId(CodecId codecId) {
switch (codecId){
case CodecAAC: return AMFValue(FLV_CODEC_AAC);
case CodecH264: return AMFValue(FLV_CODEC_H264);
case CodecH265: return AMFValue(FLV_CODEC_H265);
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
switch (codecId) {
case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac);
case CodecH264: return AMFValue((int)RtmpVideoCodec::h264);
case CodecH265: return AMFValue((int)RtmpVideoCodec::h265);
case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a);
case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u);
case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus);
default: return AMFValue(AMF_NULL);
}
}

View File

@ -21,6 +21,16 @@ namespace mediakit{
class Factory {
public:
/**
* codec_id track
* @param codecId id
* @param sample_rate 90000
* @param channels
* @param sample_bit
*/
static Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0);
////////////////////////////////rtsp相关//////////////////////////////////
/**
* sdp生成Track对象

View File

@ -13,6 +13,7 @@
#include "H265.h"
#include "Common/Parser.h"
#include "Common/Stamp.h"
#include "Common/MediaSource.h"
using namespace std;
using namespace toolkit;
@ -31,11 +32,11 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
return std::make_shared<FrameCacheAble>(frame);
}
FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp)
FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp)
{
_frame = std::move(frame);
//覆盖时间戳
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp);
// kModifyStampSystem时采用系统时间戳kModifyStampRelative采用相对时间戳
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem);
}
TrackType getTrackType(CodecId codecId) {

View File

@ -40,7 +40,7 @@ typedef enum {
XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \
XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \
XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1) \
XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_JPEG_2000)
XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_RESERVED)
typedef enum {
CodecInvalid = -1,
@ -492,7 +492,7 @@ private:
class FrameStamp : public Frame {
public:
using Ptr = std::shared_ptr<FrameStamp>;
FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp);
FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp);
~FrameStamp() override {}
uint64_t dts() const override { return (uint64_t)_dts; }

View File

@ -12,9 +12,10 @@
#include "SPSParser.h"
#include "Util/logger.h"
#include "Util/base64.h"
#include "Common/config.h"
using namespace toolkit;
using namespace std;
using namespace toolkit;
namespace mediakit {
@ -248,7 +249,14 @@ public:
_printer << "b=AS:" << bitrate << "\r\n";
}
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n";
_printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id=";
/**
Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed)
Non Interleaved Mode = 1// Non-interleaved Mode: 1-2324 (STAP-A)28 (FU-A) are allowed
Interleaved Mode = 2, // 25 (STAP-B)26 (MTAP16)27 (MTAP24)28 (EU-A)and 29 (EU-B) are allowed.
**/
GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA);
_printer << "a=fmtp:" << payload_type << " packetization-mode=" << h264_stap_a << "; profile-level-id=";
uint32_t profile_level_id = 0;
if (strSPS.length() >= 4) { // sanity check

View File

@ -30,10 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
* 0x00 00 00 01sps pps
*/
static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) {
if (thiz.getMediaType() != FLV_CODEC_H264) {
return false;
}
if (!thiz.isCfgFrame()) {
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) {
return false;
}
if (thiz.buffer.size() < 13) {
@ -59,7 +56,7 @@ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) {
}
void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
if (pkt->isCfgFrame()) {
if (pkt->isConfigFrame()) {
//缓存sps pps后续插入到I帧之前
if (!getH264Config(*pkt, _sps, _pps)) {
WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size());
@ -159,26 +156,21 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
}
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
//flags
_rtmp_packet->buffer[0] = FLV_CODEC_H264 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
//not config
_rtmp_packet->buffer[1] = true;
int32_t cts = pts - dts;
if (cts < 0) {
cts = 0;
}
//cts
set_be24(&_rtmp_packet->buffer[2], cts);
_rtmp_packet->time_stamp = dts;
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
_rtmp_packet->chunk_id = CHUNK_VIDEO;
_rtmp_packet->stream_index = STREAM_MEDIA;
_rtmp_packet->type_id = MSG_VIDEO;
//输出rtmp packet
RtmpCodec::inputRtmp(_rtmp_packet);
_rtmp_packet = nullptr;
}, &_rtmp_packet->buffer);
// flags
_rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4);
_rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu;
int32_t cts = pts - dts;
// cts
set_be24(&_rtmp_packet->buffer[2], cts);
_rtmp_packet->time_stamp = dts;
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
_rtmp_packet->chunk_id = CHUNK_VIDEO;
_rtmp_packet->stream_index = STREAM_MEDIA;
_rtmp_packet->type_id = MSG_VIDEO;
// 输出rtmp packet
RtmpCodec::inputRtmp(_rtmp_packet);
_rtmp_packet = nullptr;
}, &_rtmp_packet->buffer);
}
void H264RtmpEncoder::makeVideoConfigPkt() {
@ -186,42 +178,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() {
WarnL << "sps长度不足4字节";
return;
}
int8_t flags = FLV_CODEC_H264;
flags |= (FLV_KEY_FRAME << 4);
bool is_config = true;
auto rtmpPkt = RtmpPacket::create();
//header
rtmpPkt->buffer.push_back(flags);
rtmpPkt->buffer.push_back(!is_config);
//cts
rtmpPkt->buffer.append("\x0\x0\x0", 3);
//AVCDecoderConfigurationRecord start
rtmpPkt->buffer.push_back(1); // version
rtmpPkt->buffer.push_back(_sps[1]); // profile
rtmpPkt->buffer.push_back(_sps[2]); // compat
rtmpPkt->buffer.push_back(_sps[3]); // level
rtmpPkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
rtmpPkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
//sps
auto flags = (uint8_t)RtmpVideoCodec::h264;
flags |= ((uint8_t)RtmpFrameType::key_frame << 4);
auto pkt = RtmpPacket::create();
// header
pkt->buffer.push_back(flags);
pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
// cts
pkt->buffer.append("\x0\x0\x0", 3);
// AVCDecoderConfigurationRecord start
pkt->buffer.push_back(1); // version
pkt->buffer.push_back(_sps[1]); // profile
pkt->buffer.push_back(_sps[2]); // compat
pkt->buffer.push_back(_sps[3]); // level
pkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
// sps
uint16_t size = (uint16_t)_sps.size();
size = htons(size);
rtmpPkt->buffer.append((char *) &size, 2);
rtmpPkt->buffer.append(_sps);
//pps
rtmpPkt->buffer.push_back(1); // version
pkt->buffer.append((char *)&size, 2);
pkt->buffer.append(_sps);
// pps
pkt->buffer.push_back(1); // version
size = (uint16_t)_pps.size();
size = htons(size);
rtmpPkt->buffer.append((char *) &size, 2);
rtmpPkt->buffer.append(_pps);
pkt->buffer.append((char *)&size, 2);
pkt->buffer.append(_pps);
rtmpPkt->body_size = rtmpPkt->buffer.size();
rtmpPkt->chunk_id = CHUNK_VIDEO;
rtmpPkt->stream_index = STREAM_MEDIA;
rtmpPkt->time_stamp = 0;
rtmpPkt->type_id = MSG_VIDEO;
RtmpCodec::inputRtmp(rtmpPkt);
pkt->body_size = pkt->buffer.size();
pkt->chunk_id = CHUNK_VIDEO;
pkt->stream_index = STREAM_MEDIA;
pkt->time_stamp = 0;
pkt->type_id = MSG_VIDEO;
RtmpCodec::inputRtmp(pkt);
}
}//namespace mediakit

View File

@ -13,9 +13,7 @@
namespace mediakit{
#if defined(_WIN32)
#pragma pack(push, 1)
#endif // defined(_WIN32)
class FuFlags {
public:
@ -30,11 +28,9 @@ public:
unsigned end_bit: 1;
unsigned start_bit: 1;
#endif
} PACKED;
};
#if defined(_WIN32)
#pragma pack(pop)
#endif // defined(_WIN32)
H264RtpDecoder::H264RtpDecoder() {
_frame = obtainFrame();
@ -209,8 +205,8 @@ void H264RtpEncoder::insertConfigFrame(uint64_t pts){
void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
if (len + 3 <= getMaxSize()) {
//STAP-A模式打包小于MTU
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
// 采用STAP-A/Single NAL unit packet per H.264 模式
packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos);
} else {
//STAP-A模式打包会大于MTU,所以采用FU-A模式
packRtpFu(ptr, len, pts, is_mark, gop_pos);
@ -220,8 +216,8 @@ void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_
void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
auto packet_size = getMaxSize() - 2;
if (len <= packet_size + 1) {
//小于FU-A打包最小字节长度要求采用STAP-A模式
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
// 小于FU-A打包最小字节长度要求采用STAP-A/Single NAL unit packet per H.264 模式
packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos);
return;
}
@ -257,8 +253,17 @@ void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool i
}
}
void H264RtpEncoder::packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) {
GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA);
if (h264_stap_a) {
packRtpStapA(data, len, pts, is_mark, gop_pos);
} else {
packRtpSingleNalu(data, len, pts, is_mark, gop_pos);
}
}
void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
//如果帧长度不超过mtu,为了兼容性 webrtc采用STAP-A模式打包
// 如果帧长度不超过mtu,为了兼容性 webrtc采用STAP-A模式打包
auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts);
uint8_t *payload = rtp->getPayload();
//STAP-A
@ -270,6 +275,11 @@ void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, boo
RtpCodec::inputRtp(rtp, gop_pos);
}
void H264RtpEncoder::packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) {
// Single NAL unit packet per H.264 模式
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, is_mark, pts), gop_pos);
}
bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
auto ptr = frame->data() + frame->prefixSize();
switch (H264_TYPE(ptr[0])) {

View File

@ -28,7 +28,7 @@ public:
using Ptr = std::shared_ptr<H264RtpDecoder>;
H264RtpDecoder();
~H264RtpDecoder() {}
~H264RtpDecoder() override = default;
/**
* 264 rtp包
@ -77,9 +77,10 @@ public:
uint32_t sample_rate = 90000,
uint8_t pt = 96,
uint8_t interleaved = TrackVideo * 2);
~H264RtpEncoder() {}
/**
~H264RtpEncoder() override = default;
/**
* 264
* @param frame
*/
@ -96,6 +97,8 @@ private:
void packRtp(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
void packRtpFu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
void packRtpStapA(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
void packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
void packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
private:
Frame::Ptr _sps;

View File

@ -12,12 +12,12 @@
#include "H265Rtmp.h"
#ifdef ENABLE_MP4
#include "mpeg4-hevc.h"
#endif//ENABLE_MP4
#endif // ENABLE_MP4
using namespace std;
using namespace toolkit;
namespace mediakit{
namespace mediakit {
H265RtmpDecoder::H265RtmpDecoder() {
_h265frame = obtainFrame();
@ -30,46 +30,105 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
}
#ifdef ENABLE_MP4
static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, string &frame) {
struct mpeg4_hevc_t hevc;
memset(&hevc, 0, sizeof(hevc));
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) {
uint8_t *config = new uint8_t[bytes * 2];
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
if (size > 4) {
frame.assign((char *)config + 4, size - 4);
}
delete[] config;
return size > 4;
}
return false;
}
/**
* 0x00 00 00 01sps
* @return
*/
static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
if (thiz.getMediaType() != FLV_CODEC_H265) {
return false;
}
if (!thiz.isCfgFrame()) {
static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) {
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) {
return false;
}
if (thiz.buffer.size() < 6) {
WarnL << "bad H265 cfg!";
return false;
}
auto extra = thiz.buffer.data() + 5;
auto bytes = thiz.buffer.size() - 5;
struct mpeg4_hevc_t hevc;
memset(&hevc, 0, sizeof(hevc));
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) {
uint8_t *config = new uint8_t[bytes * 2];
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
if (size > 4) {
frame.assign((char *) config + 4, size - 4);
}
delete [] config;
return size > 4;
}
return false;
return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame);
}
#endif
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
if (pkt->isCfgFrame()) {
if (_info.codec == CodecInvalid) {
// 先判断是否为增强型rtmp
parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info);
}
if (_info.is_enhanced) {
// 增强型rtmp
parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info);
if (!_info.is_enhanced || _info.codec != CodecH265) {
throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!");
}
auto data = (uint8_t *)pkt->data() + 5;
auto size = pkt->size() - 5;
switch (_info.video.pkt_type) {
case RtmpPacketType::PacketTypeSequenceStart: {
#ifdef ENABLE_MP4
string config;
if (decode_HEVCDecoderConfigurationRecord(data, size, config)) {
onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp);
}
#else
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
#endif
break;
}
case RtmpPacketType::PacketTypeCodedFramesX:
case RtmpPacketType::PacketTypeCodedFrames: {
auto pts = pkt->time_stamp;
if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) {
// SI24 = [CompositionTime Offset]
CHECK(size > 7);
int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000;
pts += cts;
data += 3;
size -= 3;
}
splitFrame(data, size, pkt->time_stamp, pts);
break;
}
case RtmpPacketType::PacketTypeMetadata: {
// The body does not contain video data. The body is an AMF encoded metadata.
// The metadata will be represented by a series of [name, value] pairs.
// For now the only defined [name, value] pair is [“colorInfo”, Object]
// See Metadata Frame section for more details of this object.
//
// For a deeper understanding of the encoding please see description
// of SCRIPTDATA and SSCRIPTDATAVALUE in the FLV file spec.
// DATA = [“colorInfo”, Object]
break;
}
case RtmpPacketType::PacketTypeSequenceEnd: {
// signals end of sequence
break;
}
default: break;
}
return;
}
// 国内扩展(12) H265 rtmp
if (pkt->isConfigFrame()) {
#ifdef ENABLE_MP4
string config;
if(getH265ConfigFrame(*pkt,config)){
onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp);
if (getH265ConfigFrame(*pkt, config)) {
onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp);
}
#else
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
@ -78,41 +137,42 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
}
if (pkt->buffer.size() > 9) {
auto total_len = pkt->buffer.size();
size_t offset = 5;
uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2);
uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2);
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
auto pts = pkt->time_stamp + cts;
while (offset + 4 < total_len) {
uint32_t frame_len;
memcpy(&frame_len, pkt->buffer.data() + offset, 4);
frame_len = ntohl(frame_len);
offset += 4;
if (frame_len + offset > total_len) {
break;
}
onGetH265(pkt->buffer.data() + offset, frame_len, pkt->time_stamp, pts);
offset += frame_len;
}
splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts);
}
}
inline void H265RtmpDecoder::onGetH265(const char* pcData, size_t iLen, uint32_t dts,uint32_t pts) {
if(iLen == 0){
void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) {
auto end = data + size;
while (data + 4 < end) {
uint32_t frame_len = load_be32(data);
data += 4;
if (data + frame_len > end) {
break;
}
onGetH265((const char *)data, frame_len, dts, pts);
data += frame_len;
}
}
inline void H265RtmpDecoder::onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts) {
if (size == 0) {
return;
}
#if 1
_h265frame->_dts = dts;
_h265frame->_pts = pts;
_h265frame->_buffer.assign("\x00\x00\x00\x01", 4); //添加265头
_h265frame->_buffer.append(pcData, iLen);
_h265frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头
_h265frame->_buffer.append(data, size);
//写入环形缓存
// 写入环形缓存
RtmpCodec::inputFrame(_h265frame);
_h265frame = obtainFrame();
#else
//防止内存拷贝这样产生的265帧不会有0x00 00 01头
auto frame = std::make_shared<H265FrameNoCacheAble>((char *)pcData,iLen,dts,pts,0);
// 防止内存拷贝这样产生的265帧不会有0x00 00 01头
auto frame = std::make_shared<H265FrameNoCacheAble>((char *)data, size, dts, pts, 0);
RtmpCodec::inputFrame(frame);
#endif
}
@ -123,16 +183,16 @@ H265RtmpEncoder::H265RtmpEncoder(const Track::Ptr &track) {
_track = dynamic_pointer_cast<H265Track>(track);
}
void H265RtmpEncoder::makeConfigPacket(){
void H265RtmpEncoder::makeConfigPacket() {
if (_track && _track->ready()) {
//尝试从track中获取sps pps信息
// 尝试从track中获取sps pps信息
_sps = _track->getSps();
_pps = _track->getPps();
_vps = _track->getVps();
}
if (!_sps.empty() && !_pps.empty() && !_vps.empty()) {
//获取到sps/pps
// 获取到sps/pps
makeVideoConfigPkt();
_got_config_frame = true;
}
@ -175,50 +235,42 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
if (!_rtmp_packet) {
_rtmp_packet = RtmpPacket::create();
//flags/not_config/cts预占位
// flags/not_config/cts预占位
_rtmp_packet->buffer.resize(5);
}
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
//flags
_rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
//not config
_rtmp_packet->buffer[1] = true;
int32_t cts = pts - dts;
if (cts < 0) {
cts = 0;
}
//cts
set_be24(&_rtmp_packet->buffer[2], cts);
_rtmp_packet->time_stamp = dts;
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
_rtmp_packet->chunk_id = CHUNK_VIDEO;
_rtmp_packet->stream_index = STREAM_MEDIA;
_rtmp_packet->type_id = MSG_VIDEO;
//输出rtmp packet
RtmpCodec::inputRtmp(_rtmp_packet);
_rtmp_packet = nullptr;
// flags
_rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4);
_rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu;
int32_t cts = pts - dts;
// cts
set_be24(&_rtmp_packet->buffer[2], cts);
_rtmp_packet->time_stamp = dts;
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
_rtmp_packet->chunk_id = CHUNK_VIDEO;
_rtmp_packet->stream_index = STREAM_MEDIA;
_rtmp_packet->type_id = MSG_VIDEO;
// 输出rtmp packet
RtmpCodec::inputRtmp(_rtmp_packet);
_rtmp_packet = nullptr;
}, &_rtmp_packet->buffer);
}
void H265RtmpEncoder::makeVideoConfigPkt() {
#ifdef ENABLE_MP4
int8_t flags = FLV_CODEC_H265;
flags |= (FLV_KEY_FRAME << 4);
bool is_config = true;
auto rtmpPkt = RtmpPacket::create();
//header
rtmpPkt->buffer.push_back(flags);
rtmpPkt->buffer.push_back(!is_config);
//cts
rtmpPkt->buffer.append("\x0\x0\x0", 3);
auto flags = (uint8_t)RtmpVideoCodec::h265;
flags |= ((uint8_t)RtmpFrameType::key_frame << 4);
auto pkt = RtmpPacket::create();
// header
pkt->buffer.push_back(flags);
pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
// cts
pkt->buffer.append("\x0\x0\x0", 3);
struct mpeg4_hevc_t hevc;
memset(&hevc, 0, sizeof(hevc));
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
string("\x00\x00\x00\x01", 4) + _sps +
string("\x00\x00\x00\x01", 4) + _pps;
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps;
h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int)vps_sps_pps.size(), NULL, 0, NULL, NULL);
uint8_t extra_data[1024];
int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data));
@ -226,17 +278,17 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
WarnL << "生成H265 extra_data 失败";
return;
}
//HEVCDecoderConfigurationRecord
rtmpPkt->buffer.append((char *)extra_data, extra_data_size);
rtmpPkt->body_size = rtmpPkt->buffer.size();
rtmpPkt->chunk_id = CHUNK_VIDEO;
rtmpPkt->stream_index = STREAM_MEDIA;
rtmpPkt->time_stamp = 0;
rtmpPkt->type_id = MSG_VIDEO;
RtmpCodec::inputRtmp(rtmpPkt);
// HEVCDecoderConfigurationRecord
pkt->buffer.append((char *)extra_data, extra_data_size);
pkt->body_size = pkt->buffer.size();
pkt->chunk_id = CHUNK_VIDEO;
pkt->stream_index = STREAM_MEDIA;
pkt->time_stamp = 0;
pkt->type_id = MSG_VIDEO;
RtmpCodec::inputRtmp(pkt);
#else
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
#endif
}
}//namespace mediakit
} // namespace mediakit

View File

@ -15,7 +15,7 @@
#include "Extension/Track.h"
#include "Extension/H265.h"
namespace mediakit{
namespace mediakit {
/**
* h265 Rtmp解码类
* h265 over rtmp h265-Frame
@ -25,7 +25,7 @@ public:
using Ptr = std::shared_ptr<H265RtmpDecoder>;
H265RtmpDecoder();
~H265RtmpDecoder() {}
~H265RtmpDecoder() = default;
/**
* 265 Rtmp包
@ -33,22 +33,23 @@ public:
*/
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
CodecId getCodecId() const override{
return CodecH265;
}
CodecId getCodecId() const override { return CodecH265; }
protected:
void onGetH265(const char *pcData, size_t iLen, uint32_t dts,uint32_t pts);
H265Frame::Ptr obtainFrame();
void onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts);
void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts);
protected:
RtmpPacketInfo _info;
H265Frame::Ptr _h265frame;
};
/**
* 265 Rtmp打包类
*/
class H265RtmpEncoder : public H265RtmpDecoder{
class H265RtmpEncoder : public H265RtmpDecoder {
public:
using Ptr = std::shared_ptr<H265RtmpEncoder>;
@ -87,9 +88,9 @@ private:
std::string _pps;
H265Track::Ptr _track;
RtmpPacket::Ptr _rtmp_packet;
FrameMerger _merger{FrameMerger::mp4_nal_size};
FrameMerger _merger { FrameMerger::mp4_nal_size };
};
}//namespace mediakit
} // namespace mediakit
#endif //ZLMEDIAKIT_H265RTMPCODEC_H
#endif // ZLMEDIAKIT_H265RTMPCODEC_H

View File

@ -302,7 +302,7 @@ void H265RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool i
}
void H265RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
if (len + 3 <= getMaxSize()) {
if (len <= getMaxSize()) {
//signal-nalu
RtpCodec::inputRtp(makeRtp(getTrackType(), ptr, len, is_mark, pts), gop_pos);
} else {

View File

@ -39,10 +39,8 @@ public:
using RingDataType = std::shared_ptr<toolkit::List<FMP4Packet::Ptr> >;
using RingType = toolkit::RingBuffer<RingDataType>;
FMP4MediaSource(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
FMP4MediaSource(const MediaTuple& tuple,
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, tuple), _ring_size(ring_size) {}
~FMP4MediaSource() override { flush(); }
@ -53,8 +51,8 @@ public:
return _ring;
}
void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) override {
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
_ring->getInfoList(cb, on_change);
}
@ -108,7 +106,7 @@ public:
private:
void createRing(){
std::weak_ptr<FMP4MediaSource> weak_self = std::dynamic_pointer_cast<FMP4MediaSource>(shared_from_this());
std::weak_ptr<FMP4MediaSource> weak_self = std::static_pointer_cast<FMP4MediaSource>(shared_from_this());
_ring = std::make_shared<RingType>(_ring_size, [weak_self](int size) {
auto strong_self = weak_self.lock();
if (!strong_self) {

View File

@ -11,8 +11,6 @@
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#if defined(ENABLE_MP4)
#include "FMP4MediaSource.h"
#include "Record/MP4Muxer.h"
@ -23,12 +21,9 @@ class FMP4MediaSourceMuxer final : public MP4MuxerMemory, public MediaSourceEven
public:
using Ptr = std::shared_ptr<FMP4MediaSourceMuxer>;
FMP4MediaSourceMuxer(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
const ProtocolOption &option) {
FMP4MediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) {
_option = option;
_media_src = std::make_shared<FMP4MediaSource>(vhost, app, stream_id);
_media_src = std::make_shared<FMP4MediaSource>(tuple);
}
~FMP4MediaSourceMuxer() override { MP4MuxerMemory::flush(); };
@ -66,7 +61,8 @@ public:
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
}
void onAllTrackReady() {
void addTrackCompleted() override {
MP4MuxerMemory::addTrackCompleted();
_media_src->setInitSegment(getInitSegment());
}
@ -89,5 +85,4 @@ private:
}//namespace mediakit
#endif// defined(ENABLE_MP4)
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H

View File

@ -37,7 +37,7 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) {
if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') {
segment.duration = extinf_dur;
segment.url = Parser::merge_url(http_url, line);
segment.url = Parser::mergeUrl(http_url, line);
if (!_is_m3u8_inner) {
//ts按照先后顺序排序
ts_map.emplace(index++, segment);

View File

@ -71,6 +71,11 @@ void HlsPlayer::teardown() {
void HlsPlayer::fetchSegment() {
if (_ts_list.empty()) {
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628
if(!HlsParser::isLive()){
teardown();
return;
}
//播放列表为空那么立即重新下载m3u8文件
_timer.reset();
fetchIndexFile();
@ -80,7 +85,7 @@ void HlsPlayer::fetchSegment() {
//播放器目前还存活,正在下载中
return;
}
weak_ptr<HlsPlayer> weak_self = dynamic_pointer_cast<HlsPlayer>(shared_from_this());
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
if (!_http_ts_player) {
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller());
_http_ts_player->setOnCreateSocket([weak_self](const EventPoller::Ptr &poller) {
@ -121,18 +126,21 @@ void HlsPlayer::fetchSegment() {
WarnL << "Download ts segment " << url << " failed:" << err;
if (err.getErrCode() == Err_timeout) {
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE);
}else{
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple -1 , MIN_TIMEOUT_MULTIPLE);
} else {
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple - 1, MIN_TIMEOUT_MULTIPLE);
}
}
//提前半秒下载好
auto delay = duration - ticker.elapsedTime() / 1000.0f - 0.5;
if (delay <= 0) {
//延时最小10ms
delay = 10;
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628
auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f;
if (delay > 2.0) {
// 提前1秒下载
delay -= 1.0;
} else if (delay <= 0) {
// 延时最小10ms
delay = 0.01;
}
//延时下载下一个切片
strong_self->_timer_ts.reset(new Timer(delay / 1000.0f, [weak_self]() {
// 延时下载下一个切片
strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->fetchSegment();
@ -186,7 +194,7 @@ bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts
throw invalid_argument("empty sub hls list:" + getUrl());
}
_timer.reset();
weak_ptr<HlsPlayer> weak_self = dynamic_pointer_cast<HlsPlayer>(shared_from_this());
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
auto url = ts_map.rbegin()->second.url;
getPoller()->async([weak_self, url]() {
auto strong_self = weak_self.lock();
@ -259,7 +267,7 @@ bool HlsPlayer::onRedirectUrl(const string &url, bool temporary) {
}
void HlsPlayer::playDelay() {
weak_ptr<HlsPlayer> weak_self = dynamic_pointer_cast<HlsPlayer>(shared_from_this());
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
_timer.reset(new Timer(delaySecond(), [weak_self]() {
auto strong_self = weak_self.lock();
if (strong_self) {

View File

@ -21,7 +21,7 @@ namespace mediakit {
void HttpClient::sendRequest(const string &url) {
clearResponse();
_url = url;
auto protocol = FindField(url.data(), NULL, "://");
auto protocol = findSubString(url.data(), NULL, "://");
uint16_t port;
bool is_https;
if (strcasecmp(protocol.data(), "http") == 0) {
@ -35,11 +35,11 @@ void HttpClient::sendRequest(const string &url) {
throw std::invalid_argument(strErr);
}
auto host = FindField(url.data(), "://", "/");
auto host = findSubString(url.data(), "://", "/");
if (host.empty()) {
host = FindField(url.data(), "://", NULL);
host = findSubString(url.data(), "://", NULL);
}
_path = FindField(url.data(), host.data(), NULL);
_path = findSubString(url.data(), host.data(), NULL);
if (_path.empty()) {
_path = "/";
}
@ -100,7 +100,7 @@ void HttpClient::clearResponse() {
_header_recved = false;
_recved_body_size = 0;
_total_body_size = 0;
_parser.Clear();
_parser.clear();
_chunked_splitter = nullptr;
_wait_header.resetTime();
_wait_body.resetTime();
@ -176,25 +176,25 @@ void HttpClient::onRecv(const Buffer::Ptr &pBuf) {
HttpRequestSplitter::input(pBuf->data(), pBuf->size());
}
void HttpClient::onErr(const SockException &ex) {
void HttpClient::onError(const SockException &ex) {
onResponseCompleted_l(ex);
}
ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
_parser.Parse(data);
if (_parser.Url() == "302" || _parser.Url() == "301" || _parser.Url() == "303") {
auto new_url = Parser::merge_url(_url, _parser["Location"]);
_parser.parse(data, len);
if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") {
auto new_url = Parser::mergeUrl(_url, _parser["Location"]);
if (new_url.empty()) {
throw invalid_argument("未找到Location字段(跳转url)");
}
if (onRedirectUrl(new_url, _parser.Url() == "302")) {
if (onRedirectUrl(new_url, _parser.status() == "302")) {
HttpClient::sendRequest(new_url);
return 0;
}
}
checkCookie(_parser.getHeader());
onResponseHeader(_parser.Url(), _parser.getHeader());
onResponseHeader(_parser.status(), _parser.getHeader());
_header_recved = true;
if (_parser["Transfer-Encoding"] == "chunked") {
@ -226,7 +226,7 @@ ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
if (_total_body_size == 0) {
//后续没content本次http请求结束
onResponseCompleted_l(SockException(Err_success, "success"));
onResponseCompleted_l(SockException(Err_success, "The request is successful but has no body"));
return 0;
}
@ -260,7 +260,7 @@ void HttpClient::onRecvContent(const char *data, size_t len) {
if (_recved_body_size == (size_t)_total_body_size) {
//content接收完毕
onResponseBody(data, len);
onResponseCompleted_l(SockException(Err_success, "success"));
onResponseCompleted_l(SockException(Err_success, "completed"));
return;
}
@ -329,7 +329,7 @@ void HttpClient::onResponseCompleted_l(const SockException &ex) {
if (_total_body_size > 0 && _recved_body_size >= (size_t)_total_body_size) {
//回复header中有content-length信息那么收到的body大于等于声明值则认为成功
onResponseCompleted(SockException(Err_success, "success"));
onResponseCompleted(SockException(Err_success, "read body completed"));
return;
}
@ -361,8 +361,8 @@ void HttpClient::checkCookie(HttpClient::HttpHeader &headers) {
int index = 0;
auto arg_vec = split(it_set_cookie->second, ";");
for (string &key_val : arg_vec) {
auto key = FindField(key_val.data(), NULL, "=");
auto val = FindField(key_val.data(), "=", NULL);
auto key = findSubString(key_val.data(), NULL, "=");
auto val = findSubString(key_val.data(), "=", NULL);
if (index++ == 0) {
cookie->setKeyVal(key, val);

View File

@ -22,7 +22,7 @@
#include "HttpRequestSplitter.h"
#include "HttpCookie.h"
#include "HttpChunkedSplitter.h"
#include "strCoding.h"
#include "Common/strCoding.h"
#include "HttpBody.h"
namespace mediakit {
@ -177,7 +177,7 @@ protected:
//// TcpClient override ////
void onConnect(const toolkit::SockException &ex) override;
void onRecv(const toolkit::Buffer::Ptr &pBuf) override;
void onErr(const toolkit::SockException &ex) override;
void onError(const toolkit::SockException &ex) override;
void onFlush() override;
void onManager() override;

View File

@ -18,7 +18,7 @@ using namespace toolkit;
namespace mediakit{
const char *getHttpStatusMessage(int status) {
const char *HttpConst::getHttpStatusMessage(int status) {
switch (status) {
case 100: return "Continue";
case 101: return "Switching Protocol";
@ -196,7 +196,7 @@ static const char *s_mime_src[][2] = {
{"avi", "video/x-msvideo"},
};
const string &getHttpContentType(const char *name) {
const string& HttpConst::getHttpContentType(const char *name) {
const char *dot;
dot = strrchr(name, '.');
static StrCaseMap mapType;

View File

@ -15,19 +15,25 @@
namespace mediakit{
/**
* http错误代码获取字符说明
* @param status 404
* @return Not Found
*/
const char *getHttpStatusMessage(int status);
class HttpConst {
public:
HttpConst() = delete;
~HttpConst() = delete;
/**
* http mime
* @param name html
* @return mime值text/html
*/
const std::string &getHttpContentType(const char *name);
/**
* http错误代码获取字符说明
* @param status 404
* @return Not Found
*/
static const char *getHttpStatusMessage(int status);
/**
* http mime
* @param name html
* @return mime值text/html
*/
static const std::string &getHttpContentType(const char *name);
};
}//mediakit

View File

@ -61,7 +61,7 @@ bool HttpServerCookie::isExpired() {
return _ticker.elapsedTime() > _max_elapsed * 1000;
}
void HttpServerCookie::setAttach(std::shared_ptr<void> attach) {
void HttpServerCookie::setAttach(toolkit::Any attach) {
_attach = std::move(attach);
}
@ -114,8 +114,7 @@ void HttpCookieManager::onManager() {
}
}
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in,
uint64_t max_elapsed, std::shared_ptr<void> attach, int max_client) {
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, uint64_t max_elapsed, toolkit::Any attach, int max_client) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto cookie = _generator.obtain();
auto uid = uid_in.empty() ? cookie : uid_in;
@ -158,9 +157,9 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, co
if (it == http_header.end()) {
return nullptr;
}
auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";");
auto cookie = findSubString(it->second.data(), (cookie_name + "=").data(), ";");
if (cookie.empty()) {
cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr);
cookie = findSubString(it->second.data(), (cookie_name + "=").data(), nullptr);
}
if (cookie.empty()) {
return nullptr;

View File

@ -85,14 +85,14 @@ public:
/**
*
*/
void setAttach(std::shared_ptr<void> attach);
void setAttach(toolkit::Any attach);
/*
*
*/
template <class T>
T& getAttach() {
return *static_cast<T *>(_attach.get());
return _attach.get<T>();
}
private:
@ -104,7 +104,7 @@ private:
std::string _cookie_uuid;
uint64_t _max_elapsed;
toolkit::Ticker _ticker;
std::shared_ptr<void> _attach;
toolkit::Any _attach;
std::weak_ptr<HttpCookieManager> _manager;
};
@ -163,7 +163,7 @@ public:
*/
HttpServerCookie::Ptr addCookie(
const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,
std::shared_ptr<void> attach = nullptr,
toolkit::Any = toolkit::Any{},
int max_client = 1);
/**

View File

@ -20,7 +20,7 @@
#include "Record/HlsMediaSource.h"
#include "Common/Parser.h"
#include "Common/config.h"
#include "strCoding.h"
#include "Common/strCoding.h"
using namespace std;
using namespace toolkit;
@ -31,12 +31,16 @@ namespace mediakit {
// 每次访问一次该cookie那么将重新刷新cookie有效期
// 假如播放器在60秒内都未访问该cookie那么将重新触发hls播放鉴权
static int kHlsCookieSecond = 60;
static int kFindSrcIntervalSecond = 3;
static const string kCookieName = "ZL_COOKIE";
static const string kHlsSuffix = "/hls.m3u8";
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
struct HttpCookieAttachment {
//是否已经查找到过MediaSource
// 是否已经查找到过MediaSource
bool _find_src = false;
// 查找MediaSource计时
Ticker _find_src_ticker;
//cookie生效作用域本cookie只对该目录下的文件生效
string _path;
//上次鉴权失败信息,为空则上次鉴权成功
@ -46,7 +50,112 @@ struct HttpCookieAttachment {
};
const string &HttpFileManager::getContentType(const char *name) {
return getHttpContentType(name);
return HttpConst::getHttpContentType(name);
}
namespace {
class UInt128 {
public:
UInt128() = default;
UInt128(const struct sockaddr_storage &storage) {
_family = storage.ss_family;
memset(_bytes, 0, 16);
switch (storage.ss_family) {
case AF_INET: {
memcpy(_bytes, &(reinterpret_cast<const struct sockaddr_in &>(storage).sin_addr), 4);
break;
}
case AF_INET6: {
memcpy(_bytes, &(reinterpret_cast<const struct sockaddr_in6 &>(storage).sin6_addr), 16);
break;
}
default: CHECK(false, "Invalid socket family"); break;
}
}
bool operator==(const UInt128 &that) const { return _family == that._family && !memcmp(_bytes, that._bytes, 16); }
bool operator<=(const UInt128 &that) const { return *this < that || *this == that; }
bool operator>=(const UInt128 &that) const { return *this > that || *this == that; }
bool operator>(const UInt128 &that) const { return that < *this; }
bool operator<(const UInt128 &that) const {
auto sz = _family == AF_INET ? 4 : 16;
for (int i = 0; i < sz; ++i) {
if (_bytes[i] < that._bytes[i]) {
return true;
} else if (_bytes[i] > that._bytes[i]) {
return false;
}
}
return false;
}
operator bool() const { return _family != -1; }
bool same_type(const UInt128 &that) const { return _family == that._family; }
private:
int _family = -1;
uint8_t _bytes[16];
};
}
static UInt128 get_ip_uint64(const std::string &ip) {
try {
return UInt128(SockUtil::make_sockaddr(ip.data(), 0));
} catch (std::exception &ex) {
WarnL << ex.what();
}
return UInt128();
}
bool HttpFileManager::isIPAllowed(const std::string &ip) {
using IPRangs = std::vector<std::pair<UInt128 /*min_ip*/, UInt128 /*max_ip*/>>;
GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs {
IPRangs ret;
auto vec = split(str, ",");
for (auto &item : vec) {
if (trim(item).empty()) {
continue;
}
auto range = split(item, "-");
if (range.size() == 2) {
auto ip_min = get_ip_uint64(trim(range[0]));
auto ip_max = get_ip_uint64(trim(range[1]));
if (ip_min && ip_max && ip_min.same_type(ip_max)) {
ret.emplace_back(ip_min, ip_max);
} else {
WarnL << "Invalid ip range or family: " << item;
}
} else if (range.size() == 1) {
auto ip = get_ip_uint64(trim(range[0]));
if (ip) {
ret.emplace_back(ip, ip);
} else {
WarnL << "Invalid ip: " << item;
}
} else {
WarnL << "Invalid ip range: " << item;
}
}
return ret;
});
if (allow_ip_range.empty()) {
return true;
}
auto ip_int = get_ip_uint64(ip);
for (auto &range : allow_ip_range) {
if (ip_int.same_type(range.first) && ip_int >= range.first && ip_int <= range.second) {
return true;
}
}
return false;
}
static string searchIndexFile(const string &dir){
@ -57,7 +166,7 @@ static string searchIndexFile(const string &dir){
}
set<string> setFile;
while ((pDirent = readdir(pDir)) != NULL) {
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm"};
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
string ret = pDirent->d_name;
closedir(pDir);
@ -188,7 +297,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con
//cookie有效期为kHlsCookieSecond
invoker(err, "", kHlsCookieSecond);
};
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast<SockInfo &>(sender));
bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender);
if (!flag) {
//未开启鉴权,那么允许播放
auth_invoker("");
@ -240,8 +349,8 @@ public:
static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir,
const function<void(const string &err_msg, const HttpServerCookie::Ptr &cookie)> &callback) {
//获取用户唯一id
auto uid = parser.Params();
auto path = parser.Url();
auto uid = parser.params();
auto path = parser.url();
//先根据http头中的cookie字段获取cookie
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
@ -268,7 +377,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
return;
}
//上次鉴权失败但是如果url参数发生变更那么也重新鉴权下
if (parser.Params().empty() || parser.Params() == cookie->getUid()) {
if (parser.params().empty() || parser.params() == cookie->getUid()) {
//url参数未变或者本来就没有url参数那么判断本次请求为重复请求无访问权限
callback(attach._err_msg, update_cookie ? cookie : nullptr);
return;
@ -278,7 +387,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
HttpCookieManager::Instance().delCookie(cookie);
}
bool is_hls = media_info._schema == HLS_SCHEMA;
bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA;
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
info->_identifier = sender.getIdentifier();
@ -308,7 +417,9 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
// hls相关信息
attach->_hls_data = std::make_shared<HlsCookieData>(media_info, info);
}
callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, attach));
toolkit::Any any;
any.set(std::move(attach));
callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, std::move(any)));
} else {
callback(err_msg, nullptr);
}
@ -320,10 +431,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
return;
}
//事件未被拦截则认为是http下载请求
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast<SockInfo &>(sender));
// 事件未被拦截则认为是http下载请求
bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
if (!flag) {
//此事件无人监听,我们默认都有权限访问
// 此事件无人监听,我们默认都有权限访问
callback("", nullptr);
}
}
@ -355,7 +466,7 @@ static string pathCat(const string &a, const string &b){
* @param cb
*/
static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) {
bool is_hls = end_with(file_path, kHlsSuffix);
bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix);
if (!is_hls && !File::fileExist(file_path.data())) {
//文件不存在且不是hls,那么直接返回404
sendNotFound(cb);
@ -363,11 +474,16 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
}
if (is_hls) {
// hls那么移除掉后缀获取真实的stream_id并且修改协议为HLS
const_cast<string &>(media_info._schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info._streamid), kHlsSuffix, "");
if (end_with(file_path, kHlsSuffix)) {
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
} else {
const_cast<string &>(media_info.schema) = HLS_FMP4_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsFMP4Suffix, "");
}
}
weak_ptr<Session> weakSession = sender.shared_from_this();
weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this());
//判断是否有权限访问该文件
canAccessPath(sender, parser, media_info, false, [cb, file_path, parser, is_hls, media_info, weakSession](const string &err_msg, const HttpServerCookie::Ptr &cookie) {
auto strongSession = weakSession.lock();
@ -421,14 +537,15 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
return;
}
auto src = cookie->getAttach<HttpCookieAttachment>()._hls_data->getMediaSource();
auto &attach = cookie->getAttach<HttpCookieAttachment>();
auto src = attach._hls_data->getMediaSource();
if (src) {
//直接从内存获取m3u8索引文件(而不是从文件系统)
// 直接从内存获取m3u8索引文件(而不是从文件系统)
response_file(cookie, cb, file_path, parser, src->getIndexFile());
return;
}
if (cookie->getAttach<HttpCookieAttachment>()._find_src) {
//查找过MediaSource但是流已经注销了不用再查找
if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) {
// 最近已经查找过MediaSource了为了防止频繁查找导致占用全局互斥锁的问题我们尝试直接从磁盘返回hls索引文件
response_file(cookie, cb, file_path, parser);
return;
}
@ -444,11 +561,14 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
auto &attach = cookie->getAttach<HttpCookieAttachment>();
attach._hls_data->setMediaSource(hls);
//添加HlsMediaSource的观看人数(HLS是按需生成的这样可以触发HLS文件的生成)
// 添加HlsMediaSource的观看人数(HLS是按需生成的这样可以触发HLS文件的生成)
attach._hls_data->addByteUsage(0);
//标记找到MediaSource
// 标记找到MediaSource
attach._find_src = true;
// 重置查找MediaSource计时
attach._find_src_ticker.resetTime();
// m3u8文件可能不存在, 等待m3u8索引文件按需生成
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
response_file(cookie, cb, file_path, parser, file);
@ -457,7 +577,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
});
}
static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){
static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) {
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
GET_CONFIG(string, rootPath, Http::kRootPath);
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
@ -465,15 +585,15 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
});
string url, path;
auto it = virtualPathMap.find(media_info._app);
auto it = virtualPathMap.find(media_info.app);
if (it != virtualPathMap.end()) {
//访问的是virtualPath
path = it->second;
url = parser.Url().substr(1 + media_info._app.size());
url = parser.url().substr(1 + media_info.app.size());
} else {
//访问的是rootPath
path = rootPath;
url = parser.Url();
url = parser.url();
}
for (auto &ch : url) {
if (ch == '\\') {
@ -481,8 +601,15 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
ch = '/';
}
}
auto ret = File::absolutePath(enableVhost ? media_info._vhost + url : url, path);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path);
if (!start_with(ret, http_root)) {
// 访问的http文件不得在http根目录之外
throw std::runtime_error("Attempting to access files outside of the http root directory");
}
// 替换url防止返回的目录索引网页被注入非法内容
const_cast<Parser&>(parser).setUrl("/" + ret.substr(http_root.size()));
NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
return ret;
}
@ -493,7 +620,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
* @param cb
*/
void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) {
auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl();
auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.fullUrl();
MediaInfo media_info(fullUrl);
auto file_path = getFilePath(parser, media_info, sender);
if (file_path.size() == 0) {
@ -504,15 +631,18 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
if (File::is_dir(file_path.data())) {
auto indexFile = searchIndexFile(file_path);
if (!indexFile.empty()) {
//发现该文件夹下有index文件
// 发现该文件夹下有index文件
file_path = pathCat(file_path, indexFile);
parser.setUrl(pathCat(parser.Url(), indexFile));
accessFile(sender, parser, media_info, file_path, cb);
return;
if (!File::is_dir(file_path.data())) {
// 不是文件夹
parser.setUrl(pathCat(parser.url(), indexFile));
accessFile(sender, parser, media_info, file_path, cb);
return;
}
}
string strMenu;
//生成文件夹菜单索引
if (!makeFolderMenu(parser.Url(), file_path, strMenu)) {
if (!makeFolderMenu(parser.url(), file_path, strMenu)) {
//文件夹不存在
sendNotFound(cb);
return;
@ -600,8 +730,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
if (!strRange.empty()) {
//分节下载
code = 206;
auto iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
auto iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data());
auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data());
auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data());
auto fileSize = fileBody->remainSize();
if (iRangeEnd == 0) {
iRangeEnd = fileSize - 1;
@ -621,4 +751,4 @@ HttpResponseInvokerImp::operator bool(){
}
}//namespace mediakit
}//namespace mediakit

View File

@ -62,6 +62,13 @@ public:
* @return mime值
*/
static const std::string &getContentType(const char *name);
/**
* ip是否再白名单中
* @param ip ipv4和ipv6
*/
static bool isIPAllowed(const std::string &ip);
private:
HttpFileManager() = delete;
~HttpFileManager() = delete;

View File

@ -91,7 +91,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
_remain_data.assign(ptr, _remain_data_size);
return;
}
//收到content数据并且接content完毕
//收到content数据并且接content完毕
onRecvContent(ptr,_content_len);
_remain_data_size -= _content_len;

View File

@ -27,7 +27,7 @@ void HttpRequester::onResponseBody(const char *buf, size_t size) {
void HttpRequester::onResponseCompleted(const SockException &ex) {
if (ex && _retry++ < _max_retry) {
std::weak_ptr<HttpRequester> weak_self = std::dynamic_pointer_cast<HttpRequester>(shared_from_this());
std::weak_ptr<HttpRequester> weak_self = std::static_pointer_cast<HttpRequester>(shared_from_this());
getPoller()->doDelayTask(_retry_delay, [weak_self](){
if (auto self = weak_self.lock()) {
InfoL << "resend request " << self->getUrl() << " with retry " << self->getRetry();
@ -271,7 +271,7 @@ static void sendReport() {
}
static toolkit::onceToken s_token([]() {
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPool &pool, size_t &size) {
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) {
// 第一次汇报在程序启动后5分钟
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
sendReport();

View File

@ -12,7 +12,7 @@
#include <sys/stat.h>
#include <algorithm>
#include "Common/config.h"
#include "strCoding.h"
#include "Common/strCoding.h"
#include "HttpSession.h"
#include "HttpConst.h"
#include "Util/base64.h"
@ -24,105 +24,168 @@ using namespace toolkit;
namespace mediakit {
HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) {
GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
GET_CONFIG(uint32_t, keep_alive_sec, Http::kKeepAliveSecond);
pSock->setSendTimeOutSecond(keep_alive_sec);
}
HttpSession::~HttpSession() = default;
void HttpSession::Handle_Req_HEAD(ssize_t &content_len){
//暂时全部返回200 OK因为HTTP GET存在按需生成流的操作所以不能按照HTTP GET的流程返回
//如果直接返回404那么又会导致按需生成流的逻辑失效所以HTTP HEAD在静态文件或者已存在资源时才有效
//对于按需生成流的直播场景并不适用
void HttpSession::onHttpRequest_HEAD() {
// 暂时全部返回200 OK因为HTTP GET存在按需生成流的操作所以不能按照HTTP GET的流程返回
// 如果直接返回404那么又会导致按需生成流的逻辑失效所以HTTP HEAD在静态文件或者已存在资源时才有效
// 对于按需生成流的直播场景并不适用
sendResponse(200, false);
}
void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) {
void HttpSession::onHttpRequest_OPTIONS() {
KeyValue header;
header.emplace("Allow", "GET, POST, OPTIONS");
header.emplace("Access-Control-Allow-Origin", "*");
header.emplace("Allow", "GET, POST, HEAD, OPTIONS");
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
if (allow_cross_domains) {
header.emplace("Access-Control-Allow-Origin", "*");
header.emplace("Access-Control-Allow-Headers", "*");
header.emplace("Access-Control-Allow-Methods", "GET, POST, HEAD, OPTIONS");
}
header.emplace("Access-Control-Allow-Credentials", "true");
header.emplace("Access-Control-Request-Methods", "GET, POST, OPTIONS");
header.emplace("Access-Control-Request-Headers", "Accept,Accept-Language,Content-Language,Content-Type");
sendResponse(200, true, nullptr, header);
}
ssize_t HttpSession::onRecvHeader(const char *header,size_t len) {
typedef void (HttpSession::*HttpCMDHandle)(ssize_t &);
static unordered_map<string, HttpCMDHandle> s_func_map;
ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
using func_type = void (HttpSession::*)();
static unordered_map<string, func_type> s_func_map;
static onceToken token([]() {
s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD);
s_func_map.emplace("OPTIONS",&HttpSession::Handle_Req_OPTIONS);
}, nullptr);
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
// DELETE命令用于whip/whep用只用于触发http api
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD);
s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS);
});
_parser.Parse(header);
CHECK(_parser.Url()[0] == '/');
_parser.parse(header, len);
CHECK(_parser.url()[0] == '/');
urlDecode(_parser);
string cmd = _parser.Method();
auto &cmd = _parser.method();
auto it = s_func_map.find(cmd);
if (it == s_func_map.end()) {
WarnP(this) << "不支持该命令:" << cmd;
WarnP(this) << "Http method not supported: " << cmd;
sendResponse(405, true);
return 0;
}
//跨域
_origin = _parser["Origin"];
size_t content_len;
auto &content_len_str = _parser["Content-Length"];
if (content_len_str.empty()) {
if (it->first == "POST") {
// Http post未指定长度我们认为是不定长的body
WarnL << "Received http post request without content-length, consider it to be unlimited length";
content_len = SIZE_MAX;
} else {
content_len = 0;
}
} else {
// 已经指定长度
content_len = atoll(content_len_str.data());
}
//默认后面数据不是content而是header
ssize_t content_len = 0;
(this->*(it->second))(content_len);
if (content_len == 0) {
//// 没有body的情况直接触发回调 ////
(this->*(it->second))();
_parser.clear();
// 如果设置了_on_recv_body, 那么说明后续要处理body
return _on_recv_body ? -1 : 0;
}
//清空解析器节省内存
_parser.Clear();
//返回content长度
return content_len;
GET_CONFIG(size_t, maxReqSize, Http::kMaxReqSize);
if (content_len > maxReqSize) {
//// 不定长body或超大body ////
if (content_len != SIZE_MAX) {
WarnL << "Http body size is too huge: " << content_len << " > " << maxReqSize
<< ", please set " << Http::kMaxReqSize << " in config.ini file.";
}
size_t received = 0;
auto parser = std::move(_parser);
_on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable {
received += len;
onRecvUnlimitedContent(parser, data, len, content_len, received);
if (received < content_len) {
// 还没收满
return true;
}
// 收满了
setContentLen(0);
return false;
};
// 声明后续都是bodyHttp body在本对象缓冲不通过HttpRequestSplitter保存
return -1;
}
//// body size明确指定且小于最大值的情况 ////
auto body = std::make_shared<std::string>();
// 预留一定的内存buffer防止频繁的内存拷贝
body->reserve(content_len);
_on_recv_body = [this, body, content_len, it](const char *data, size_t len) mutable {
body->append(data, len);
if (body->size() < content_len) {
// 未收满数据
return true;
}
// 收集body完毕
_parser.setContent(std::move(*body));
(this->*(it->second))();
_parser.clear();
// 后续是header
setContentLen(0);
return false;
};
// 声明后续都是bodyHttp body在本对象缓冲不通过HttpRequestSplitter保存
return -1;
}
void HttpSession::onRecvContent(const char *data,size_t len) {
if(_contentCallBack){
if(!_contentCallBack(data,len)){
_contentCallBack = nullptr;
}
void HttpSession::onRecvContent(const char *data, size_t len) {
if (_on_recv_body && !_on_recv_body(data, len)) {
_on_recv_body = nullptr;
}
}
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
_ticker.resetTime();
input(pBuf->data(),pBuf->size());
input(pBuf->data(), pBuf->size());
}
void HttpSession::onError(const SockException& err) {
void HttpSession::onError(const SockException &err) {
if (_is_live_stream) {
//flv/ts播放器
// flv/ts播放器
uint64_t duration = _ticker.createdTime() / 1000;
WarnP(this) << "FLV/TS/FMP4播放器("
<< _mediaInfo.shortUrl()
<< ")断开:" << err
<< ",耗时(s):" << duration;
WarnP(this) << "FLV/TS/FMP4播放器(" << _mediaInfo.shortUrl() << ")断开:" << err << ",耗时(s):" << duration;
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_total_bytes_usage >= iFlowThreshold * 1024) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage,
duration, true, static_cast<SockInfo &>(*this));
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this);
}
return;
}
}
void HttpSession::onManager() {
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
if(_ticker.elapsedTime() > keepAliveSec * 1000){
//1分钟超时
shutdown(SockException(Err_timeout,"session timeout"));
if (_ticker.elapsedTime() > keepAliveSec * 1000) {
// 1分钟超时
shutdown(SockException(Err_timeout, "session timeout"));
}
}
bool HttpSession::checkWebSocket(){
bool HttpSession::checkWebSocket() {
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
if (Sec_WebSocket_Key.empty()) {
return false;
@ -142,25 +205,31 @@ bool HttpSession::checkWebSocket(){
sendResponse(101, false, nullptr, headerOut, nullptr, true);
};
//判断是否为websocket-flv
if (checkLiveStreamFlv(res_cb)) {
//这里是websocket-flv直播请求
auto res_cb_flv = [this, headerOut]() mutable {
_live_over_websocket = true;
headerOut.emplace("Cache-Control", "no-store");
sendResponse(101, false, nullptr, headerOut, nullptr, true);
};
// 判断是否为websocket-flv
if (checkLiveStreamFlv(res_cb_flv)) {
// 这里是websocket-flv直播请求
return true;
}
//判断是否为websocket-ts
// 判断是否为websocket-ts
if (checkLiveStreamTS(res_cb)) {
//这里是websocket-ts直播请求
// 这里是websocket-ts直播请求
return true;
}
//判断是否为websocket-fmp4
// 判断是否为websocket-fmp4
if (checkLiveStreamFMP4(res_cb)) {
//这里是websocket-fmp4直播请求
// 这里是websocket-fmp4直播请求
return true;
}
//这是普通的websocket连接
// 这是普通的websocket连接
if (!onWebSocketConnect(_parser)) {
sendResponse(501, true, nullptr, headerOut);
return true;
@ -169,8 +238,8 @@ bool HttpSession::checkWebSocket(){
return true;
}
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb){
std::string url = _parser.Url();
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb) {
std::string url = _parser.url();
auto it = _parser.getUrlArgs().find("schema");
if (it != _parser.getUrlArgs().end()) {
if (strcasecmp(it->second.c_str(), schema.c_str())) {
@ -180,57 +249,57 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
} else {
auto prefix_size = url_suffix.size();
if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) {
//未找到后缀
// 未找到后缀
return false;
}
// url去除特殊后缀
url.resize(url.size() - prefix_size);
}
//带参数的url
if (!_parser.Params().empty()) {
// 带参数的url
if (!_parser.params().empty()) {
url += "?";
url += _parser.Params();
url += _parser.params();
}
//解析带上协议+参数完整的url
// 解析带上协议+参数完整的url
_mediaInfo.parse(schema + "://" + _parser["Host"] + url);
if (_mediaInfo._app.empty() || _mediaInfo._streamid.empty()) {
//url不合法
if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) {
// url不合法
return false;
}
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
//鉴权结果回调
// 鉴权结果回调
auto onRes = [cb, weak_self, close_flag](const string &err) {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
if (!err.empty()) {
//播放鉴权失败
// 播放鉴权失败
strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
return;
}
//异步查找直播流
// 异步查找直播流
MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
if (!src) {
//未找到该流
// 未找到该流
strong_self->sendNotFound(close_flag);
} else {
strong_self->_is_live_stream = true;
//触发回调
// 触发回调
cb(src);
}
});
@ -242,38 +311,42 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
}
};
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast<SockInfo &>(*this));
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this);
if (!flag) {
//该事件无人监听,默认不鉴权
// 该事件无人监听,默认不鉴权
onRes("");
}
return true;
}
//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(src);
assert(fmp4_src);
if (!cb) {
//找到源发送http头负载后续发送
// 找到源发送http头负载后续发送
sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
} else {
//自定义发送http头
// 自定义发送http头
cb();
}
//直播牺牲延时提升发送性能
// 直播牺牲延时提升发送性能
setSocketFlags();
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
fmp4_src->pause(false);
_fmp4_reader = fmp4_src->getRing()->attach(getPoller());
_fmp4_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
_fmp4_reader->setGetInfoCB([weak_self]() {
Any ret;
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_fmp4_reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached"));
@ -281,83 +354,84 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
size_t i = 0;
auto size = fmp4_list->size();
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) {
strong_self->onWrite(ts, ++i == size);
});
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
});
});
}
//http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
assert(ts_src);
if (!cb) {
//找到源发送http头负载后续发送
// 找到源发送http头负载后续发送
sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
} else {
//自定义发送http头
// 自定义发送http头
cb();
}
//直播牺牲延时提升发送性能
// 直播牺牲延时提升发送性能
setSocketFlags();
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
ts_src->pause(false);
_ts_reader = ts_src->getRing()->attach(getPoller());
_ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
_ts_reader->setDetachCB([weak_self](){
_ts_reader->setGetInfoCB([weak_self]() {
Any ret;
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_ts_reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
strong_self->shutdown(SockException(Err_shutdown,"ts ring buffer detached"));
strong_self->shutdown(SockException(Err_shutdown, "ts ring buffer detached"));
});
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
size_t i = 0;
auto size = ts_list->size();
ts_list->for_each([&](const TSPacket::Ptr &ts) {
strong_self->onWrite(ts, ++i == size);
});
ts_list->for_each([&](const TSPacket::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
});
});
}
//http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb) {
auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data());
return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) {
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
assert(rtmp_src);
if (!cb) {
//找到源发送http头负载后续发送
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true);
// 找到源发送http头负载后续发送
KeyValue headerOut;
headerOut["Cache-Control"] = "no-store";
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true);
} else {
//自定义发送http头
// 自定义发送http头
cb();
}
//直播牺牲延时提升发送性能
// 直播牺牲延时提升发送性能
setSocketFlags();
//非H264/AAC时打印警告日志防止用户提无效问题
// 非H264/AAC时打印警告日志防止用户提无效问题
auto tracks = src->getTracks(false);
for (auto &track : tracks) {
switch (track->getCodecId()) {
case CodecH264:
case CodecAAC:
break;
case CodecAAC: break;
default: {
WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName();
break;
@ -369,46 +443,42 @@ bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
});
}
void HttpSession::Handle_Req_GET(ssize_t &content_len) {
Handle_Req_GET_l(content_len, true);
}
void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) {
//先看看是否为WebSocket请求
void HttpSession::onHttpRequest_GET() {
// 先看看是否为WebSocket请求
if (checkWebSocket()) {
content_len = -1;
_contentCallBack = [this](const char *data, size_t len) {
WebSocketSplitter::decode((uint8_t *) data, len);
//_contentCallBack是可持续的后面还要处理后续数据
// 后续都是websocket body数据
_on_recv_body = [this](const char *data, size_t len) {
WebSocketSplitter::decode((uint8_t *)data, len);
// _contentCallBack是可持续的后面还要处理后续数据
return true;
};
return;
}
if (emitHttpEvent(false)) {
//拦截http api事件
// 拦截http api事件
return;
}
if (checkLiveStreamFlv()) {
//拦截http-flv播放器
// 拦截http-flv播放器
return;
}
if (checkLiveStreamTS()) {
//拦截http-ts播放器
// 拦截http-ts播放器
return;
}
if (checkLiveStreamFMP4()) {
//拦截http-fmp4播放器
// 拦截http-fmp4播放器
return;
}
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
HttpFileManager::onAccessPath(*this, _parser, [weak_self, bClose](int code, const string &content_type,
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
@ -434,12 +504,13 @@ class AsyncSenderData {
public:
friend class AsyncSender;
using Ptr = std::shared_ptr<AsyncSenderData>;
AsyncSenderData(const Session::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) {
_session = dynamic_pointer_cast<HttpSession>(session);
AsyncSenderData(HttpSession::Ptr session, const HttpBody::Ptr &body, bool close_when_complete) {
_session = std::move(session);
_body = body;
_close_when_complete = close_when_complete;
}
~AsyncSenderData() = default;
private:
std::weak_ptr<HttpSession> _session;
HttpBody::Ptr _body;
@ -453,7 +524,7 @@ public:
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
if (data->_read_complete) {
if (data->_close_when_complete) {
//发送完毕需要关闭socket
// 发送完毕需要关闭socket
shutdown(data->_session.lock());
}
return false;
@ -463,13 +534,13 @@ public:
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);
@ -482,14 +553,14 @@ 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还可写继续请求数据
// socket还可写继续请求数据
onSocketFlushed(data);
}
return;
}
//文件写完了
// 文件写完了
data->_read_complete = true;
if (!session->isSocketBusy() && data->_close_when_complete) {
shutdown(session);
@ -497,34 +568,25 @@ private:
}
static void shutdown(const std::shared_ptr<HttpSession> &session) {
if(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";
void HttpSession::sendResponse(int code,
bool bClose,
const char *pcContentType,
const HttpSession::KeyValue &header,
const HttpBody::Ptr &body,
bool no_content_length ){
GET_CONFIG(string,charSet,Http::kCharSet);
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
bool no_content_length) {
GET_CONFIG(string, charSet, Http::kCharSet);
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
//body默认为空
// body默认为空
int64_t size = 0;
if (body && body->remainSize()) {
//有body获取body大小
// 有body获取body大小
size = body->remainSize();
}
@ -532,52 +594,53 @@ void HttpSession::sendResponse(int code,
// http-flv直播是Keep-Alive类型
bClose = false;
} else if ((size_t)size >= SIZE_MAX || size < 0) {
//不固定长度的body那么发送完body后应该关闭socket以便浏览器做下载完毕的判断
// 不固定长度的body那么发送完body后应该关闭socket以便浏览器做下载完毕的判断
bClose = true;
}
HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
headerOut.emplace(kDate, dateStr());
headerOut.emplace(kServer, kServerName);
headerOut.emplace(kConnection, bClose ? "close" : "keep-alive");
headerOut.emplace("Date", dateStr());
headerOut.emplace("Server", kServerName);
headerOut.emplace("Connection", bClose ? "close" : "keep-alive");
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
if (allow_cross_domains) {
headerOut.emplace("Access-Control-Allow-Origin", "*");
headerOut.emplace("Access-Control-Allow-Credentials", "true");
}
if (!bClose) {
string keepAliveString = "timeout=";
keepAliveString += to_string(keepAliveSec);
keepAliveString += ", max=100";
headerOut.emplace(kKeepAlive, std::move(keepAliveString));
}
if (!_origin.empty()) {
//设置跨域
headerOut.emplace(kAccessControlAllowOrigin, _origin);
headerOut.emplace(kAccessControlAllowCredentials, "true");
headerOut.emplace("Keep-Alive", std::move(keepAliveString));
}
if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) {
//文件长度为固定值,且不是http-flv强制设置Content-Length
headerOut[kContentLength] = to_string(size);
// 文件长度为固定值,且不是http-flv强制设置Content-Length
headerOut["Content-Length"] = to_string(size);
}
if (size && !pcContentType) {
//有body时设置缺省类型
// 有body时设置缺省类型
pcContentType = "text/plain";
}
if ((size || no_content_length) && pcContentType) {
//有body时设置文件类型
// 有body时设置文件类型
string strContentType = pcContentType;
strContentType += "; charset=";
strContentType += charSet;
headerOut.emplace(kContentType, std::move(strContentType));
headerOut.emplace("Content-Type", std::move(strContentType));
}
//发送http头
// 发送http头
string str;
str.reserve(256);
str += "HTTP/1.1 ";
str += to_string(code);
str += ' ';
str += getHttpStatusMessage(code);
str += HttpConst::getHttpStatusMessage(code);
str += "\r\n";
for (auto &pr : header) {
str += pr.first;
@ -590,9 +653,9 @@ void HttpSession::sendResponse(int code,
_ticker.resetTime();
if (!size) {
//没有body
// 没有body
if (bClose) {
shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << code));
shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http header completed with status code:" << code));
}
return;
}
@ -607,20 +670,20 @@ void HttpSession::sendResponse(int code,
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
if (body->remainSize() > sendBufSize) {
//文件下载提升发送性能
// 文件下载提升发送性能
setSocketFlags();
}
//发送http body
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(), body, bClose);
// 发送http body
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(static_pointer_cast<HttpSession>(shared_from_this()), body, bClose);
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
AsyncSender::onSocketFlushed(data);
}
string HttpSession::urlDecode(const string &str){
string HttpSession::urlDecode(const string &str) {
auto ret = strCoding::UrlDecode(str);
#ifdef _WIN32
GET_CONFIG(string,charSet,Http::kCharSet);
GET_CONFIG(string, charSet, Http::kCharSet);
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
if (isGb2312) {
ret = strCoding::UTF8ToGB2312(ret);
@ -629,117 +692,51 @@ string HttpSession::urlDecode(const string &str){
return ret;
}
void HttpSession::urlDecode(Parser &parser){
parser.setUrl(urlDecode(parser.Url()));
for(auto &pr : _parser.getUrlArgs()){
void HttpSession::urlDecode(Parser &parser) {
parser.setUrl(urlDecode(parser.url()));
for (auto &pr : _parser.getUrlArgs()) {
const_cast<string &>(pr.second) = urlDecode(pr.second);
}
}
bool HttpSession::emitHttpEvent(bool doInvoke){
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
bool HttpSession::emitHttpEvent(bool doInvoke) {
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
/////////////////////异步回复Invoker///////////////////////////////
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
HttpResponseInvoker invoker = [weak_self,bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body){
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
HttpResponseInvoker invoker = [weak_self, bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body) {
auto strong_self = weak_self.lock();
if(!strong_self) {
if (!strong_self) {
return;
}
strong_self->async([weak_self, bClose, code, headerOut, body]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
//本对象已经销毁
// 本对象已经销毁
return;
}
strong_self->sendResponse(code, bClose, nullptr, headerOut, body);
});
};
///////////////////广播HTTP事件///////////////////////////
bool consumed = false;//该事件是否被消费
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,static_cast<SockInfo &>(*this));
if(!consumed && doInvoke){
//该事件无人消费所以返回404
invoker(404,KeyValue(), HttpBody::Ptr());
bool consumed = false; // 该事件是否被消费
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
if (!consumed && doInvoke) {
// 该事件无人消费所以返回404
invoker(404, KeyValue(), HttpBody::Ptr());
}
return consumed;
}
std::string HttpSession::get_peer_ip() {
GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader);
if(!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()){
if (!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()) {
return _parser.getHeader()[forwarded_ip_header];
}
return Session::get_peer_ip();
}
void HttpSession::Handle_Req_POST(ssize_t &content_len) {
GET_CONFIG(size_t,maxReqSize,Http::kMaxReqSize);
ssize_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
if(totalContentLen == 0){
//content为空
//emitHttpEvent内部会选择是否关闭连接
emitHttpEvent(true);
return;
}
if(totalContentLen > 0 && (size_t)totalContentLen < maxReqSize ){
//返回固定长度的content
content_len = totalContentLen;
auto parserCopy = _parser;
_contentCallBack = [this,parserCopy](const char *data,size_t len){
//恢复http头
_parser = parserCopy;
//设置content
_parser.setContent(string(data,len));
//触发http事件emitHttpEvent内部会选择是否关闭连接
emitHttpEvent(true);
//清空数据,节省内存
_parser.Clear();
//content已经接收完毕
return false;
};
}else{
//返回不固定长度的content或者超过长度限制的content
content_len = -1;
auto parserCopy = _parser;
std::shared_ptr<size_t> recvedContentLen = std::make_shared<size_t>(0);
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,size_t len){
*(recvedContentLen) += len;
if (totalContentLen < 0) {
//不固定长度的content,源源不断接收数据
onRecvUnlimitedContent(parserCopy, data, len, SIZE_MAX, *(recvedContentLen));
return true;
}
//长度超过限制的content
onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen));
if(*(recvedContentLen) < (size_t)totalContentLen){
//数据还没接收完毕
//_contentCallBack是可持续的后面还要处理后续content数据
return true;
}
//数据接收完毕
if(!bClose){
//keep-alive类型连接
//content接收完毕后续都是http header
setContentLen(0);
//content已经接收完毕
return false;
}
//连接类型是close类型收完content就关闭连接
shutdown(SockException(Err_shutdown,"recv http content completed"));
//content已经接收完毕
return false ;
};
}
//有后续content数据要处理,暂时不关闭连接
void HttpSession::onHttpRequest_POST() {
emitHttpEvent(true);
}
void HttpSession::sendNotFound(bool bClose) {
@ -747,19 +744,19 @@ void HttpSession::sendNotFound(bool bClose) {
sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared<HttpStringBody>(notFound));
}
void HttpSession::setSocketFlags(){
void HttpSession::setSocketFlags() {
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
if(mergeWriteMS > 0) {
//推流模式下关闭TCP_NODELAY会增加推流端的延时但是服务器性能将提高
if (mergeWriteMS > 0) {
// 推流模式下关闭TCP_NODELAY会增加推流端的延时但是服务器性能将提高
SockUtil::setNoDelay(getSock()->rawFD(), false);
//播放模式下开启MSG_MORE会增加延时但是能提高发送性能
// 播放模式下开启MSG_MORE会增加延时但是能提高发送性能
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
}
}
void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
if(flush){
//需要flush那么一次刷新缓存
if (flush) {
// 需要flush那么一次刷新缓存
HttpSession::setSendFlushFlag(true);
}
@ -777,18 +774,18 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
}
if (flush) {
//本次刷新缓存后,下次不用刷新缓存
// 本次刷新缓存后,下次不用刷新缓存
HttpSession::setSendFlushFlag(false);
}
}
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer){
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer) {
_total_bytes_usage += buffer->size();
send(std::move(buffer));
}
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
WebSocketHeader& header = const_cast<WebSocketHeader&>(header_in);
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in) {
WebSocketHeader &header = const_cast<WebSocketHeader &>(header_in);
header._mask_flag = false;
switch (header._opcode) {
@ -798,15 +795,15 @@ void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
break;
}
default : break;
default: break;
}
}
void HttpSession::onDetach() {
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
shutdown(SockException(Err_shutdown, "rtmp ring buffer detached"));
}
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr(){
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr() {
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
}

View File

@ -28,15 +28,16 @@ class HttpSession: public toolkit::Session,
public HttpRequestSplitter,
public WebSocketSplitter {
public:
typedef StrCaseMap KeyValue;
typedef HttpResponseInvokerImp HttpResponseInvoker;
using Ptr = std::shared_ptr<HttpSession>;
using KeyValue = StrCaseMap;
using HttpResponseInvoker = HttpResponseInvokerImp ;
friend class AsyncSender;
/**
* @param errMsg
* @param accessPath 访
* @param cookieLifeSecond cookie有效期
**/
typedef std::function<void(const std::string &errMsg,const std::string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
using HttpAccessPathInvoker = std::function<void(const std::string &errMsg,const std::string &accessPath, int cookieLifeSecond)>;
HttpSession(const toolkit::Socket::Ptr &pSock);
~HttpSession() override;
@ -100,11 +101,10 @@ protected:
std::string get_peer_ip() override;
private:
void Handle_Req_GET(ssize_t &content_len);
void Handle_Req_GET_l(ssize_t &content_len, bool sendBody);
void Handle_Req_POST(ssize_t &content_len);
void Handle_Req_HEAD(ssize_t &content_len);
void Handle_Req_OPTIONS(ssize_t &content_len);
void onHttpRequest_GET();
void onHttpRequest_POST();
void onHttpRequest_HEAD();
void onHttpRequest_OPTIONS();
bool checkLiveStream(const std::string &schema, const std::string &url_suffix, const std::function<void(const MediaSource::Ptr &src)> &cb);
@ -123,19 +123,20 @@ private:
//设置socket标志
void setSocketFlags();
protected:
MediaInfo _mediaInfo;
private:
bool _is_live_stream = false;
bool _live_over_websocket = false;
//消耗的总流量
uint64_t _total_bytes_usage = 0;
std::string _origin;
Parser _parser;
toolkit::Ticker _ticker;
MediaInfo _mediaInfo;
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
//处理content数据的callback
std::function<bool (const char *data,size_t len) > _contentCallBack;
std::function<bool (const char *data,size_t len) > _on_recv_body;
};
using HttpsSession = toolkit::SessionWithSSL<HttpSession>;

Some files were not shown because too many files have changed in this diff Show More