mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-27 05:38:31 +08:00
sync from master
This commit is contained in:
commit
8f89c04b8c
@ -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})
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 7e40c751659d5c1ec623699732284c12e0a4feb8
|
||||
Subproject commit e4744a0a523817356f2ec995ee5a732264c31629
|
5
AUTHORS
5
AUTHORS
@ -70,4 +70,7 @@ 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)
|
@ -449,11 +449,6 @@ if(ENABLE_API)
|
||||
add_subdirectory(api)
|
||||
endif()
|
||||
|
||||
# IOS 不编译可执行程序
|
||||
if(IOS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
##############################################################################
|
||||
|
||||
if(ENABLE_PLAYER AND ENABLE_FFMPEG)
|
||||
@ -461,13 +456,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)
|
||||
set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
# IOS 不编译可执行程序
|
||||
if(IOS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
#cpp测试demo程序
|
||||
if (ENABLE_TESTS)
|
||||
add_subdirectory(tests)
|
||||
|
10
README.md
10
README.md
@ -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)
|
||||
@ -61,7 +63,8 @@
|
||||
- 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)
|
||||
@ -99,6 +102,7 @@
|
||||
- 支持es/ps/ts/ehome rtp推流
|
||||
- 支持es/ps rtp转推
|
||||
- 支持GB28181主动拉流模式
|
||||
- 支持双向语音对讲
|
||||
|
||||
- MP4点播与录制
|
||||
- 支持录制为FLV/HLS/MP4
|
||||
@ -119,6 +123,7 @@
|
||||
- 支持datachannel
|
||||
- 支持webrtc over tcp模式
|
||||
- 优秀的nack、jitter buffer算法, 抗丢包能力卓越
|
||||
- 支持whip/whep协议
|
||||
- [SRT支持](./srt/srt.md)
|
||||
- 其他
|
||||
- 支持丰富的restful api以及web hook事件
|
||||
@ -302,6 +307,9 @@ 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)
|
||||
|
||||
## 使用案例
|
||||
|
||||
|
442
README_en.md
442
README_en.md
@ -1,138 +1,205 @@
|
||||
![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 thread,extreme performance.
|
||||
- Well performance and stable test,can be used commercially.
|
||||
- Support linux, macos, ios, android, Windows Platforms.
|
||||
- Very low latency(lower then one second), video opened immediately.
|
||||
[![](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://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png">
|
||||
|
||||
- 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)
|
||||
|
||||
- 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 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++11,GCC4.8/Clang3.3/VC2015 or above.
|
||||
- cmake3.1 or above.
|
||||
- All Linux , both 32 and 64 bits
|
||||
- Apple OSX(Darwin), both 32 and 64bits.
|
||||
- All hardware with x86/x86_64/arm/mips cpu.
|
||||
- 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 MacOS,compiling 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 +207,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 +219,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 +236,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 +267,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 +280,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 +292,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 +310,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 +324,162 @@ 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 group:542509000
|
||||
## 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 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)
|
||||
|
||||
## 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)
|
||||
|
||||
## 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!
|
||||
|
@ -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()
|
||||
|
@ -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,10 @@ 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);
|
||||
/**
|
||||
* 直播源在ZLMediaKit中被称作为MediaSource,
|
||||
* 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource
|
||||
@ -132,6 +137,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);
|
||||
|
@ -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服务/////////////////////////////////////////////
|
||||
|
@ -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,22 @@ 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_close(const mk_media_source ctx,int force){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
@ -248,6 +264,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);
|
||||
|
@ -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){
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -143,7 +143,7 @@ public:
|
||||
}
|
||||
|
||||
EventPoller::Ptr getPoller() {
|
||||
return dynamic_pointer_cast<EventPoller>(getExecutor());
|
||||
return static_pointer_cast<EventPoller>(getExecutor());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "mk_mediakit.h"
|
||||
|
||||
typedef struct {
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "mk_mediakit.h"
|
||||
#define LOG_LEV 4
|
||||
|
||||
|
@ -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,7 +32,10 @@ restart_sec=0
|
||||
#转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项
|
||||
[protocol]
|
||||
#转协议时,是否开启帧级时间戳覆盖
|
||||
modify_stamp=0
|
||||
# 0:采用源视频流绝对时间戳,不做任何改变
|
||||
# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理)
|
||||
# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
|
||||
modify_stamp=2
|
||||
#转协议是否开启音频
|
||||
enable_audio=1
|
||||
#添加acc静音音频,在关闭音频时,此开关无效
|
||||
@ -165,6 +168,8 @@ on_stream_none_reader=https://127.0.0.1/index/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 +235,8 @@ forbidCacheSuffix=
|
||||
#可以把http代理前真实客户端ip放在http头中:https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
|
||||
#切勿暴露此key,否则可能导致伪造客户端ip
|
||||
forwarded_ip_header=
|
||||
#默认允许所有跨域请求
|
||||
allow_cross_domains=1
|
||||
|
||||
[multicast]
|
||||
#rtp组播截止组播ip地址
|
||||
@ -276,6 +283,9 @@ videoMtuSize=1400
|
||||
rtpMaxSize=10
|
||||
# rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||
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 +367,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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
@ -582,7 +582,7 @@
|
||||
{
|
||||
"key": "modify_stamp",
|
||||
"value": null,
|
||||
"description": "是否重新计算时间戳",
|
||||
"description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
@ -627,7 +627,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}}"
|
||||
],
|
||||
@ -664,7 +664,7 @@
|
||||
},
|
||||
{
|
||||
"key": "dst_url",
|
||||
"value": "rtmp://127.0.0.1/live/push",
|
||||
"value": "rtmp://192.168.1.104/live/push",
|
||||
"description": "推流地址,需要与schema字段协议一致"
|
||||
},
|
||||
{
|
||||
@ -696,7 +696,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}}"
|
||||
],
|
||||
@ -713,7 +713,7 @@
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "__defaultVhost__/live/test",
|
||||
"value": "rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491",
|
||||
"description": "addStreamPusherProxy接口返回的key"
|
||||
}
|
||||
]
|
||||
@ -1363,7 +1363,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}}"
|
||||
],
|
||||
@ -1422,7 +1422,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}}"
|
||||
],
|
||||
@ -1874,6 +1874,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": [
|
||||
|
@ -38,7 +38,13 @@ if(ENABLE_SERVER_LIB)
|
||||
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
|
||||
|
@ -39,7 +39,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;
|
||||
});
|
||||
}
|
||||
@ -102,9 +102,9 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
|
||||
_process.run(cmd, log_file);
|
||||
InfoL << cmd;
|
||||
|
||||
if (is_local_ip(_media_info._host)) {
|
||||
if (is_local_ip(_media_info.host)) {
|
||||
//推流给自己的,通过判断流是否注册上来判断是否正常
|
||||
if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){
|
||||
if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) {
|
||||
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
|
||||
return;
|
||||
}
|
||||
@ -154,10 +154,10 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
|
||||
}
|
||||
|
||||
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);
|
||||
auto src = MediaSource::find(_media_info.schema,
|
||||
_media_info.vhost,
|
||||
_media_info.app,
|
||||
_media_info.stream);
|
||||
if(src || !maxWaitMS){
|
||||
cb(src);
|
||||
return;
|
||||
@ -183,10 +183,8 @@ void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSourc
|
||||
}
|
||||
|
||||
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) {
|
||||
sender.getSchema() != strongSelf->_media_info.schema ||
|
||||
!equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) {
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
return;
|
||||
}
|
||||
@ -223,7 +221,7 @@ 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)) {
|
||||
if (is_local_ip(strongSelf->_media_info.host)) {
|
||||
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
||||
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
||||
//同步查找流
|
||||
|
@ -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;
|
||||
}
|
||||
@ -248,14 +248,14 @@ static inline void addHttpListener(){
|
||||
}
|
||||
|
||||
LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__);
|
||||
log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n";
|
||||
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();
|
||||
@ -1420,12 +1424,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 +1489,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 +1567,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 +1641,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 +1676,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 +1701,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)
|
||||
|
@ -231,9 +231,12 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) {
|
||||
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,
|
||||
|
@ -44,6 +44,7 @@ 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";
|
||||
@ -69,6 +70,7 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kOnStreamNoneReader] = "";
|
||||
mINI::Instance()[kOnHttpAccess] = "";
|
||||
mINI::Instance()[kOnServerStarted] = "";
|
||||
mINI::Instance()[kOnServerExited] = "";
|
||||
mINI::Instance()[kOnServerKeepalive] = "";
|
||||
mINI::Instance()[kOnSendRtpStopped] = "";
|
||||
mINI::Instance()[kOnRtpServerTimeout] = "";
|
||||
@ -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;
|
||||
@ -213,13 +215,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 +240,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 +277,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 +294,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;
|
||||
}
|
||||
@ -321,7 +335,7 @@ void installWebHook() {
|
||||
|
||||
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 || args.param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
invoker("", ProtocolOption());
|
||||
return;
|
||||
}
|
||||
@ -346,7 +360,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 || args.param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
invoker("");
|
||||
return;
|
||||
}
|
||||
@ -360,7 +374,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 || args.param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
return;
|
||||
}
|
||||
auto body = make_json(args);
|
||||
@ -379,7 +393,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 || args.param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
// 无需认证
|
||||
invoker("");
|
||||
return;
|
||||
@ -437,9 +451,7 @@ void installWebHook() {
|
||||
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
|
||||
@ -467,7 +479,7 @@ void installWebHook() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start_with(args._param_strs, kEdgeServerParam)) {
|
||||
if (start_with(args.param_strs, kEdgeServerParam)) {
|
||||
// 源站收到来自边沿站的溯源请求,流不存在时立即返回拉流失败
|
||||
closePlayer();
|
||||
return;
|
||||
@ -503,9 +515,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;
|
||||
};
|
||||
|
||||
@ -561,9 +571,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) {
|
||||
@ -584,9 +592,7 @@ void installWebHook() {
|
||||
}
|
||||
|
||||
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,7 +620,7 @@ 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) {
|
||||
if (sender.get_peer_ip() == "127.0.0.1" || parser.params() == hook_adminparams) {
|
||||
// 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时
|
||||
invoker("", "", 60 * 60);
|
||||
return;
|
||||
@ -632,7 +638,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;
|
||||
}
|
||||
@ -676,3 +682,7 @@ void unInstallWebHook() {
|
||||
g_keepalive_timer.reset();
|
||||
NoticeCenter::Instance().delListener(&web_hook_tag);
|
||||
}
|
||||
|
||||
void onProcessExited() {
|
||||
reportServerExited();
|
||||
}
|
@ -31,6 +31,7 @@ extern const std::string kTimeoutSec;
|
||||
|
||||
void installWebHook();
|
||||
void unInstallWebHook();
|
||||
void onProcessExited();
|
||||
/**
|
||||
* 触发http hook请求
|
||||
* @param url 请求地址
|
||||
|
@ -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)
|
||||
|
||||
@ -363,8 +388,8 @@ int start_main(int argc,char *argv[]) {
|
||||
#endif//defined(ENABLE_SRT)
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "端口占用或无权限:" << ex.what() << endl;
|
||||
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl;
|
||||
WarnL << "端口占用或无权限:" << ex.what();
|
||||
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!";
|
||||
sleep(1);
|
||||
#if !defined(_WIN32)
|
||||
if (pid != getpid() && kill_parent_if_failed) {
|
||||
@ -384,9 +409,9 @@ 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();
|
||||
});// 设置退出信号
|
||||
}); // 设置退出信号
|
||||
|
||||
#if !defined(_WIN32)
|
||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
|
||||
@ -395,6 +420,8 @@ int start_main(int argc,char *argv[]) {
|
||||
}
|
||||
unInstallWebApi();
|
||||
unInstallWebHook();
|
||||
onProcessExited();
|
||||
|
||||
//休眠1秒再退出,防止资源释放顺序错误
|
||||
InfoL << "程序退出中,请等待...";
|
||||
sleep(1);
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -53,7 +53,7 @@ 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(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
|
||||
@ -105,7 +105,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 +114,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() {
|
||||
@ -424,7 +407,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 +442,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;
|
||||
}
|
||||
@ -527,7 +508,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 +551,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 +560,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 +600,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 +647,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]() {
|
||||
|
@ -136,8 +136,14 @@ class ProtocolOption {
|
||||
public:
|
||||
ProtocolOption();
|
||||
|
||||
//时间戳修复这一路流标志位
|
||||
bool modify_stamp;
|
||||
enum {
|
||||
kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变
|
||||
kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理)
|
||||
kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
|
||||
};
|
||||
// 时间戳类型
|
||||
int modify_stamp;
|
||||
|
||||
//转协议是否开启音频
|
||||
bool enable_audio;
|
||||
//添加静音音频,在关闭音频时,此开关无效
|
||||
@ -252,26 +258,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 +284,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();
|
||||
@ -369,7 +371,7 @@ public:
|
||||
// 同步查找流
|
||||
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 +396,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;
|
||||
|
@ -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,7 +33,7 @@ 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);
|
||||
}
|
||||
@ -70,52 +70,42 @@ static string getTrackInfoStr(const TrackSource *track_src){
|
||||
return std::move(codec_info);
|
||||
}
|
||||
|
||||
const std::string &MultiMediaSourceMuxer::getVhost() const {
|
||||
return _vhost;
|
||||
}
|
||||
|
||||
const std::string &MultiMediaSourceMuxer::getApp() const {
|
||||
return _app;
|
||||
}
|
||||
|
||||
const std::string &MultiMediaSourceMuxer::getStreamId() const {
|
||||
return _stream_id;
|
||||
}
|
||||
|
||||
std::string MultiMediaSourceMuxer::shortUrl() const {
|
||||
auto ret = getOriginUrl(MediaSource::NullMediaSource());
|
||||
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) {
|
||||
_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_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 = std::make_shared<TSMediaSourceMuxer>(_tuple, option);
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (option.enable_fmp4) {
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream, option);
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(_tuple, option);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -252,10 +242,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) {
|
||||
@ -277,7 +268,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE
|
||||
}
|
||||
});
|
||||
|
||||
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 +310,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;
|
||||
@ -330,7 +321,6 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
|
||||
|
||||
bool ret = false;
|
||||
if (_rtmp) {
|
||||
ret = _rtmp->addTrack(track) ? true : ret;
|
||||
@ -385,6 +375,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);
|
||||
}
|
||||
|
||||
@ -436,9 +431,9 @@ void MultiMediaSourceMuxer::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;
|
||||
@ -474,7 +469,9 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
||||
// 视频时,遇到第一帧配置帧或关键帧则标记为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());
|
||||
|
@ -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,9 @@ public:
|
||||
*/
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
|
||||
const std::string& getVhost() const;
|
||||
const std::string& getApp() const;
|
||||
const std::string& getStreamId() const;
|
||||
const MediaTuple& getMediaTuple() const {
|
||||
return _tuple;
|
||||
}
|
||||
std::string shortUrl() const;
|
||||
|
||||
protected:
|
||||
@ -164,9 +164,7 @@ 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];
|
||||
|
@ -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,142 @@ 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;
|
||||
}
|
||||
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;
|
||||
auto next_line = strstr(ptr, "\r\n");
|
||||
CHECK(next_line);
|
||||
if (ptr == buf) {
|
||||
auto blank = strchr(ptr, ' ');
|
||||
CHECK(blank > ptr && blank < next_line);
|
||||
_method = std::string(ptr, blank);
|
||||
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, pos };
|
||||
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 + 2;
|
||||
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 +253,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 +310,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;
|
||||
@ -312,4 +333,4 @@ static onceToken token([](){
|
||||
});
|
||||
#endif
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
@ -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 ¶ms() 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
|
||||
|
@ -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_CTS ? 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
|
||||
|
@ -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
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "MediaSource.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
@ -120,7 +121,7 @@ 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;
|
||||
@ -159,6 +160,7 @@ 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";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
||||
@ -186,6 +188,7 @@ static onceToken token([]() {
|
||||
<< endl;
|
||||
mINI::Instance()[kForbidCacheSuffix] = "";
|
||||
mINI::Instance()[kForwardedIpHeader] = "";
|
||||
mINI::Instance()[kAllowCrossDomains] = 1;
|
||||
});
|
||||
|
||||
} // namespace Http
|
||||
@ -206,6 +209,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,6 +218,7 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
mINI::Instance()[kDirectProxy] = 1;
|
||||
mINI::Instance()[kLowLatency] = 0;
|
||||
mINI::Instance()[kRtpTransportType] = -1;
|
||||
});
|
||||
} // namespace Rtsp
|
||||
|
||||
@ -239,15 +244,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
|
||||
|
||||
|
@ -246,6 +246,8 @@ 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;
|
||||
} // namespace Http
|
||||
|
||||
////////////SHELL配置///////////
|
||||
@ -271,6 +273,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 +298,8 @@ extern const std::string kAudioMtuSize;
|
||||
extern const std::string kRtpMaxSize;
|
||||
// rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||
extern const std::string kLowLatency;
|
||||
//H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
|
||||
extern const std::string kH264StapA;
|
||||
} // namespace Rtp
|
||||
|
||||
////////////组播配置///////////
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
namespace mediakit {
|
||||
|
||||
class strCoding {
|
@ -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丢包了,那么整帧应该废弃
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
@ -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) {
|
||||
|
@ -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; }
|
||||
|
@ -209,8 +209,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 +220,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 +257,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 +279,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])) {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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(); }
|
||||
|
||||
@ -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) {
|
||||
|
@ -23,12 +23,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(); };
|
||||
|
@ -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);
|
||||
|
@ -80,7 +80,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) {
|
||||
@ -186,7 +186,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 +259,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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -158,9 +158,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;
|
||||
|
@ -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;
|
||||
@ -46,7 +46,7 @@ struct HttpCookieAttachment {
|
||||
};
|
||||
|
||||
const string &HttpFileManager::getContentType(const char *name) {
|
||||
return getHttpContentType(name);
|
||||
return HttpConst::getHttpContentType(name);
|
||||
}
|
||||
|
||||
static string searchIndexFile(const string &dir){
|
||||
@ -240,8 +240,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 +268,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 +278,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;
|
||||
|
||||
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
|
||||
info->_identifier = sender.getIdentifier();
|
||||
@ -363,11 +363,11 @@ 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, "");
|
||||
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
|
||||
}
|
||||
|
||||
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();
|
||||
@ -465,15 +465,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,7 +481,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
||||
ch = '/';
|
||||
}
|
||||
}
|
||||
auto ret = File::absolutePath(enableVhost ? media_info._vhost + url : url, path);
|
||||
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
|
||||
return ret;
|
||||
}
|
||||
@ -493,7 +493,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) {
|
||||
@ -506,13 +506,13 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
|
||||
if (!indexFile.empty()) {
|
||||
//发现该文件夹下有index文件
|
||||
file_path = pathCat(file_path, indexFile);
|
||||
parser.setUrl(pathCat(parser.Url(), indexFile));
|
||||
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 +600,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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
// 声明后续都是body;Http 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;
|
||||
};
|
||||
|
||||
// 声明后续都是body;Http 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));
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, static_cast<SockInfo &>(*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);
|
||||
}
|
||||
});
|
||||
@ -244,36 +313,36 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
|
||||
|
||||
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast<SockInfo &>(*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->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 +350,80 @@ 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->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 +435,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 +496,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 +516,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 +526,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 +545,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 +560,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 +586,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 +645,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 +662,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 +684,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; // 该事件是否被消费
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, static_cast<SockInfo &>(*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 +736,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 +766,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 +787,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());
|
||||
}
|
||||
|
||||
|
@ -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>;
|
||||
|
@ -34,7 +34,12 @@ void TsPlayer::teardown() {
|
||||
void TsPlayer::onResponseCompleted(const SockException &ex) {
|
||||
if (!_play_result) {
|
||||
_play_result = true;
|
||||
onPlayResult(ex);
|
||||
if (!ex && responseBodyTotalSize() == 0 && responseBodySize() == 0) {
|
||||
//if the server does not return any data, it is considered a failure
|
||||
onShutdown(ex);
|
||||
} else {
|
||||
onPlayResult(ex);
|
||||
}
|
||||
} else {
|
||||
onShutdown(ex);
|
||||
}
|
||||
@ -44,7 +49,7 @@ void TsPlayer::onResponseCompleted(const SockException &ex) {
|
||||
void TsPlayer::onResponseBody(const char *buf, size_t size) {
|
||||
if (!_play_result) {
|
||||
_play_result = true;
|
||||
onPlayResult(SockException(Err_success, "play http-ts success"));
|
||||
onPlayResult(SockException(Err_success, "read http-ts stream successfully"));
|
||||
}
|
||||
if (!_benchmark_mode) {
|
||||
HttpTSPlayer::onResponseBody(buf, size);
|
||||
|
@ -218,7 +218,7 @@ protected:
|
||||
/**
|
||||
* tcp连接断开
|
||||
*/
|
||||
void onErr(const toolkit::SockException &ex) override {
|
||||
void onError(const toolkit::SockException &ex) override {
|
||||
// tcp断开或者shutdown导致的断开
|
||||
onWebSocketException(ex);
|
||||
}
|
||||
@ -316,7 +316,7 @@ private:
|
||||
if (!ex) {
|
||||
// websocket握手成功
|
||||
// 此处截取TcpClient派生类发送的数据并进行websocket协议打包
|
||||
std::weak_ptr<HttpWsClient> weakSelf = std::dynamic_pointer_cast<HttpWsClient>(shared_from_this());
|
||||
std::weak_ptr<HttpWsClient> weakSelf = std::static_pointer_cast<HttpWsClient>(shared_from_this());
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->setOnBeforeSendCB([weakSelf](const toolkit::Buffer::Ptr &buf) {
|
||||
auto strong_self = weakSelf.lock();
|
||||
@ -350,7 +350,7 @@ private:
|
||||
// 握手成功之后的中途断开
|
||||
_onRecv = nullptr;
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onErr(ex);
|
||||
strong_ref->onError(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ protected:
|
||||
}
|
||||
|
||||
//此处截取数据并进行websocket协议打包
|
||||
std::weak_ptr<WebSocketSessionBase> weakSelf = std::dynamic_pointer_cast<WebSocketSessionBase>(HttpSessionType::shared_from_this());
|
||||
std::weak_ptr<WebSocketSessionBase> weakSelf = std::static_pointer_cast<WebSocketSessionBase>(HttpSessionType::shared_from_this());
|
||||
std::dynamic_pointer_cast<SendInterceptor>(_session)->setOnBeforeSendCB([weakSelf](const toolkit::Buffer::Ptr &buf) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (strongSelf) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "PlayerBase.h"
|
||||
#include "Rtsp/RtspPlayerImp.h"
|
||||
#include "Rtmp/RtmpPlayerImp.h"
|
||||
#include "Rtmp/FlvPlayer.h"
|
||||
#include "Http/HlsPlayer.h"
|
||||
#include "Http/TsPlayerImp.h"
|
||||
|
||||
@ -20,15 +21,16 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) {
|
||||
static auto releasePlayer = [](PlayerBase *ptr) {
|
||||
onceToken token(nullptr, [&]() {
|
||||
delete ptr;
|
||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) {
|
||||
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
|
||||
static auto releasePlayer = [poller](PlayerBase *ptr) {
|
||||
poller->async([ptr]() {
|
||||
onceToken token(nullptr, [&]() { delete ptr; });
|
||||
ptr->teardown();
|
||||
});
|
||||
ptr->teardown();
|
||||
};
|
||||
string url = url_in;
|
||||
string prefix = FindField(url.data(), NULL, "://");
|
||||
string prefix = findSubString(url.data(), NULL, "://");
|
||||
auto pos = url.find('?');
|
||||
if (pos != string::npos) {
|
||||
//去除?后面的字符串
|
||||
@ -53,9 +55,13 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const s
|
||||
if ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) {
|
||||
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
|
||||
return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer);
|
||||
} else if (end_with(url, ".ts") || end_with(url_in, ".ts")) {
|
||||
}
|
||||
if (end_with(url, ".ts") || end_with(url_in, ".ts")) {
|
||||
return PlayerBase::Ptr(new TsPlayerImp(poller), releasePlayer);
|
||||
}
|
||||
if (end_with(url, ".flv") || end_with(url_in, ".flv")) {
|
||||
return PlayerBase::Ptr(new FlvPlayerImp(poller), releasePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::invalid_argument("not supported play schema:" + url_in);
|
||||
|
@ -8,43 +8,97 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "PlayerProxy.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Util/MD5.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Common/config.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Rtmp/RtmpMediaSource.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
#include "Rtmp/RtmpPlayer.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
#include "Rtsp/RtspPlayer.h"
|
||||
#include "Util/MD5.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/mini.h"
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
PlayerProxy::PlayerProxy(const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option,
|
||||
int retry_count, const EventPoller::Ptr &poller) : MediaPlayer(poller) , _option(option) {
|
||||
_vhost = vhost;
|
||||
_app = app;
|
||||
_stream_id = stream_id;
|
||||
PlayerProxy::PlayerProxy(
|
||||
const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option, int retry_count,
|
||||
const EventPoller::Ptr &poller, int reconnect_delay_min, int reconnect_delay_max, int reconnect_delay_step)
|
||||
: MediaPlayer(poller)
|
||||
, _option(option) {
|
||||
_tuple.vhost = vhost;
|
||||
_tuple.app = app;
|
||||
_tuple.stream = stream_id;
|
||||
_retry_count = retry_count;
|
||||
_on_close = [](const SockException &) {};
|
||||
|
||||
setOnClose(nullptr);
|
||||
setOnConnect(nullptr);
|
||||
setOnDisconnect(nullptr);
|
||||
|
||||
_reconnect_delay_min = reconnect_delay_min > 0 ? reconnect_delay_min : 2;
|
||||
_reconnect_delay_max = reconnect_delay_max > 0 ? reconnect_delay_max : 60;
|
||||
_reconnect_delay_step = reconnect_delay_step > 0 ? reconnect_delay_step : 3;
|
||||
_live_secs = 0;
|
||||
_live_status = 1;
|
||||
_repull_count = 0;
|
||||
(*this)[Client::kWaitTrackReady] = false;
|
||||
}
|
||||
|
||||
void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb) {
|
||||
_on_play = cb;
|
||||
void PlayerProxy::setPlayCallbackOnce(function<void(const SockException &ex)> cb) {
|
||||
_on_play = std::move(cb);
|
||||
}
|
||||
|
||||
void PlayerProxy::setOnClose(const function<void(const SockException &ex)> &cb) {
|
||||
_on_close = cb ? cb : [](const SockException &) {};
|
||||
void PlayerProxy::setOnClose(function<void(const SockException &ex)> cb) {
|
||||
_on_close = cb ? std::move(cb) : [](const SockException &) {};
|
||||
}
|
||||
|
||||
void PlayerProxy::setOnDisconnect(std::function<void()> cb) {
|
||||
_on_disconnect = cb ? std::move(cb) : [] () {};
|
||||
}
|
||||
|
||||
void PlayerProxy::setOnConnect(std::function<void(const TranslationInfo&)> cb) {
|
||||
_on_connect = cb ? std::move(cb) : [](const TranslationInfo&) {};
|
||||
}
|
||||
|
||||
void PlayerProxy::setTranslationInfo()
|
||||
{
|
||||
_transtalion_info.byte_speed = _media_src ? _media_src->getBytesSpeed() : -1;
|
||||
_transtalion_info.start_time_stamp = _media_src ? _media_src->getCreateStamp() : 0;
|
||||
_transtalion_info.stream_info.clear();
|
||||
auto tracks = _muxer->getTracks();
|
||||
for (auto &track : tracks) {
|
||||
_transtalion_info.stream_info.emplace_back();
|
||||
auto &back = _transtalion_info.stream_info.back();
|
||||
back.bitrate = track->getBitRate();
|
||||
back.codec_type = track->getTrackType();
|
||||
back.codec_name = track->getCodecName();
|
||||
switch (back.codec_type) {
|
||||
case TrackAudio : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
back.audio_sample_rate = audio_track->getAudioSampleRate();
|
||||
back.audio_channel = audio_track->getAudioChannel();
|
||||
back.audio_sample_bit = audio_track->getAudioSampleBit();
|
||||
break;
|
||||
}
|
||||
case TrackVideo : {
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
|
||||
back.video_width = video_track->getVideoWidth();
|
||||
back.video_height = video_track->getVideoHeight();
|
||||
back.video_fps = video_track->getVideoFps();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerProxy::play(const string &strUrlTmp) {
|
||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
||||
std::shared_ptr<int> piFailedCnt(new int(0)); //连续播放失败次数
|
||||
std::shared_ptr<int> piFailedCnt(new int(0)); // 连续播放失败次数
|
||||
setOnPlayResult([weakSelf, strUrlTmp, piFailedCnt](const SockException &err) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
@ -58,15 +112,22 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
|
||||
if (!err) {
|
||||
// 取消定时器,避免hls拉流索引文件因为网络波动失败重连成功后出现循环重试的情况
|
||||
strongSelf->_timer.reset();
|
||||
strongSelf->_timer.reset();
|
||||
strongSelf->_live_ticker.resetTime();
|
||||
strongSelf->_live_status = 0;
|
||||
// 播放成功
|
||||
*piFailedCnt = 0;//连续播放失败次数清0
|
||||
*piFailedCnt = 0; // 连续播放失败次数清0
|
||||
strongSelf->onPlaySuccess();
|
||||
strongSelf->setTranslationInfo();
|
||||
strongSelf->_on_connect(strongSelf->_transtalion_info);
|
||||
|
||||
InfoL << "play " << strUrlTmp << " success";
|
||||
} else if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||
// 播放失败,延时重试播放
|
||||
strongSelf->_on_disconnect();
|
||||
strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++);
|
||||
} else {
|
||||
//达到了最大重试次数,回调关闭
|
||||
// 达到了最大重试次数,回调关闭
|
||||
strongSelf->_on_close(err);
|
||||
}
|
||||
});
|
||||
@ -76,7 +137,7 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
return;
|
||||
}
|
||||
|
||||
//注销直接拉流代理产生的流:#532
|
||||
// 注销直接拉流代理产生的流:#532
|
||||
strongSelf->setMediaSource(nullptr);
|
||||
|
||||
if (strongSelf->_muxer) {
|
||||
@ -92,11 +153,20 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
strongSelf->_muxer->resetTracks();
|
||||
}
|
||||
}
|
||||
//播放异常中断,延时重试播放
|
||||
|
||||
if (*piFailedCnt == 0) {
|
||||
// 第一次重拉更新时长
|
||||
strongSelf->_live_secs += strongSelf->_live_ticker.elapsedTime() / 1000;
|
||||
strongSelf->_live_ticker.resetTime();
|
||||
TraceL << " live secs " << strongSelf->_live_secs;
|
||||
}
|
||||
|
||||
// 播放异常中断,延时重试播放
|
||||
if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||
strongSelf->_repull_count++;
|
||||
strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++);
|
||||
} else {
|
||||
//达到了最大重试次数,回调关闭
|
||||
// 达到了最大重试次数,回调关闭
|
||||
strongSelf->_on_close(err);
|
||||
}
|
||||
});
|
||||
@ -104,7 +174,7 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
MediaPlayer::play(strUrlTmp);
|
||||
} catch (std::exception &ex) {
|
||||
ErrorL << ex.what();
|
||||
_on_play_result(SockException(Err_other, ex.what()));
|
||||
onPlayResult(SockException(Err_other, ex.what()));
|
||||
return;
|
||||
}
|
||||
_pull_url = strUrlTmp;
|
||||
@ -114,14 +184,14 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
void PlayerProxy::setDirectProxy() {
|
||||
MediaSource::Ptr mediaSource;
|
||||
if (dynamic_pointer_cast<RtspPlayer>(_delegate)) {
|
||||
//rtsp拉流
|
||||
// rtsp拉流
|
||||
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
|
||||
if (directProxy) {
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_vhost, _app, _stream_id);
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_tuple);
|
||||
}
|
||||
} else if (dynamic_pointer_cast<RtmpPlayer>(_delegate)) {
|
||||
//rtmp拉流,rtmp强制直接代理
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_vhost, _app, _stream_id);
|
||||
// rtmp拉流,rtmp强制直接代理
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_tuple);
|
||||
}
|
||||
if (mediaSource) {
|
||||
setMediaSource(mediaSource);
|
||||
@ -131,30 +201,37 @@ void PlayerProxy::setDirectProxy() {
|
||||
PlayerProxy::~PlayerProxy() {
|
||||
_timer.reset();
|
||||
// 避免析构时, 忘记回调api请求
|
||||
if(_on_play) {
|
||||
_on_play(SockException(Err_shutdown, "player proxy close"));
|
||||
if (_on_play) {
|
||||
try {
|
||||
_on_play(SockException(Err_shutdown, "player proxy close"));
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "Exception occurred: " << ex.what();
|
||||
}
|
||||
_on_play = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerProxy::rePlay(const string &strUrl, int iFailedCnt) {
|
||||
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60 * 1000));
|
||||
auto iDelay = MAX(_reconnect_delay_min * 1000, MIN(iFailedCnt * _reconnect_delay_step * 1000, _reconnect_delay_max * 1000));
|
||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(iDelay / 1000.0f, [weakSelf, strUrl, iFailedCnt]() {
|
||||
//播放失败次数越多,则延时越长
|
||||
auto strongPlayer = weakSelf.lock();
|
||||
if (!strongPlayer) {
|
||||
_timer = std::make_shared<Timer>(
|
||||
iDelay / 1000.0f,
|
||||
[weakSelf, strUrl, iFailedCnt]() {
|
||||
// 播放失败次数越多,则延时越长
|
||||
auto strongPlayer = weakSelf.lock();
|
||||
if (!strongPlayer) {
|
||||
return false;
|
||||
}
|
||||
WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl;
|
||||
strongPlayer->MediaPlayer::play(strUrl);
|
||||
strongPlayer->setDirectProxy();
|
||||
return false;
|
||||
}
|
||||
WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl;
|
||||
strongPlayer->MediaPlayer::play(strUrl);
|
||||
strongPlayer->setDirectProxy();
|
||||
return false;
|
||||
}, getPoller());
|
||||
},
|
||||
getPoller());
|
||||
}
|
||||
|
||||
bool PlayerProxy::close(MediaSource &sender) {
|
||||
//通知其停止推流
|
||||
// 通知其停止推流
|
||||
weak_ptr<PlayerProxy> weakSelf = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||
getPoller()->async_first([weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
@ -194,51 +271,69 @@ float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) {
|
||||
return getPacketLossRate(type);
|
||||
}
|
||||
|
||||
TranslationInfo PlayerProxy::getTranslationInfo() {
|
||||
return _transtalion_info;
|
||||
}
|
||||
|
||||
void PlayerProxy::onPlaySuccess() {
|
||||
GET_CONFIG(bool, reset_when_replay, General::kResetWhenRePlay);
|
||||
if (dynamic_pointer_cast<RtspMediaSource>(_media_src)) {
|
||||
//rtsp拉流代理
|
||||
// rtsp拉流代理
|
||||
if (reset_when_replay || !_muxer) {
|
||||
_option.enable_rtsp = false;
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_vhost, _app, _stream_id, getDuration(), _option);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
|
||||
}
|
||||
} else if (dynamic_pointer_cast<RtmpMediaSource>(_media_src)) {
|
||||
//rtmp拉流代理
|
||||
// rtmp拉流代理
|
||||
if (reset_when_replay || !_muxer) {
|
||||
_option.enable_rtmp = false;
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_vhost, _app, _stream_id, getDuration(), _option);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
|
||||
}
|
||||
} else {
|
||||
//其他拉流代理
|
||||
// 其他拉流代理
|
||||
if (reset_when_replay || !_muxer) {
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_vhost, _app, _stream_id, getDuration(), _option);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
|
||||
}
|
||||
}
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
|
||||
auto videoTrack = getTrack(TrackVideo, false);
|
||||
if (videoTrack) {
|
||||
//添加视频
|
||||
// 添加视频
|
||||
_muxer->addTrack(videoTrack);
|
||||
//视频数据写入_mediaMuxer
|
||||
// 视频数据写入_mediaMuxer
|
||||
videoTrack->addDelegate(_muxer);
|
||||
}
|
||||
|
||||
auto audioTrack = getTrack(TrackAudio, false);
|
||||
if (audioTrack) {
|
||||
//添加音频
|
||||
// 添加音频
|
||||
_muxer->addTrack(audioTrack);
|
||||
//音频数据写入_mediaMuxer
|
||||
// 音频数据写入_mediaMuxer
|
||||
audioTrack->addDelegate(_muxer);
|
||||
}
|
||||
|
||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||
// 添加完毕所有track,防止单track情况下最大等待3秒
|
||||
_muxer->addTrackCompleted();
|
||||
|
||||
if (_media_src) {
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
// 让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
_media_src->setListener(_muxer);
|
||||
}
|
||||
}
|
||||
|
||||
int PlayerProxy::getStatus() {
|
||||
return _live_status.load();
|
||||
}
|
||||
uint64_t PlayerProxy::getLiveSecs() {
|
||||
if (_live_status == 0) {
|
||||
return _live_secs + _live_ticker.elapsedTime() / 1000;
|
||||
}
|
||||
return _live_secs;
|
||||
}
|
||||
|
||||
uint64_t PlayerProxy::getRePullCount() {
|
||||
return _repull_count;
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -11,21 +11,64 @@
|
||||
#ifndef SRC_DEVICE_PLAYERPROXY_H_
|
||||
#define SRC_DEVICE_PLAYERPROXY_H_
|
||||
|
||||
#include <memory>
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
#include "Player/MediaPlayer.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include <memory>
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PlayerProxy : public MediaPlayer, public MediaSourceEvent, public std::enable_shared_from_this<PlayerProxy> {
|
||||
struct StreamInfo
|
||||
{
|
||||
TrackType codec_type;
|
||||
std::string codec_name;
|
||||
int bitrate;
|
||||
int audio_sample_rate;
|
||||
int audio_sample_bit;
|
||||
int audio_channel;
|
||||
int video_width;
|
||||
int video_height;
|
||||
float video_fps;
|
||||
|
||||
StreamInfo()
|
||||
{
|
||||
codec_type = TrackInvalid;
|
||||
codec_name = "none";
|
||||
bitrate = -1;
|
||||
audio_sample_rate = -1;
|
||||
audio_channel = -1;
|
||||
audio_sample_bit = -1;
|
||||
video_height = -1;
|
||||
video_width = -1;
|
||||
video_fps = -1.0;
|
||||
}
|
||||
};
|
||||
|
||||
struct TranslationInfo
|
||||
{
|
||||
std::vector<StreamInfo> stream_info;
|
||||
int byte_speed;
|
||||
uint64_t start_time_stamp;
|
||||
|
||||
TranslationInfo()
|
||||
{
|
||||
byte_speed = -1;
|
||||
start_time_stamp = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerProxy
|
||||
: public MediaPlayer
|
||||
, public MediaSourceEvent
|
||||
, public std::enable_shared_from_this<PlayerProxy> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<PlayerProxy>;
|
||||
|
||||
//如果retry_count<0,则一直重试播放;否则重试retry_count次数
|
||||
//默认一直重试
|
||||
PlayerProxy(const std::string &vhost, const std::string &app, const std::string &stream_id,
|
||||
const ProtocolOption &option, int retry_count = -1, const toolkit::EventPoller::Ptr &poller = nullptr);
|
||||
// 如果retry_count<0,则一直重试播放;否则重试retry_count次数
|
||||
// 默认一直重试
|
||||
PlayerProxy(
|
||||
const std::string &vhost, const std::string &app, const std::string &stream_id, const ProtocolOption &option, int retry_count = -1,
|
||||
const toolkit::EventPoller::Ptr &poller = nullptr, int reconnect_delay_min = 2, int reconnect_delay_max = 60, int reconnect_delay_step = 3);
|
||||
|
||||
~PlayerProxy() override;
|
||||
|
||||
@ -33,13 +76,25 @@ public:
|
||||
* 设置play结果回调,只触发一次;在play执行之前有效
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void setPlayCallbackOnce(const std::function<void(const toolkit::SockException &ex)> &cb);
|
||||
void setPlayCallbackOnce(std::function<void(const toolkit::SockException &ex)> cb);
|
||||
|
||||
/**
|
||||
* 设置主动关闭回调
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void setOnClose(const std::function<void(const toolkit::SockException &ex)> &cb);
|
||||
void setOnClose(std::function<void(const toolkit::SockException &ex)> cb);
|
||||
|
||||
/**
|
||||
* Set a callback for failed server connection
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void setOnDisconnect(std::function<void()> cb);
|
||||
|
||||
/**
|
||||
* Set a callback for a successful connection to the server
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void setOnConnect(std::function<void(const TranslationInfo&)> cb);
|
||||
|
||||
/**
|
||||
* 开始拉流播放
|
||||
@ -50,10 +105,17 @@ public:
|
||||
/**
|
||||
* 获取观看总人数
|
||||
*/
|
||||
int totalReaderCount() ;
|
||||
int totalReaderCount();
|
||||
|
||||
int getStatus();
|
||||
uint64_t getLiveSecs();
|
||||
uint64_t getRePullCount();
|
||||
|
||||
// Using this only makes sense after a successful connection to the server
|
||||
TranslationInfo getTranslationInfo();
|
||||
|
||||
private:
|
||||
//MediaSourceEvent override
|
||||
// MediaSourceEvent override
|
||||
bool close(MediaSource &sender) override;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
MediaOriginType getOriginType(MediaSource &sender) const override;
|
||||
@ -61,21 +123,33 @@ private:
|
||||
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
|
||||
float getLossRate(MediaSource &sender, TrackType type) override;
|
||||
|
||||
void rePlay(const std::string &strUrl,int iFailedCnt);
|
||||
void rePlay(const std::string &strUrl, int iFailedCnt);
|
||||
void onPlaySuccess();
|
||||
void setDirectProxy();
|
||||
void setTranslationInfo();
|
||||
|
||||
private:
|
||||
ProtocolOption _option;
|
||||
int _retry_count;
|
||||
std::string _vhost;
|
||||
std::string _app;
|
||||
std::string _stream_id;
|
||||
int _reconnect_delay_min;
|
||||
int _reconnect_delay_max;
|
||||
int _reconnect_delay_step;
|
||||
MediaTuple _tuple;
|
||||
std::string _pull_url;
|
||||
toolkit::Timer::Ptr _timer;
|
||||
std::function<void()> _on_disconnect;
|
||||
std::function<void(const TranslationInfo &info)> _on_connect;
|
||||
std::function<void(const toolkit::SockException &ex)> _on_close;
|
||||
std::function<void(const toolkit::SockException &ex)> _on_play;
|
||||
TranslationInfo _transtalion_info;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
|
||||
toolkit::Ticker _live_ticker;
|
||||
// 0 表示正常 1 表示正在尝试拉流
|
||||
std::atomic<int> _live_status;
|
||||
std::atomic<uint64_t> _live_secs;
|
||||
|
||||
std::atomic<uint64_t> _repull_count;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -31,8 +31,7 @@ MediaPusher::MediaPusher(const string &schema,
|
||||
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
|
||||
}
|
||||
|
||||
MediaPusher::~MediaPusher() {
|
||||
}
|
||||
MediaPusher::~MediaPusher() = default;
|
||||
|
||||
static void setOnCreateSocket_l(const std::shared_ptr<PusherBase> &delegate, const Socket::onCreateSocket &cb){
|
||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||
|
@ -26,7 +26,7 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &poller,
|
||||
});
|
||||
ptr->teardown();
|
||||
};
|
||||
std::string prefix = FindField(url.data(), NULL, "://");
|
||||
std::string prefix = findSubString(url.data(), NULL, "://");
|
||||
|
||||
if (strcasecmp("rtsps",prefix.data()) == 0) {
|
||||
return PusherBase::Ptr(new TcpClientWithSSL<RtspPusherImp>(poller, std::dynamic_pointer_cast<RtspMediaSource>(src)), releasePusher);
|
||||
|
@ -16,10 +16,13 @@ using namespace std;
|
||||
namespace mediakit {
|
||||
|
||||
PusherProxy::PusherProxy(const MediaSource::Ptr &src, int retry_count, const EventPoller::Ptr &poller)
|
||||
: MediaPusher(src, poller){
|
||||
: MediaPusher(src, poller) {
|
||||
_retry_count = retry_count;
|
||||
_on_close = [](const SockException &) {};
|
||||
_weak_src = src;
|
||||
_live_secs = 0;
|
||||
_live_status = 1;
|
||||
_republish_count = 0;
|
||||
}
|
||||
|
||||
PusherProxy::~PusherProxy() {
|
||||
@ -52,13 +55,17 @@ void PusherProxy::publish(const string &dst_url) {
|
||||
auto src = strong_self->_weak_src.lock();
|
||||
if (!err) {
|
||||
// 推流成功
|
||||
strong_self->_live_ticker.resetTime();
|
||||
strong_self->_live_status = 0;
|
||||
*failed_cnt = 0;
|
||||
InfoL << "Publish " << dst_url << " success";
|
||||
} else if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) {
|
||||
// 推流失败,延时重试推送
|
||||
strong_self->_republish_count++;
|
||||
strong_self->_live_status = 1;
|
||||
strong_self->rePublish(dst_url, (*failed_cnt)++);
|
||||
} else {
|
||||
//如果媒体源已经注销, 或达到了最大重试次数,回调关闭
|
||||
// 如果媒体源已经注销, 或达到了最大重试次数,回调关闭
|
||||
strong_self->_on_close(err);
|
||||
}
|
||||
});
|
||||
@ -69,12 +76,20 @@ void PusherProxy::publish(const string &dst_url) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*failed_cnt == 0) {
|
||||
// 第一次重推更新时长
|
||||
strong_self->_live_secs += strong_self->_live_ticker.elapsedTime() / 1000;
|
||||
strong_self->_live_ticker.resetTime();
|
||||
TraceL << " live secs " << strong_self->_live_secs;
|
||||
}
|
||||
|
||||
auto src = strong_self->_weak_src.lock();
|
||||
//推流异常中断,延时重试播放
|
||||
// 推流异常中断,延时重试播放
|
||||
if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) {
|
||||
strong_self->_republish_count++;
|
||||
strong_self->rePublish(dst_url, (*failed_cnt)++);
|
||||
} else {
|
||||
//如果媒体源已经注销, 或达到了最大重试次数,回调关闭
|
||||
// 如果媒体源已经注销, 或达到了最大重试次数,回调关闭
|
||||
strong_self->_on_close(err);
|
||||
}
|
||||
});
|
||||
@ -85,16 +100,34 @@ void PusherProxy::publish(const string &dst_url) {
|
||||
void PusherProxy::rePublish(const string &dst_url, int failed_cnt) {
|
||||
auto delay = MAX(2 * 1000, MIN(failed_cnt * 3000, 60 * 1000));
|
||||
weak_ptr<PusherProxy> weak_self = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(delay / 1000.0f, [weak_self, dst_url, failed_cnt]() {
|
||||
//推流失败次数越多,则延时越长
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
_timer = std::make_shared<Timer>(
|
||||
delay / 1000.0f,
|
||||
[weak_self, dst_url, failed_cnt]() {
|
||||
// 推流失败次数越多,则延时越长
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return false;
|
||||
}
|
||||
WarnL << "推流重试[" << failed_cnt << "]:" << dst_url;
|
||||
strong_self->MediaPusher::publish(dst_url);
|
||||
return false;
|
||||
}
|
||||
WarnL << "推流重试[" << failed_cnt << "]:" << dst_url;
|
||||
strong_self->MediaPusher::publish(dst_url);
|
||||
return false;
|
||||
}, getPoller());
|
||||
},
|
||||
getPoller());
|
||||
}
|
||||
|
||||
int PusherProxy::getStatus() {
|
||||
return _live_status.load();
|
||||
}
|
||||
uint64_t PusherProxy::getLiveSecs() {
|
||||
if (_live_status == 0) {
|
||||
return _live_secs + _live_ticker.elapsedTime() / 1000;
|
||||
} else {
|
||||
return _live_secs;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t PusherProxy::getRePublishCount() {
|
||||
return _republish_count;
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -16,7 +16,9 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PusherProxy : public MediaPusher, public std::enable_shared_from_this<PusherProxy> {
|
||||
class PusherProxy
|
||||
: public MediaPusher
|
||||
, public std::enable_shared_from_this<PusherProxy> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<PusherProxy>;
|
||||
|
||||
@ -41,7 +43,11 @@ public:
|
||||
* 开始拉流播放
|
||||
* @param dstUrl 目标推流地址
|
||||
*/
|
||||
void publish(const std::string& dstUrl) override;
|
||||
void publish(const std::string &dstUrl) override;
|
||||
|
||||
int getStatus();
|
||||
uint64_t getLiveSecs();
|
||||
uint64_t getRePublishCount();
|
||||
|
||||
private:
|
||||
// 重推逻辑函数
|
||||
@ -50,6 +56,11 @@ private:
|
||||
private:
|
||||
int _retry_count;
|
||||
toolkit::Timer::Ptr _timer;
|
||||
toolkit::Ticker _live_ticker;
|
||||
// 0 表示正常 1 表示正在尝试推流
|
||||
std::atomic<int> _live_status;
|
||||
std::atomic<uint64_t> _live_secs;
|
||||
std::atomic<uint64_t> _republish_count;
|
||||
std::weak_ptr<MediaSource> _weak_src;
|
||||
std::function<void(const toolkit::SockException &ex)> _on_close;
|
||||
std::function<void(const toolkit::SockException &ex)> _on_publish;
|
||||
@ -57,4 +68,4 @@ private:
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif //SRC_DEVICE_PUSHERPROXY_H
|
||||
#endif // SRC_DEVICE_PUSHERPROXY_H
|
||||
|
@ -22,9 +22,7 @@ HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) {
|
||||
_seg_keep = seg_keep;
|
||||
}
|
||||
|
||||
HlsMaker::~HlsMaker() {
|
||||
}
|
||||
|
||||
HlsMaker::~HlsMaker() = default;
|
||||
|
||||
void HlsMaker::makeIndexFile(bool eof) {
|
||||
char file_content[1024];
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <tuple>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
|
@ -163,10 +163,10 @@ std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) {
|
||||
}
|
||||
|
||||
void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||
_media_src = std::make_shared<HlsMediaSource>(vhost, app, stream_id);
|
||||
_info.app = app;
|
||||
_info.stream = stream_id;
|
||||
_info.vhost = vhost;
|
||||
_media_src = std::make_shared<HlsMediaSource>(_info);
|
||||
}
|
||||
|
||||
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||
|
@ -46,7 +46,11 @@ HlsCookieData::~HlsCookieData() {
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
uint64_t bytes = _bytes.load();
|
||||
if (bytes >= iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast<SockInfo &>(*_sock_info));
|
||||
try {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast<SockInfo &>(*_sock_info));
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "Exception occurred: " << ex.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,7 +72,7 @@ HlsMediaSource::Ptr HlsCookieData::getMediaSource() const {
|
||||
void HlsMediaSource::setIndexFile(std::string index_file)
|
||||
{
|
||||
if (!_ring) {
|
||||
std::weak_ptr<HlsMediaSource> weakSelf = std::dynamic_pointer_cast<HlsMediaSource>(shared_from_this());
|
||||
std::weak_ptr<HlsMediaSource> weakSelf = std::static_pointer_cast<HlsMediaSource>(shared_from_this());
|
||||
auto lam = [weakSelf](int size) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
|
@ -25,8 +25,7 @@ public:
|
||||
using RingType = toolkit::RingBuffer<std::string>;
|
||||
using Ptr = std::shared_ptr<HlsMediaSource>;
|
||||
|
||||
HlsMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id)
|
||||
: MediaSource(HLS_SCHEMA, vhost, app, stream_id) {}
|
||||
HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {}
|
||||
~HlsMediaSource() override = default;
|
||||
|
||||
/**
|
||||
|
@ -35,8 +35,8 @@ public:
|
||||
|
||||
~HlsRecorder() { MpegMuxer::flush(); };
|
||||
|
||||
void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id) {
|
||||
_hls->setMediaSource(vhost, app, stream_id);
|
||||
void setMediaSource(const MediaTuple& tuple) {
|
||||
_hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream);
|
||||
}
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
|
@ -21,7 +21,7 @@ using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
MP4Demuxer::MP4Demuxer() {}
|
||||
MP4Demuxer::MP4Demuxer() = default;
|
||||
|
||||
MP4Demuxer::~MP4Demuxer() {
|
||||
closeMP4();
|
||||
|
@ -20,17 +20,18 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
MP4Reader::MP4Reader(const string &vhost, const string &app, const string &stream_id, const string &file_path) {
|
||||
MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path) {
|
||||
//读写文件建议放在后台线程
|
||||
auto tuple = MediaTuple{vhost, app, stream_id};
|
||||
_poller = WorkThreadPool::Instance().getPoller();
|
||||
_file_path = file_path;
|
||||
if (_file_path.empty()) {
|
||||
GET_CONFIG(string, recordPath, Protocol::kMP4SavePath);
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
if (enableVhost) {
|
||||
_file_path = vhost + "/" + app + "/" + stream_id;
|
||||
_file_path = tuple.shortUrl();
|
||||
} else {
|
||||
_file_path = app + "/" + stream_id;
|
||||
_file_path = tuple.app + "/" + tuple.stream;
|
||||
}
|
||||
_file_path = File::absolutePath(_file_path, recordPath);
|
||||
}
|
||||
@ -38,14 +39,14 @@ MP4Reader::MP4Reader(const string &vhost, const string &app, const string &strea
|
||||
_demuxer = std::make_shared<MP4Demuxer>();
|
||||
_demuxer->openMP4(_file_path);
|
||||
|
||||
if (stream_id.empty()) {
|
||||
if (tuple.stream.empty()) {
|
||||
return;
|
||||
}
|
||||
ProtocolOption option;
|
||||
//读取mp4文件并流化时,不重复生成mp4/hls文件
|
||||
option.enable_mp4 = false;
|
||||
option.enable_hls = false;
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(vhost, app, stream_id, _demuxer->getDurationMS() / 1000.0f, option);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(tuple, _demuxer->getDurationMS() / 1000.0f, option);
|
||||
auto tracks = _demuxer->getTracks(false);
|
||||
if (tracks.empty()) {
|
||||
throw std::runtime_error(StrPrinter << "该mp4文件没有有效的track:" << _file_path);
|
||||
@ -179,7 +180,7 @@ bool MP4Reader::pause(MediaSource &sender, bool pause) {
|
||||
}
|
||||
|
||||
bool MP4Reader::speed(MediaSource &sender, float speed) {
|
||||
if (speed < 0.1 && speed > 20) {
|
||||
if (speed < 0.1 || speed > 20) {
|
||||
WarnL << "播放速度取值范围非法:" << speed;
|
||||
return false;
|
||||
}
|
||||
|
@ -41,16 +41,16 @@ MP4Recorder::~MP4Recorder() {
|
||||
void MP4Recorder::createFile() {
|
||||
closeFile();
|
||||
auto date = getTimeStr("%Y-%m-%d");
|
||||
auto time = getTimeStr("%H-%M-%S");
|
||||
auto full_path_tmp = _folder_path + date + "/." + time + ".mp4";
|
||||
auto full_path = _folder_path + date + "/" + time + ".mp4";
|
||||
auto file_name = getTimeStr("%H-%M-%S") + "-" + std::to_string(_file_index++) + ".mp4";
|
||||
auto full_path = _folder_path + date + "/" + file_name;
|
||||
auto full_path_tmp = _folder_path + date + "/." + file_name;
|
||||
|
||||
/////record 业务逻辑//////
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = time + ".mp4";
|
||||
_info.file_name = file_name;
|
||||
_info.file_path = full_path;
|
||||
GET_CONFIG(string, appName, Record::kAppName);
|
||||
_info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + time + ".mp4";
|
||||
_info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + file_name;
|
||||
|
||||
try {
|
||||
_muxer = std::make_shared<MP4Muxer>();
|
||||
|
@ -57,13 +57,14 @@ private:
|
||||
private:
|
||||
bool _have_video = false;
|
||||
size_t _max_second;
|
||||
uint64_t _last_dts = 0;
|
||||
uint64_t _file_index = 0;
|
||||
std::string _folder_path;
|
||||
std::string _full_path;
|
||||
std::string _full_path_tmp;
|
||||
RecordInfo _info;
|
||||
MP4Muxer::Ptr _muxer;
|
||||
std::list<Track::Ptr> _tracks;
|
||||
uint64_t _last_dts = 0;
|
||||
};
|
||||
|
||||
#endif ///ENABLE_MP4
|
||||
|
@ -30,19 +30,18 @@ MpegMuxer::~MpegMuxer() {
|
||||
releaseContext();
|
||||
}
|
||||
|
||||
#define XX(name, type, value, str, mpeg_id) \
|
||||
case name : { \
|
||||
if (mpeg_id == PSI_STREAM_RESERVED) { \
|
||||
break; \
|
||||
} \
|
||||
_codec_to_trackid[track->getCodecId()] = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0); \
|
||||
return true; \
|
||||
#define XX(name, type, value, str, mpeg_id) \
|
||||
case name: { \
|
||||
if (mpeg_id == PSI_STREAM_RESERVED) { \
|
||||
break; \
|
||||
} \
|
||||
if (track->getTrackType() == TrackVideo) { \
|
||||
_have_video = true; \
|
||||
} \
|
||||
_codec_to_trackid[track->getCodecId()] = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
bool MpegMuxer::addTrack(const Track::Ptr &track) {
|
||||
if (track->getTrackType() == TrackVideo) {
|
||||
_have_video = true;
|
||||
}
|
||||
switch (track->getCodecId()) {
|
||||
CODEC_MAP(XX)
|
||||
default: break;
|
||||
@ -85,6 +84,11 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
//没有视频时,才以音频时间戳为TS的时间戳
|
||||
_timestamp = frame->dts();
|
||||
}
|
||||
|
||||
if(frame->getTrackType() == TrackType::TrackVideo){
|
||||
_key_pos = frame->keyFrame();
|
||||
_timestamp = frame->dts();
|
||||
}
|
||||
_max_cache_size = 512 + 1.2 * frame->size();
|
||||
mpeg_muxer_input((::mpeg_muxer_t *)_context, track_id, frame->keyFrame() ? 0x0001 : 0, frame->pts() * 90LL, frame->dts() * 90LL, frame->data(), frame->size());
|
||||
flushCache();
|
||||
|
@ -86,7 +86,7 @@ namespace mediakit {
|
||||
|
||||
class MpegMuxer : public MediaSinkInterface {
|
||||
public:
|
||||
MpegMuxer(bool is_ps) = default;
|
||||
MpegMuxer(bool is_ps) {}
|
||||
~MpegMuxer() override = default;
|
||||
bool addTrack(const Track::Ptr &track) override { return false; }
|
||||
void resetTracks() override {}
|
||||
|
@ -21,16 +21,16 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
string Recorder::getRecordPath(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path) {
|
||||
string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, const string &customized_path) {
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
switch (type) {
|
||||
case Recorder::type_hls: {
|
||||
GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath);
|
||||
string m3u8FilePath;
|
||||
if (enableVhost) {
|
||||
m3u8FilePath = vhost + "/" + app + "/" + stream_id + "/hls.m3u8";
|
||||
m3u8FilePath = tuple.shortUrl() + "/hls.m3u8";
|
||||
} else {
|
||||
m3u8FilePath = app + "/" + stream_id + "/hls.m3u8";
|
||||
m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.m3u8";
|
||||
}
|
||||
//Here we use the customized file path.
|
||||
if (!customized_path.empty()) {
|
||||
@ -43,9 +43,9 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
|
||||
GET_CONFIG(string, recordAppName, Record::kAppName);
|
||||
string mp4FilePath;
|
||||
if (enableVhost) {
|
||||
mp4FilePath = vhost + "/" + recordAppName + "/" + app + "/" + stream_id + "/";
|
||||
mp4FilePath = tuple.vhost + "/" + recordAppName + "/" + tuple.app + "/" + tuple.stream + "/";
|
||||
} else {
|
||||
mp4FilePath = recordAppName + "/" + app + "/" + stream_id + "/";
|
||||
mp4FilePath = recordAppName + "/" + tuple.app + "/" + tuple.stream + "/";
|
||||
}
|
||||
//Here we use the customized file path.
|
||||
if (!customized_path.empty()) {
|
||||
@ -58,14 +58,14 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option){
|
||||
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option){
|
||||
switch (type) {
|
||||
case Recorder::type_hls: {
|
||||
#if defined(ENABLE_HLS)
|
||||
auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.hls_save_path);
|
||||
auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path);
|
||||
GET_CONFIG(bool, enable_vhost, General::kEnableVhost);
|
||||
auto ret = std::make_shared<HlsRecorder>(path, enable_vhost ? string(VHOST_KEY) + "=" + vhost : "", option);
|
||||
ret->setMediaSource(vhost, app, stream_id);
|
||||
auto ret = std::make_shared<HlsRecorder>(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option);
|
||||
ret->setMediaSource(tuple);
|
||||
return ret;
|
||||
#else
|
||||
throw std::invalid_argument("hls相关功能未打开,请开启ENABLE_HLS宏后编译再测试");
|
||||
@ -75,8 +75,8 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st
|
||||
|
||||
case Recorder::type_mp4: {
|
||||
#if defined(ENABLE_MP4)
|
||||
auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.mp4_save_path);
|
||||
return std::make_shared<MP4Recorder>(path, vhost, app, stream_id, option.mp4_max_second);
|
||||
auto path = Recorder::getRecordPath(type, tuple, option.mp4_save_path);
|
||||
return std::make_shared<MP4Recorder>(path, tuple.vhost, tuple.app, tuple.stream, option.mp4_max_second);
|
||||
#else
|
||||
throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试");
|
||||
#endif
|
||||
|
@ -18,7 +18,16 @@ namespace mediakit {
|
||||
class MediaSinkInterface;
|
||||
class ProtocolOption;
|
||||
|
||||
class RecordInfo {
|
||||
struct MediaTuple {
|
||||
std::string vhost;
|
||||
std::string app;
|
||||
std::string stream;
|
||||
std::string shortUrl() const {
|
||||
return vhost + '/' + app + '/' + stream;
|
||||
}
|
||||
};
|
||||
|
||||
class RecordInfo: public MediaTuple {
|
||||
public:
|
||||
time_t start_time; // GMT 标准时间,单位秒
|
||||
float time_len; // 录像长度,单位秒
|
||||
@ -27,9 +36,6 @@ public:
|
||||
std::string file_name; // 文件名称
|
||||
std::string folder; // 文件夹路径
|
||||
std::string url; // 播放路径
|
||||
std::string app; // 应用名称
|
||||
std::string stream; // 流 ID
|
||||
std::string vhost; // 虚拟主机
|
||||
};
|
||||
|
||||
class Recorder{
|
||||
@ -50,7 +56,7 @@ public:
|
||||
* @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置
|
||||
* @return 录制文件绝对路径
|
||||
*/
|
||||
static std::string getRecordPath(type type, const std::string &vhost, const std::string &app, const std::string &stream_id,const std::string &customized_path = "");
|
||||
static std::string getRecordPath(type type, const MediaTuple& tuple, const std::string &customized_path = "");
|
||||
|
||||
/**
|
||||
* 创建录制器对象
|
||||
@ -62,7 +68,7 @@ public:
|
||||
* @param max_second mp4录制最大切片时间,单位秒,置0则采用配置文件配置
|
||||
* @return 对象指针,可能为nullptr
|
||||
*/
|
||||
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const std::string &vhost, const std::string &app, const std::string &stream_id, const ProtocolOption &option);
|
||||
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option);
|
||||
|
||||
private:
|
||||
Recorder() = delete;
|
||||
|
@ -34,7 +34,7 @@ size_t RtcpContext::getLost() {
|
||||
throw std::runtime_error("没有实现, rtp发送者无法统计丢包率");
|
||||
}
|
||||
|
||||
size_t RtcpContext::geLostInterval() {
|
||||
size_t RtcpContext::getLostInterval() {
|
||||
throw std::runtime_error("没有实现, rtp发送者无法统计丢包率");
|
||||
}
|
||||
|
||||
@ -231,7 +231,7 @@ size_t RtcpContextForRecv::getLost() {
|
||||
return getExpectedPackets() - _packets;
|
||||
}
|
||||
|
||||
size_t RtcpContextForRecv::geLostInterval() {
|
||||
size_t RtcpContextForRecv::getLostInterval() {
|
||||
auto lost = getLost();
|
||||
auto ret = lost - _last_lost;
|
||||
_last_lost = lost;
|
||||
@ -248,7 +248,7 @@ Buffer::Ptr RtcpContextForRecv::createRtcpRR(uint32_t rtcp_ssrc, uint32_t rtp_ss
|
||||
uint8_t fraction = 0;
|
||||
auto expected_interval = getExpectedPacketsInterval();
|
||||
if (expected_interval) {
|
||||
fraction = uint8_t(geLostInterval() << 8 / expected_interval);
|
||||
fraction = uint8_t(getLostInterval() << 8 / expected_interval);
|
||||
}
|
||||
|
||||
item->fraction = fraction;
|
||||
|
@ -78,7 +78,7 @@ public:
|
||||
/**
|
||||
* 上次结果与本次结果间丢包个数
|
||||
*/
|
||||
virtual size_t geLostInterval();
|
||||
virtual size_t getLostInterval();
|
||||
|
||||
protected:
|
||||
// 收到或发送的rtp的字节数
|
||||
@ -120,7 +120,7 @@ public:
|
||||
size_t getExpectedPackets() const override;
|
||||
size_t getExpectedPacketsInterval() override;
|
||||
size_t getLost() override;
|
||||
size_t geLostInterval() override;
|
||||
size_t getLostInterval() override;
|
||||
void onRtcp(RtcpHeader *rtcp) override;
|
||||
|
||||
private:
|
||||
|
@ -97,18 +97,16 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) {
|
||||
header->flv[0] = 'F';
|
||||
header->flv[1] = 'L';
|
||||
header->flv[2] = 'V';
|
||||
header->version = 1;
|
||||
header->length = htonl(9);
|
||||
header->version = FLVHeader::kFlvVersion;
|
||||
header->length = htonl(FLVHeader::kFlvHeaderLength);
|
||||
header->have_video = src->haveVideo();
|
||||
header->have_audio = src->haveAudio();
|
||||
//memset时已经赋值为0
|
||||
//header->previous_tag_size0 = 0;
|
||||
|
||||
//flv header
|
||||
onWrite(buffer, false);
|
||||
|
||||
//PreviousTagSize0 Always 0
|
||||
auto size = htonl(0);
|
||||
onWrite(obtainBuffer((char *) &size, 4), false);
|
||||
|
||||
auto &metadata = src->getMetaData();
|
||||
if (metadata) {
|
||||
//在有metadata的情况下才发送metadata
|
||||
|
72
src/Rtmp/FlvPlayer.cpp
Normal file
72
src/Rtmp/FlvPlayer.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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 "FlvPlayer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) {
|
||||
setPoller(poller);
|
||||
}
|
||||
|
||||
void FlvPlayer::play(const string &url) {
|
||||
TraceL << "play http-flv: " << url;
|
||||
_play_result = false;
|
||||
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
|
||||
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||
setMethod("GET");
|
||||
sendRequest(url);
|
||||
}
|
||||
|
||||
void FlvPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) {
|
||||
if (status != "200" && status != "206") {
|
||||
// http状态码不符合预期
|
||||
throw invalid_argument("bad http status code:" + status);
|
||||
}
|
||||
|
||||
auto content_type = const_cast<HttpClient::HttpHeader &>(header)["Content-Type"];
|
||||
if (content_type.find("video/x-flv") != 0) {
|
||||
throw invalid_argument("content type not http-flv: " + content_type);
|
||||
}
|
||||
}
|
||||
|
||||
void FlvPlayer::teardown() {
|
||||
HttpClientImp::shutdown();
|
||||
}
|
||||
|
||||
void FlvPlayer::onResponseCompleted(const SockException &ex) {
|
||||
if (!_play_result) {
|
||||
_play_result = true;
|
||||
onPlayResult(ex);
|
||||
} else {
|
||||
onShutdown(ex);
|
||||
}
|
||||
}
|
||||
|
||||
void FlvPlayer::onResponseBody(const char *buf, size_t size) {
|
||||
FlvSplitter::input(buf, size);
|
||||
}
|
||||
|
||||
bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) {
|
||||
return onMetadata(metadata);
|
||||
}
|
||||
|
||||
void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) {
|
||||
if (!_play_result && !packet->isCfgFrame()) {
|
||||
_play_result = true;
|
||||
onPlayResult(SockException(Err_success, "play http-flv success"));
|
||||
}
|
||||
onRtmpPacket(std::move(packet));
|
||||
}
|
||||
|
||||
}//mediakit
|
48
src/Rtmp/FlvPlayer.h
Normal file
48
src/Rtmp/FlvPlayer.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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_FLVPLAYER_H
|
||||
#define ZLMEDIAKIT_FLVPLAYER_H
|
||||
|
||||
#include "FlvSplitter.h"
|
||||
#include "Http/HttpClientImp.h"
|
||||
#include "Player/PlayerBase.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class FlvPlayer : public PlayerBase, public HttpClientImp, private FlvSplitter {
|
||||
public:
|
||||
FlvPlayer(const toolkit::EventPoller::Ptr &poller);
|
||||
~FlvPlayer() override = default;
|
||||
|
||||
void play(const std::string &url) override;
|
||||
void teardown() override;
|
||||
|
||||
protected:
|
||||
void onResponseHeader(const std::string &status, const HttpHeader &header) override;
|
||||
void onResponseCompleted(const toolkit::SockException &ex) override;
|
||||
void onResponseBody(const char *buf, size_t size) override;
|
||||
|
||||
protected:
|
||||
virtual void onRtmpPacket(RtmpPacket::Ptr packet) = 0;
|
||||
virtual bool onMetadata(const AMFValue &metadata) = 0;
|
||||
|
||||
private:
|
||||
bool onRecvMetadata(const AMFValue &metadata) override;
|
||||
void onRecvRtmpPacket(RtmpPacket::Ptr packet) override;
|
||||
|
||||
private:
|
||||
bool _play_result = false;
|
||||
};
|
||||
|
||||
using FlvPlayerImp = FlvPlayerBase<FlvPlayer>;
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_FLVPLAYER_H
|
125
src/Rtmp/FlvSplitter.cpp
Normal file
125
src/Rtmp/FlvSplitter.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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 "FlvSplitter.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
const char *FlvSplitter::onSearchPacketTail(const char *data, size_t len) {
|
||||
if (!_flv_started) {
|
||||
//还没获取到flv头
|
||||
if (len < sizeof(FLVHeader)) {
|
||||
//数据不够
|
||||
return nullptr;
|
||||
}
|
||||
return data + sizeof(FLVHeader);
|
||||
}
|
||||
|
||||
//获取到flv头,处理tag数据
|
||||
if (len < sizeof(RtmpTagHeader)) {
|
||||
//数据不够
|
||||
return nullptr;
|
||||
}
|
||||
return data + sizeof(RtmpTagHeader);
|
||||
}
|
||||
|
||||
ssize_t FlvSplitter::onRecvHeader(const char *data, size_t len) {
|
||||
if (!_flv_started) {
|
||||
//获取到flv头了
|
||||
auto header = reinterpret_cast<const FLVHeader *>(data);
|
||||
if (memcmp(header->flv, "FLV", 3)) {
|
||||
throw std::invalid_argument("不是flv容器格式!");
|
||||
}
|
||||
if (header->version != FLVHeader::kFlvVersion) {
|
||||
throw std::invalid_argument("flv头中version字段不正确");
|
||||
}
|
||||
if (!header->have_video && !header->have_audio) {
|
||||
throw std::invalid_argument("flv头中声明音频和视频都不存在");
|
||||
}
|
||||
if (FLVHeader::kFlvHeaderLength != ntohl(header->length)) {
|
||||
throw std::invalid_argument("flv头中length字段非法");
|
||||
}
|
||||
if (0 != ntohl(header->previous_tag_size0)) {
|
||||
throw std::invalid_argument("flv头中previous tag size字段非法");
|
||||
}
|
||||
onRecvFlvHeader(*header);
|
||||
_flv_started = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//获取到flv头,处理tag数据
|
||||
auto tag = reinterpret_cast<const RtmpTagHeader *>(data);
|
||||
auto data_size = load_be24(tag->data_size);
|
||||
_type = tag->type;
|
||||
_time_stamp = load_be24(tag->timestamp);
|
||||
_time_stamp |= (tag->timestamp_ex << 24);
|
||||
return data_size + 4/*PreviousTagSize*/;
|
||||
}
|
||||
|
||||
void FlvSplitter::onRecvContent(const char *data, size_t len) {
|
||||
len -= 4;
|
||||
auto previous_tag_size = load_be32(data + len);
|
||||
if (len != previous_tag_size - sizeof(RtmpTagHeader)) {
|
||||
WarnL << "flv previous tag size 字段非法:" << len << " != " << previous_tag_size - sizeof(RtmpTagHeader);
|
||||
}
|
||||
RtmpPacket::Ptr packet;
|
||||
switch (_type) {
|
||||
case MSG_AUDIO : {
|
||||
packet = RtmpPacket::create();
|
||||
packet->chunk_id = CHUNK_AUDIO;
|
||||
packet->stream_index = STREAM_MEDIA;
|
||||
break;
|
||||
}
|
||||
case MSG_VIDEO: {
|
||||
packet = RtmpPacket::create();
|
||||
packet->chunk_id = CHUNK_VIDEO;
|
||||
packet->stream_index = STREAM_MEDIA;
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_DATA:
|
||||
case MSG_DATA3: {
|
||||
BufferLikeString buffer(string(data, len));
|
||||
AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
bool flag = true;
|
||||
if (type == "@setDataFrame") {
|
||||
std::string type = dec.load<std::string>();
|
||||
if (type == "onMetaData") {
|
||||
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||
} else {
|
||||
WarnL << "unknown type:" << type;
|
||||
}
|
||||
} else if (type == "onMetaData") {
|
||||
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||
} else {
|
||||
WarnL << "unknown notify:" << type;
|
||||
}
|
||||
if(!flag){
|
||||
throw std::invalid_argument("check rtmp metadata failed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: WarnL << "不识别的flv msg type:" << (int) _type; return;
|
||||
}
|
||||
|
||||
packet->time_stamp = _time_stamp;
|
||||
packet->type_id = _type;
|
||||
packet->body_size = len;
|
||||
packet->buffer.assign(data, len);
|
||||
onRecvRtmpPacket(std::move(packet));
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
42
src/Rtmp/FlvSplitter.h
Normal file
42
src/Rtmp/FlvSplitter.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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_FLVSPLITTER_H
|
||||
#define ZLMEDIAKIT_FLVSPLITTER_H
|
||||
|
||||
#include "Rtmp.h"
|
||||
#include "Http/HttpRequestSplitter.h"
|
||||
#include "RtmpPlayerImp.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class FlvSplitter : public HttpRequestSplitter {
|
||||
public:
|
||||
FlvSplitter() = default;
|
||||
~FlvSplitter() = default;
|
||||
|
||||
protected:
|
||||
void onRecvContent(const char *data,size_t len) override;
|
||||
ssize_t onRecvHeader(const char *data,size_t len) override;
|
||||
const char *onSearchPacketTail(const char *data, size_t len) override;
|
||||
|
||||
protected:
|
||||
virtual void onRecvFlvHeader(const FLVHeader &header) {};
|
||||
virtual bool onRecvMetadata(const AMFValue &metadata) = 0;
|
||||
virtual void onRecvRtmpPacket(RtmpPacket::Ptr packet) = 0;
|
||||
|
||||
private:
|
||||
bool _flv_started = false;
|
||||
uint8_t _type;
|
||||
uint32_t _time_stamp;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_FLVSPLITTER_H
|
@ -113,6 +113,8 @@ public:
|
||||
|
||||
class FLVHeader {
|
||||
public:
|
||||
static constexpr uint8_t kFlvVersion = 1;
|
||||
static constexpr uint8_t kFlvHeaderLength = 9;
|
||||
//FLV
|
||||
char flv[3];
|
||||
//File version (for example, 0x01 for FLV version 1)
|
||||
@ -138,6 +140,8 @@ public:
|
||||
#endif
|
||||
//The length of this header in bytes,固定为9
|
||||
uint32_t length;
|
||||
//固定为0
|
||||
uint32_t previous_tag_size0;
|
||||
} PACKED;
|
||||
|
||||
class RtmpTagHeader {
|
||||
|
@ -46,12 +46,7 @@ public:
|
||||
* @param stream_id 流id
|
||||
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
|
||||
*/
|
||||
RtmpMediaSource(const std::string &vhost,
|
||||
const std::string &app,
|
||||
const std::string &stream_id,
|
||||
int ring_size = RTMP_GOP_SIZE) :
|
||||
MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {
|
||||
}
|
||||
RtmpMediaSource(const MediaTuple& tuple, int ring_size = RTMP_GOP_SIZE): MediaSource(RTMP_SCHEMA, tuple), _ring_size(ring_size) {}
|
||||
|
||||
~RtmpMediaSource() override { flush(); }
|
||||
|
||||
|
@ -41,7 +41,7 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
|
||||
}
|
||||
|
||||
if (!_ring) {
|
||||
std::weak_ptr<RtmpMediaSource> weakSelf = std::dynamic_pointer_cast<RtmpMediaSource>(shared_from_this());
|
||||
std::weak_ptr<RtmpMediaSource> weakSelf = std::static_pointer_cast<RtmpMediaSource>(shared_from_this());
|
||||
auto lam = [weakSelf](int size) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
@ -63,7 +63,7 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
|
||||
}
|
||||
|
||||
|
||||
RtmpMediaSourceImp::RtmpMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize) : RtmpMediaSource(vhost, app, id, ringSize)
|
||||
RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize) : RtmpMediaSource(tuple, ringSize)
|
||||
{
|
||||
_demuxer = std::make_shared<RtmpDemuxer>();
|
||||
_demuxer->setTrackListener(this);
|
||||
@ -99,7 +99,7 @@ void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option)
|
||||
_option = option;
|
||||
//不重复生成rtmp协议
|
||||
_option.enable_rtmp = false;
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), _option);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, _demuxer->getDuration(), _option);
|
||||
_muxer->setMediaListener(getListener());
|
||||
_muxer->setTrackListener(std::static_pointer_cast<RtmpMediaSourceImp>(shared_from_this()));
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
* @param id 流id
|
||||
* @param ringSize 环形缓存大小
|
||||
*/
|
||||
RtmpMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize = RTMP_GOP_SIZE);
|
||||
RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize = RTMP_GOP_SIZE);
|
||||
|
||||
~RtmpMediaSourceImp() override = default;
|
||||
|
||||
|
@ -21,13 +21,11 @@ class RtmpMediaSourceMuxer final : public RtmpMuxer, public MediaSourceEventInte
|
||||
public:
|
||||
using Ptr = std::shared_ptr<RtmpMediaSourceMuxer>;
|
||||
|
||||
RtmpMediaSourceMuxer(const std::string &vhost,
|
||||
const std::string &strApp,
|
||||
const std::string &strId,
|
||||
RtmpMediaSourceMuxer(const MediaTuple& tuple,
|
||||
const ProtocolOption &option,
|
||||
const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title) {
|
||||
_option = option;
|
||||
_media_src = std::make_shared<RtmpMediaSource>(vhost, strApp, strId);
|
||||
_media_src = std::make_shared<RtmpMediaSource>(tuple);
|
||||
getRtmpRing()->setDelegate(_media_src);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace mediakit {
|
||||
RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {}
|
||||
|
||||
RtmpPlayer::~RtmpPlayer() {
|
||||
DebugL << endl;
|
||||
DebugL;
|
||||
}
|
||||
|
||||
void RtmpPlayer::teardown() {
|
||||
@ -52,14 +52,14 @@ void RtmpPlayer::teardown() {
|
||||
|
||||
void RtmpPlayer::play(const string &url) {
|
||||
teardown();
|
||||
string host_url = FindField(url.data(), "://", "/");
|
||||
string host_url = findSubString(url.data(), "://", "/");
|
||||
{
|
||||
auto pos = url.find_last_of('/');
|
||||
if (pos != string::npos) {
|
||||
_stream_id = url.substr(pos + 1);
|
||||
}
|
||||
}
|
||||
_app = FindField(url.data(), (host_url + "/").data(), ("/" + _stream_id).data());
|
||||
_app = findSubString(url.data(), (host_url + "/").data(), ("/" + _stream_id).data());
|
||||
_tc_url = string("rtmp://") + host_url + "/" + _app;
|
||||
|
||||
if (!_app.size() || !_stream_id.size()) {
|
||||
@ -75,7 +75,7 @@ void RtmpPlayer::play(const string &url) {
|
||||
setNetAdapter((*this)[Client::kNetAdapter]);
|
||||
}
|
||||
|
||||
weak_ptr<RtmpPlayer> weak_self = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
weak_ptr<RtmpPlayer> weak_self = static_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
float play_timeout_sec = (*this)[Client::kTimeoutMS].as<int>() / 1000.0f;
|
||||
_play_timer.reset(new Timer(play_timeout_sec, [weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
@ -90,7 +90,7 @@ void RtmpPlayer::play(const string &url) {
|
||||
startConnect(host_url, port, play_timeout_sec);
|
||||
}
|
||||
|
||||
void RtmpPlayer::onErr(const SockException &ex){
|
||||
void RtmpPlayer::onError(const SockException &ex){
|
||||
//定时器_pPlayTimer为空后表明握手结束了
|
||||
onPlayResult_l(ex, !_play_timer);
|
||||
}
|
||||
@ -120,7 +120,7 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) {
|
||||
//播放成功,恢复rtmp接收超时定时器
|
||||
_rtmp_recv_ticker.resetTime();
|
||||
auto timeout_ms = (*this)[Client::kMediaTimeoutMS].as<uint64_t>();
|
||||
weak_ptr<RtmpPlayer> weak_self = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
weak_ptr<RtmpPlayer> weak_self = static_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
auto lam = [weak_self, timeout_ms]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -146,7 +146,7 @@ void RtmpPlayer::onConnect(const SockException &err) {
|
||||
onPlayResult_l(err, false);
|
||||
return;
|
||||
}
|
||||
weak_ptr<RtmpPlayer> weak_self = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
weak_ptr<RtmpPlayer> weak_self = static_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
startClientSession([weak_self]() {
|
||||
if (auto strong_self = weak_self.lock()) {
|
||||
strong_self->send_connect();
|
||||
@ -258,7 +258,7 @@ void RtmpPlayer::send_pause(bool pause) {
|
||||
|
||||
_beat_timer.reset();
|
||||
if (pause) {
|
||||
weak_ptr<RtmpPlayer> weak_self = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
weak_ptr<RtmpPlayer> weak_self = static_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
_beat_timer.reset(new Timer((*this)[Client::kBeatIntervalMS].as<int>() / 1000.0f, [weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -313,8 +313,8 @@ void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
||||
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
||||
//TraceL;
|
||||
auto val = dec.load<AMFValue>();
|
||||
if (!onCheckMeta(val)) {
|
||||
throw std::runtime_error("onCheckMeta failed");
|
||||
if (!onMetadata(val)) {
|
||||
throw std::runtime_error("onMetadata failed");
|
||||
}
|
||||
_metadata_got = true;
|
||||
}
|
||||
@ -328,18 +328,18 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) {
|
||||
_rtmp_recv_ticker.resetTime();
|
||||
if (!_play_timer) {
|
||||
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
||||
onMediaData(chunk_data);
|
||||
onRtmpPacket(chunk_data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk_data->isCfgFrame()) {
|
||||
//输入配置帧以便初始化完成各个track
|
||||
onMediaData(chunk_data);
|
||||
onRtmpPacket(chunk_data);
|
||||
} else {
|
||||
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
||||
onPlayResult_l(SockException(Err_success, "play rtmp success"), false);
|
||||
//触发onPlayResult事件后,再把帧数据输入到解码器
|
||||
onMediaData(chunk_data);
|
||||
onRtmpPacket(chunk_data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,8 +379,8 @@ void RtmpPlayer::onRtmpChunk(RtmpPacket::Ptr packet) {
|
||||
_now_stamp[idx] = chunk_data.time_stamp;
|
||||
}
|
||||
if (!_metadata_got) {
|
||||
if (!onCheckMeta(TitleMeta().getMetadata())) {
|
||||
throw std::runtime_error("onCheckMeta failed");
|
||||
if (!onMetadata(TitleMeta().getMetadata())) {
|
||||
throw std::runtime_error("onMetadata failed");
|
||||
}
|
||||
_metadata_got = true;
|
||||
}
|
||||
@ -420,49 +420,4 @@ void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
float RtmpPlayerImp::getDuration() const
|
||||
{
|
||||
return _demuxer ? _demuxer->getDuration() : 0;
|
||||
}
|
||||
|
||||
std::vector<mediakit::Track::Ptr> RtmpPlayerImp::getTracks(bool ready /*= true*/) const
|
||||
{
|
||||
return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready);
|
||||
}
|
||||
|
||||
bool RtmpPlayerImp::onCheckMeta(const AMFValue &val)
|
||||
{
|
||||
//无metadata或metadata中无track信息时,需要从数据包中获取track
|
||||
_wait_track_ready = (*this)[Client::kWaitTrackReady].as<bool>() || RtmpDemuxer::trackCount(val) == 0;
|
||||
onCheckMeta_l(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RtmpPlayerImp::onMediaData(RtmpPacket::Ptr chunkData)
|
||||
{
|
||||
if (!_demuxer) {
|
||||
//有些rtmp流没metadata
|
||||
onCheckMeta_l(TitleMeta().getMetadata());
|
||||
}
|
||||
_demuxer->inputRtmp(chunkData);
|
||||
if (_rtmp_src) {
|
||||
_rtmp_src->onWrite(std::move(chunkData));
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayerImp::onCheckMeta_l(const AMFValue &val)
|
||||
{
|
||||
_rtmp_src = std::dynamic_pointer_cast<RtmpMediaSource>(_media_src);
|
||||
if (_rtmp_src) {
|
||||
_rtmp_src->setMetaData(val);
|
||||
}
|
||||
if (_demuxer) {
|
||||
return;
|
||||
}
|
||||
_demuxer = std::make_shared<RtmpDemuxer>();
|
||||
//TraceL<<" _wait_track_ready "<<_wait_track_ready;
|
||||
_demuxer->setTrackListener(this, _wait_track_ready);
|
||||
_demuxer->loadMetaData(val);
|
||||
}
|
||||
} /* namespace mediakit */
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user