mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-10-31 00:37:39 +08:00
commit
8b6394d43d
@ -1 +1 @@
|
||||
Subproject commit 17e82574991134f798ae32f82d48e2d6c6b97b06
|
||||
Subproject commit 8611e88c2eda178973662dcfe180691ff1d8ba35
|
@ -1 +1 @@
|
||||
Subproject commit 43facc343afc2b5b70bbbc3c177f20dfa936f2bf
|
||||
Subproject commit d54285e96260e6d56d68929ec9ace402b83ff6b0
|
4
AUTHORS
4
AUTHORS
@ -14,3 +14,7 @@ huohuo <913481084@qq.com>
|
||||
[γ瑞γミ](https://github.com/JerryLinGd)
|
||||
[茄子](https://github.com/taotaobujue2008)
|
||||
[好心情](<409257224@qq.com>)
|
||||
[Xiaofeng Wang](https://github.com/wasphin)
|
||||
[doodoocoder](https://github.com/doodoocoder)
|
||||
[qingci](https://github.com/Colibrow)
|
||||
Zhou Weimin <zhouweimin@supremind.com>
|
@ -151,15 +151,7 @@ endif()
|
||||
#添加rtp库用于rtp转ps/ts
|
||||
if(ENABLE_RTPPROXY AND ENABLE_HLS)
|
||||
message(STATUS "ENABLE_RTPPROXY defined")
|
||||
include_directories(${MediaServer_Root}/librtp/include)
|
||||
|
||||
aux_source_directory(${MediaServer_Root}/librtp/include src_rtp)
|
||||
aux_source_directory(${MediaServer_Root}/librtp/source src_rtp)
|
||||
aux_source_directory(${MediaServer_Root}/librtp/payload src_rtp)
|
||||
add_library(rtp STATIC ${src_rtp})
|
||||
add_definitions(-DENABLE_RTPPROXY)
|
||||
list(APPEND LINK_LIB_LIST rtp)
|
||||
list(APPEND CXX_API_TARGETS rtp)
|
||||
endif()
|
||||
|
||||
#收集源代码
|
||||
|
86
README.md
86
README.md
@ -1,25 +1,28 @@
|
||||
![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/www/logo.png)
|
||||
![logo](https://raw.githubusercontent.com/xia-chu/ZLMediaKit/master/www/logo.png)
|
||||
|
||||
[english readme](https://github.com/xiongziliang/ZLMediaKit/blob/master/README_en.md)
|
||||
[english readme](https://github.com/xia-chu/ZLMediaKit/blob/master/README_en.md)
|
||||
|
||||
# 一个基于C++11的高性能运营级流媒体服务框架
|
||||
|
||||
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
|
||||
|
||||
[![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)
|
||||
|
||||
## 项目特点
|
||||
|
||||
- 基于C++11开发,避免使用裸指针,代码稳定可靠,性能优越。
|
||||
- 支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV/GB28181/MP4),支持协议互转。
|
||||
- 支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV/GB28181/HTTP-TS/Websocket-TS/HTTP-fMP4/Websocket-fMP4/MP4),支持协议互转。
|
||||
- 使用多路复用/多线程/异步网络IO模式开发,并发性能优越,支持海量客户端连接。
|
||||
- 代码经过长期大量的稳定性、性能测试,已经在线上商用验证已久。
|
||||
- 支持linux、macos、ios、android、windows全平台。
|
||||
- 支持画面秒开、极低延时([500毫秒内,最低可达100毫秒](https://github.com/zlmediakit/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95))。
|
||||
- 提供完善的标准[C API](https://github.com/xiongziliang/ZLMediaKit/tree/master/api/include),可以作SDK用,或供其他语言调用。
|
||||
- 提供完整的[MediaServer](https://github.com/xiongziliang/ZLMediaKit/tree/master/server)服务器,可以免开发直接部署为商用服务器。
|
||||
- 提供完善的[restful api](https://github.com/xiongziliang/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API)以及[web hook](https://github.com/xiongziliang/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API),支持丰富的业务逻辑。
|
||||
- 支持画面秒开、极低延时([500毫秒内,最低可达100毫秒](https://github.com/xia-chu/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95))。
|
||||
- 提供完善的标准[C API](https://github.com/xia-chu/ZLMediaKit/tree/master/api/include),可以作SDK用,或供其他语言调用。
|
||||
- 提供完整的[MediaServer](https://github.com/xia-chu/ZLMediaKit/tree/master/server)服务器,可以免开发直接部署为商用服务器。
|
||||
- 提供完善的[restful api](https://github.com/xia-chu/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API)以及[web hook](https://github.com/xia-chu/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API),支持丰富的业务逻辑。
|
||||
- 打通了视频监控协议栈与直播协议栈,对RTSP/RTMP支持都很完善。
|
||||
- 全面支持H265/H264/AAC/G711。
|
||||
- 全面支持H265/H264/AAC/G711/OPUS。
|
||||
|
||||
## 项目定位
|
||||
|
||||
@ -29,6 +32,8 @@
|
||||
|
||||
|
||||
## 功能清单
|
||||
### 功能一览
|
||||
<img width="800" alt="图片" src="https://user-images.githubusercontent.com/11495632/93857284-f15b5f00-fcec-11ea-894d-83eb656dc235.png">
|
||||
|
||||
- RTSP[S]
|
||||
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
||||
@ -38,7 +43,7 @@
|
||||
- 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口
|
||||
- 支持H265编码
|
||||
- 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式)
|
||||
- 支持任意编码格式的rtsp推流,只是除H264/H265/AAC/G711外无法转协议
|
||||
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||
|
||||
- RTMP[S]
|
||||
- RTMP[S] 播放服务器,支持RTSP/MP4/HLS转RTMP
|
||||
@ -47,16 +52,25 @@
|
||||
- RTMP[S] 推流客户端
|
||||
- 支持http[s]-flv直播
|
||||
- 支持websocket-flv直播
|
||||
- 支持任意编码格式的rtmp推流,只是除H264/H265/AAC/G711外无法转协议
|
||||
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
||||
- 支持[RTMP-OPUS](https://github.com/xia-chu/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81)
|
||||
|
||||
- HLS
|
||||
- 支持HLS文件生成,自带HTTP文件服务器
|
||||
- 通过cookie追踪技术,可以模拟HLS播放为长连接,实现丰富的业务逻辑
|
||||
- 支持完备的HLS用户追踪、播放统计等业务功能,可以实现HLS按需拉流等业务
|
||||
- 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务
|
||||
- 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4
|
||||
- 支持H264/H265/AAC/G711/OPUS编码
|
||||
|
||||
- TS
|
||||
- 支持http[s]-ts直播
|
||||
- 支持ws[s]-ts直播
|
||||
|
||||
- fMP4
|
||||
- 支持http[s]-fmp4直播
|
||||
- 支持ws[s]-fmp4直播
|
||||
|
||||
- HTTP[S]
|
||||
- HTTP[S]与WebSocket
|
||||
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
|
||||
- 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`
|
||||
- 完整HTTP API服务器,可以作为web后台开发框架
|
||||
@ -65,12 +79,15 @@
|
||||
- 支持WebSocket服务器和客户端
|
||||
- 支持http文件访问鉴权
|
||||
|
||||
- GB28181
|
||||
- 支持UDP/TCP国标RTP(PS或TS)推流,可以转换成RTSP/RTMP/HLS等协议
|
||||
- GB28181与RTP推流
|
||||
- 支持UDP/TCP国标RTP(PS或TS)推流服务器,可以转换成RTSP/RTMP/HLS等协议
|
||||
- 支持RTSP/RTMP/HLS转国标推流客户端,支持TCP/UDP模式,提供相应restful api
|
||||
- 支持H264/H265/AAC/G711/OPUS编码
|
||||
|
||||
- 点播
|
||||
- MP4点播与录制
|
||||
- 支持录制为FLV/HLS/MP4
|
||||
- RTSP/RTMP/HTTP-FLV/WS-FLV支持MP4文件点播,支持seek
|
||||
- 支持H264/H265/AAC/G711/OPUS编码
|
||||
|
||||
- 其他
|
||||
- 支持丰富的restful api以及web hook事件
|
||||
@ -83,28 +100,29 @@
|
||||
- 提供c api sdk
|
||||
- 支持FFmpeg拉流代理任意格式的流
|
||||
- 支持http api生成并返回实时截图
|
||||
- 支持按需解复用、转协议,当有人观看时才开启转协议
|
||||
|
||||
## 更新日志
|
||||
- 2020/5/17 新增支持hls播发器,支持hls拉流代理
|
||||
|
||||
|
||||
## 编译以及测试
|
||||
**编译前务必仔细参考wiki:[快速开始](https://github.com/xiongziliang/ZLMediaKit/wiki/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)操作!!!**
|
||||
**编译前务必仔细参考wiki:[快速开始](https://github.com/xia-chu/ZLMediaKit/wiki/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)操作!!!**
|
||||
|
||||
## 怎么使用
|
||||
|
||||
你有三种方法使用ZLMediaKit,分别是:
|
||||
|
||||
- 1、使用c api,作为sdk使用,请参考[这里](https://github.com/xiongziliang/ZLMediaKit/tree/master/api/include).
|
||||
- 2、作为独立的流媒体服务器使用,不想做c/c++开发的,可以参考[restful api](https://github.com/xiongziliang/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API)和[web hook](https://github.com/xiongziliang/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API).
|
||||
- 3、如果想做c/c++开发,添加业务逻辑增加功能,可以参考这里的[测试程序](https://github.com/xiongziliang/ZLMediaKit/tree/master/tests).
|
||||
- 1、使用c api,作为sdk使用,请参考[这里](https://github.com/xia-chu/ZLMediaKit/tree/master/api/include).
|
||||
- 2、作为独立的流媒体服务器使用,不想做c/c++开发的,可以参考[restful api](https://github.com/xia-chu/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API)和[web hook](https://github.com/xia-chu/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API).
|
||||
- 3、如果想做c/c++开发,添加业务逻辑增加功能,可以参考这里的[测试程序](https://github.com/xia-chu/ZLMediaKit/tree/master/tests).
|
||||
|
||||
## Docker 镜像
|
||||
|
||||
你可以从Docker Hub下载已经编译好的镜像并启动它:
|
||||
|
||||
```bash
|
||||
docker run -id -p 1935:1935 -p 8080:80 gemfield/zlmediakit:20.04-runtime-ubuntu18.04
|
||||
docker run -id -p 1935:1935 -p 8080:80 -p 8554:554 -p 10000:10000 -p 10000:10000/udp panjjo/zlmediakit
|
||||
```
|
||||
|
||||
你也可以根据Dockerfile编译镜像:
|
||||
@ -115,14 +133,17 @@ bash build_docker_images.sh
|
||||
|
||||
## 参考案例
|
||||
|
||||
- [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xiahcu/IOSMedia)
|
||||
- [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xiahcu/IOSPlayer)
|
||||
- [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xiongziliang/ZLMediaPlayer)
|
||||
- [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xia-chu/IOSMedia)
|
||||
- [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xia-chu/IOSPlayer)
|
||||
- [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xia-chu/ZLMediaPlayer)
|
||||
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
||||
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
|
||||
- [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk)
|
||||
- [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi)
|
||||
- [GB28181-2016网络视频平台](https://github.com/swwheihei/wvp)
|
||||
- [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
|
||||
- [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo)
|
||||
- [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome)
|
||||
|
||||
|
||||
## 授权协议
|
||||
@ -143,7 +164,7 @@ bash build_docker_images.sh
|
||||
- 1、仔细看下readme、wiki,如果有必要可以查看下issue.
|
||||
- 2、如果您的问题还没解决,可以提issue.
|
||||
- 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提.
|
||||
- 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/xiongziliang/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)).
|
||||
- 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/xia-chu/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)).
|
||||
|
||||
## 致谢
|
||||
|
||||
@ -165,11 +186,18 @@ bash build_docker_images.sh
|
||||
[茄子](https://github.com/taotaobujue2008)
|
||||
[好心情](<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](<zhouweimin@supremind.com>)
|
||||
|
||||
|
||||
## 捐赠
|
||||
|
||||
欢迎捐赠以便更好的推动项目的发展,谢谢您的支持!
|
||||
|
||||
[支付宝](https://gitee.com/xiahcu/other/raw/master/IMG_3919.JPG)
|
||||
[支付宝](https://gitee.com/xia-chu/other/raw/master/IMG_3919.JPG)
|
||||
|
||||
[微信](https://gitee.com/xiahcu/other/raw/master/IMG_3920.JPG)
|
||||
[微信](https://gitee.com/xia-chu/other/raw/master/IMG_3920.JPG)
|
||||
|
118
README_en.md
118
README_en.md
@ -2,12 +2,16 @@
|
||||
|
||||
# A lightweight ,high performance and stable stream server and client framework based on C++11.
|
||||
|
||||
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
|
||||
|
||||
[![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)
|
||||
|
||||
## Why ZLMediaKit?
|
||||
- Developed based on C++ 11, the code is stable and reliable, avoiding the use of raw pointers, cross-platform porting is simple and convenient, and the code is clear and concise.
|
||||
- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/Websocket-flv`),and support Inter-protocol conversion.
|
||||
- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/WebSocket-flv/HTTP-TS/WebSocket-TS/HTTP-fMP4/Websocket-fMP4/MP4`),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.
|
||||
@ -20,15 +24,15 @@
|
||||
- RTSP[S] player and pusher.
|
||||
- RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` .
|
||||
- Basic/Digest/Url Authentication.
|
||||
- H264/H265/AAC/G711 codec.
|
||||
- H265/H264/AAC/G711/OPUS codec.
|
||||
- Recorded as mp4.
|
||||
- Vod of mp4.
|
||||
|
||||
- RTMP[S]
|
||||
- RTMP[S] server,support player and pusher.
|
||||
- RTMP[S] player and pusher.
|
||||
- Support HTTP-FLV player.
|
||||
- H264/H265/AAC/G711 codec.
|
||||
- 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)
|
||||
@ -37,6 +41,12 @@
|
||||
- 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.
|
||||
|
||||
- TS
|
||||
- Support HTTP-TS/WebSocket-TS sever.
|
||||
|
||||
- fMP4
|
||||
- Support HTTP-fMP4/WebSocket-fMP4 sever.
|
||||
|
||||
- HTTP[S]
|
||||
- HTTP server,suppor directory meun、RESTful http api.
|
||||
@ -56,62 +66,6 @@
|
||||
- Support TS / PS streaming push through RTP,and it can be converted to RTSP / RTMP / HLS / FLV.
|
||||
- Support real-time online screenshot http api.
|
||||
|
||||
|
||||
- Protocol conversion:
|
||||
|
||||
| protocol/codec | H264 | H265 | AAC | other |
|
||||
| :------------------------------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | Y | Y | N |
|
||||
| RTMP --> RTSP[S] | Y | Y | Y | N |
|
||||
| RTSP[S] --> HLS | Y | Y | Y | N |
|
||||
| RTMP --> HLS | Y | Y | Y | N |
|
||||
| RTSP[S] --> MP4 | Y | Y | Y | N |
|
||||
| RTMP --> MP4 | Y | Y | Y | N |
|
||||
| MP4 --> RTSP[S] | Y | Y | Y | N |
|
||||
| MP4 --> RTMP | Y | Y | Y | N |
|
||||
| HLS --> RTSP/RTMP/MP4 | Y | Y | Y | N |
|
||||
|
||||
- Stream generation:
|
||||
|
||||
| feature/codec | H264 | H265 | AAC | other |
|
||||
| :-----------: | :--: | :--: | :--: | :---: |
|
||||
| RTSP[S] push | Y | Y | Y | Y |
|
||||
| RTSP proxy | Y | Y | Y | Y |
|
||||
| RTMP push | Y | Y | Y | Y |
|
||||
| RTMP proxy | Y | Y | Y | Y |
|
||||
|
||||
- RTP transport:
|
||||
|
||||
| feature/transport | tcp | udp | http | udp_multicast |
|
||||
| :-----------------: | :--: | :--: | :--: | :-----------: |
|
||||
| RTSP[S] Play Server | Y | Y | Y | Y |
|
||||
| RTSP[S] Push Server | Y | Y | N | N |
|
||||
| RTSP Player | Y | Y | N | Y |
|
||||
| RTSP Pusher | Y | Y | N | N |
|
||||
|
||||
|
||||
- Server supported:
|
||||
|
||||
| Server | Y/N |
|
||||
| :-----------------: | :--: |
|
||||
| RTSP[S] Play Server | Y |
|
||||
| RTSP[S] Push Server | Y |
|
||||
| RTMP | Y |
|
||||
| HTTP[S]/WebSocket[S] | Y |
|
||||
|
||||
- Client supported:
|
||||
|
||||
| Client | Y/N |
|
||||
| :---------: | :--: |
|
||||
| RTSP Player | Y |
|
||||
| RTSP Pusher | Y |
|
||||
| RTMP Player | Y |
|
||||
| RTMP Pusher | Y |
|
||||
| HTTP[S] | Y |
|
||||
| WebSocket[S] | Y |
|
||||
| HLS player | Y |
|
||||
|
||||
|
||||
## System Requirements
|
||||
|
||||
- Compiler support c++11,GCC4.8/Clang3.3/VC2015 or above.
|
||||
@ -191,8 +145,6 @@ git submodule update --init
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Build on Android
|
||||
|
||||
Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project.
|
||||
@ -298,7 +250,7 @@ git submodule update --init
|
||||
## Docker Image
|
||||
You can pull a pre-built docker image from Docker Hub and run with
|
||||
```bash
|
||||
docker run -id -p 1935:1935 -p 8080:80 gemfield/zlmediakit
|
||||
docker run -id -p 1935:1935 -p 8080:80 -p 8554:554 -p 10000:10000 -p 10000:10000/udp panjjo/zlmediakit
|
||||
```
|
||||
|
||||
Dockerfile is also supplied to build images on Ubuntu 16.04
|
||||
@ -307,44 +259,6 @@ cd docker
|
||||
docker build -t zlmediakit .
|
||||
```
|
||||
|
||||
## Mirrors
|
||||
|
||||
[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit)
|
||||
|
||||
[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit)
|
||||
|
||||
|
||||
## Licence
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
Copyright (c) 2018 huohuo <913481084@qq.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Contact
|
||||
- Email:<1213642868@qq.com>
|
||||
- QQ chat group:542509000
|
||||
|
@ -19,25 +19,25 @@ extern "C" {
|
||||
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
||||
//MP4Info对象的C映射
|
||||
typedef void* mk_mp4_info;
|
||||
//MP4Info::ui64StartedTime
|
||||
// GMT 标准时间,单位秒
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx);
|
||||
//MP4Info::ui64TimeLen
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx);
|
||||
//MP4Info::ui64FileSize
|
||||
// 录像长度,单位秒
|
||||
API_EXPORT float API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx);
|
||||
// 文件大小,单位 BYTE
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx);
|
||||
//MP4Info::strFilePath
|
||||
// 文件路径
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx);
|
||||
//MP4Info::strFileName
|
||||
// 文件名称
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx);
|
||||
//MP4Info::strFolder
|
||||
// 文件夹路径
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx);
|
||||
//MP4Info::strUrl
|
||||
// 播放路径
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx);
|
||||
//MP4Info::strVhost
|
||||
// 应用名称
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx);
|
||||
//MP4Info::strAppName
|
||||
// 流 ID
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx);
|
||||
//MP4Info::strStreamId
|
||||
// 虚拟主机
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx);
|
||||
|
||||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||
@ -276,13 +276,11 @@ typedef void* mk_publish_auth_invoker;
|
||||
/**
|
||||
* 执行Broadcast::PublishAuthInvoker
|
||||
* @param err_msg 为空或null则代表鉴权成功
|
||||
* @param enable_rtxp rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp
|
||||
* @param enable_hls 是否允许转换hls
|
||||
* @param enable_mp4 是否运行MP4录制
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
||||
const char *err_msg,
|
||||
int enable_rtxp,
|
||||
int enable_hls,
|
||||
int enable_mp4);
|
||||
|
||||
|
@ -26,14 +26,12 @@ typedef void *mk_media;
|
||||
* @param app 应用名,推荐为live
|
||||
* @param stream 流id,例如camera
|
||||
* @param duration 时长(单位秒),直播则为0
|
||||
* @param rtsp_enabled 是否启用rtsp协议
|
||||
* @param rtmp_enabled 是否启用rtmp协议
|
||||
* @param hls_enabled 是否生成hls
|
||||
* @param mp4_enabled 是否生成mp4
|
||||
* @return 对象指针
|
||||
*/
|
||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration,
|
||||
int rtsp_enabled, int rtmp_enabled, int hls_enabled, int mp4_enabled);
|
||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream,
|
||||
float duration, int hls_enabled, int mp4_enabled);
|
||||
|
||||
/**
|
||||
* 销毁媒体源
|
||||
@ -54,7 +52,7 @@ API_EXPORT void API_CALL mk_media_init_video(mk_media ctx, int track_id, int wid
|
||||
/**
|
||||
* 添加音频轨道
|
||||
* @param ctx 对象指针
|
||||
* @param track_id 2:CodecAAC/3:CodecG711A/4:CodecG711U
|
||||
* @param track_id 2:CodecAAC/3:CodecG711A/4:CodecG711U/5:OPUS
|
||||
* @param channel 通道数
|
||||
* @param sample_bit 采样位数,只支持16
|
||||
* @param sample_rate 采样率
|
||||
@ -95,7 +93,7 @@ API_EXPORT void API_CALL mk_media_input_h265(mk_media ctx, void *data, int len,
|
||||
* @param data 不包含adts头的单帧AAC数据
|
||||
* @param len 单帧AAC数据字节数
|
||||
* @param dts 时间戳,毫秒
|
||||
* @param adts adts头
|
||||
* @param adts adts头,可以为null
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_input_aac(mk_media ctx, void *data, int len, uint32_t dts, void *adts);
|
||||
|
||||
@ -109,13 +107,13 @@ API_EXPORT void API_CALL mk_media_input_aac(mk_media ctx, void *data, int len, u
|
||||
API_EXPORT void API_CALL mk_media_input_pcm(mk_media ctx, void *data, int len, uint32_t pts);
|
||||
|
||||
/**
|
||||
* 输入单帧G711音频
|
||||
* 输入单帧OPUS/G711音频帧
|
||||
* @param ctx 对象指针
|
||||
* @param data 单帧G711数据
|
||||
* @param len 单帧G711数据字节数
|
||||
* @param data 单帧音频数据
|
||||
* @param len 单帧音频数据字节数
|
||||
* @param dts 时间戳,毫秒
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_media_input_g711(mk_media ctx, void* data, int len, uint32_t dts);
|
||||
API_EXPORT void API_CALL mk_media_input_audio(mk_media ctx, void* data, int len, uint32_t dts);
|
||||
|
||||
/**
|
||||
* MediaSource.close()回调事件
|
||||
|
@ -22,5 +22,6 @@
|
||||
#include "mk_tcp.h"
|
||||
#include "mk_util.h"
|
||||
#include "mk_thread.h"
|
||||
#include "mk_rtp_server.h"
|
||||
|
||||
#endif /* MK_API_H_ */
|
||||
|
@ -31,7 +31,7 @@ typedef void(API_CALL *on_mk_play_event)(void *user_data,int err_code,const char
|
||||
* 收到音视频数据回调
|
||||
* @param user_data 用户数据指针
|
||||
* @param track_type 0:视频,1:音频
|
||||
* @param codec_id 0:H264,1:H265,2:AAC 3.G711A 4.G711U
|
||||
* @param codec_id 0:H264,1:H265,2:AAC 3.G711A 4.G711U 5.OPUS
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
* @param dts 解码时间戳,单位毫秒
|
||||
|
58
api/include/mk_rtp_server.h
Normal file
58
api/include/mk_rtp_server.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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 "mk_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* mk_rtp_server;
|
||||
|
||||
/**
|
||||
* 创建GB28181 RTP 服务器
|
||||
* @param port 监听端口,0则为随机
|
||||
* @param enable_tcp 创建udp端口时是否同时监听tcp端口
|
||||
* @param stream_id 该端口绑定的流id
|
||||
* @return
|
||||
*/
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable_tcp, const char *stream_id);
|
||||
|
||||
/**
|
||||
* 销毁GB28181 RTP 服务器
|
||||
* @param ctx 服务器对象
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx);
|
||||
|
||||
/**
|
||||
* 获取本地监听的端口号
|
||||
* @param ctx 服务器对象
|
||||
* @return 端口号
|
||||
*/
|
||||
API_EXPORT uint16_t API_CALL mk_rtp_server_port(mk_rtp_server ctx);
|
||||
|
||||
/**
|
||||
* GB28181 RTP 服务器接收流超时时触发
|
||||
* @param user_data 用户数据指针
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_rtp_server_detach)(void *user_data);
|
||||
|
||||
/**
|
||||
* 监听B28181 RTP 服务器接收流超时事件
|
||||
* @param ctx 服务器对象
|
||||
* @param cb 回调函数
|
||||
* @param user_data 回调函数用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rtp_server_detach cb, void *user_data);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -101,11 +101,10 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){
|
||||
s_events.on_mk_media_publish((mk_media_info) &args,
|
||||
(mk_publish_auth_invoker) &invoker,
|
||||
(mk_sock_info) &sender);
|
||||
}else{
|
||||
GET_CONFIG(bool,toRtxp,General::kPublishToRtxp);
|
||||
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
||||
GET_CONFIG(bool,toMP4,General::kPublishToMP4);
|
||||
invoker("",toRtxp,toHls,toMP4);
|
||||
} else {
|
||||
GET_CONFIG(bool, toHls, General::kPublishToHls);
|
||||
GET_CONFIG(bool, toMP4, General::kPublishToMP4);
|
||||
invoker("", toHls, toMP4);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -18,65 +18,65 @@
|
||||
#include "Rtsp/RtspSession.h"
|
||||
using namespace mediakit;
|
||||
|
||||
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
||||
///////////////////////////////////////////RecordInfo/////////////////////////////////////////////
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->ui64StartedTime;
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->start_time;
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx){
|
||||
API_EXPORT float API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->ui64TimeLen;
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->time_len;
|
||||
}
|
||||
|
||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->ui64FileSize;
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->file_size;
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strFilePath.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->file_path.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strFileName.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->file_name.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strFolder.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->folder.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strUrl.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->url.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strVhost.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->vhost.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strAppName.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->app.c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){
|
||||
assert(ctx);
|
||||
MP4Info *info = (MP4Info *)ctx;
|
||||
return info->strStreamId.c_str();
|
||||
RecordInfo *info = (RecordInfo *)ctx;
|
||||
return info->stream.c_str();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||
@ -256,7 +256,7 @@ static C get_http_header( const char *response_header[]){
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::move(header);
|
||||
return header;
|
||||
}
|
||||
|
||||
API_EXPORT mk_http_body API_CALL mk_http_body_from_multi_form(const char *key_val[],const char *file_path){
|
||||
@ -382,12 +382,11 @@ API_EXPORT void API_CALL mk_rtsp_auth_invoker_clone_release(const mk_rtsp_auth_i
|
||||
///////////////////////////////////////////Broadcast::PublishAuthInvoker/////////////////////////////////////////////
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
||||
const char *err_msg,
|
||||
int enable_rtxp,
|
||||
int enable_hls,
|
||||
int enable_mp4){
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
(*invoker)(err_msg ? err_msg : "", enable_rtxp, enable_hls, enable_mp4);
|
||||
(*invoker)(err_msg ? err_msg : "", enable_hls, enable_mp4);
|
||||
}
|
||||
|
||||
API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx){
|
||||
|
@ -78,7 +78,7 @@ static C get_http_header( const char *response_header[]){
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::move(header);
|
||||
return header;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_http_requester_set_body(mk_http_requester ctx, mk_http_body body){
|
||||
|
@ -117,11 +117,10 @@ API_EXPORT int API_CALL mk_media_total_reader_count(mk_media ctx){
|
||||
return (*obj)->getChannel()->totalReaderCount();
|
||||
}
|
||||
|
||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration,
|
||||
int rtsp_enabled, int rtmp_enabled, int hls_enabled, int mp4_enabled) {
|
||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream,
|
||||
float duration, int hls_enabled, int mp4_enabled) {
|
||||
assert(vhost && app && stream);
|
||||
MediaHelper::Ptr *obj(new MediaHelper::Ptr(new MediaHelper(vhost, app, stream, duration,
|
||||
rtsp_enabled, rtmp_enabled, hls_enabled, mp4_enabled)));
|
||||
MediaHelper::Ptr *obj(new MediaHelper::Ptr(new MediaHelper(vhost, app, stream, duration, hls_enabled, mp4_enabled)));
|
||||
(*obj)->attachEvent();
|
||||
return (mk_media) obj;
|
||||
}
|
||||
@ -188,8 +187,8 @@ API_EXPORT void API_CALL mk_media_input_pcm(mk_media ctx, void *data , int len,
|
||||
#endif //ENABLE_FAAC
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_media_input_g711(mk_media ctx, void* data, int len, uint32_t dts){
|
||||
API_EXPORT void API_CALL mk_media_input_audio(mk_media ctx, void* data, int len, uint32_t dts){
|
||||
assert(ctx && data && len > 0);
|
||||
MediaHelper::Ptr* obj = (MediaHelper::Ptr*) ctx;
|
||||
(*obj)->getChannel()->inputG711((char*)data, len, dts);
|
||||
(*obj)->getChannel()->inputAudio((char*)data, len, dts);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ public:
|
||||
strong_self->onData(frame);
|
||||
}
|
||||
});
|
||||
for (auto &track : _player->getTracks()) {
|
||||
for (auto &track : _player->getTracks(false)) {
|
||||
track->addDelegate(delegate);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_proxy_player API_CALL mk_proxy_player_create(const char *vhost, const char *app, const char *stream, int hls_enabled, int mp4_enabled) {
|
||||
assert(vhost && app && stream);
|
||||
PlayerProxy::Ptr *obj(new PlayerProxy::Ptr(new PlayerProxy(vhost, app, stream, true, true, hls_enabled, mp4_enabled)));
|
||||
PlayerProxy::Ptr *obj(new PlayerProxy::Ptr(new PlayerProxy(vhost, app, stream, hls_enabled, mp4_enabled)));
|
||||
return (mk_proxy_player) obj;
|
||||
}
|
||||
|
||||
|
40
api/source/mk_rtp_server.cpp
Normal file
40
api/source/mk_rtp_server.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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 "mk_rtp_server.h"
|
||||
#include "Rtp/RtpServer.h"
|
||||
using namespace mediakit;
|
||||
|
||||
API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable_tcp, const char *stream_id){
|
||||
RtpServer::Ptr *server = new RtpServer::Ptr(new RtpServer);
|
||||
(*server)->start(port, stream_id, enable_tcp);
|
||||
return server;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx){
|
||||
RtpServer::Ptr *server = (RtpServer::Ptr *)ctx;
|
||||
delete server;
|
||||
}
|
||||
|
||||
API_EXPORT uint16_t API_CALL mk_rtp_server_port(mk_rtp_server ctx){
|
||||
RtpServer::Ptr *server = (RtpServer::Ptr *)ctx;
|
||||
return (*server)->getPort();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rtp_server_detach cb, void *user_data){
|
||||
RtpServer::Ptr *server = (RtpServer::Ptr *) ctx;
|
||||
if (cb) {
|
||||
(*server)->setOnDetach([cb, user_data]() {
|
||||
cb(user_data);
|
||||
});
|
||||
} else {
|
||||
(*server)->setOnDetach(nullptr);
|
||||
}
|
||||
}
|
@ -61,8 +61,8 @@ void API_CALL on_mk_media_publish(const mk_media_info url_info,
|
||||
mk_media_info_get_stream(url_info),
|
||||
mk_media_info_get_params(url_info));
|
||||
|
||||
//允许推流,并且允许转rtxp/hls/mp4
|
||||
mk_publish_auth_invoker_do(invoker, NULL, 1, 1, 1);
|
||||
//允许推流,并且允许转hls/mp4
|
||||
mk_publish_auth_invoker_do(invoker, NULL, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,8 +40,6 @@ addMuteAudio=1
|
||||
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
resetWhenRePlay=1
|
||||
#是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
||||
publishToRtxp=1
|
||||
#是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||
publishToHls=1
|
||||
#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||
@ -68,6 +66,8 @@ segDur=2
|
||||
segNum=3
|
||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||
segRetain=5
|
||||
# 是否广播 ts 切片完成通知
|
||||
broadcastRecordTs=0
|
||||
|
||||
[hook]
|
||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||
@ -85,6 +85,8 @@ on_play=https://127.0.0.1/index/hook/on_play
|
||||
on_publish=https://127.0.0.1/index/hook/on_publish
|
||||
#录制mp4切片完成事件
|
||||
on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
|
||||
# 录制 hls ts 切片完成事件
|
||||
on_record_ts=https://127.0.0.1/index/hook/on_record_ts
|
||||
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
|
||||
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
|
||||
#rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
|
||||
|
@ -1048,6 +1048,108 @@
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "开始发送rtp(startSendRtp)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/startSendRtp?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=obs&ssrc=1&dst_url=127.0.0.1&dst_port=10000&is_udp=0",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"startSendRtp"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "live",
|
||||
"description": "应用名,例如 live"
|
||||
},
|
||||
{
|
||||
"key": "stream",
|
||||
"value": "obs",
|
||||
"description": "流id,例如 obs"
|
||||
},
|
||||
{
|
||||
"key": "ssrc",
|
||||
"value": "1",
|
||||
"description": "rtp的ssrc"
|
||||
},
|
||||
{
|
||||
"key": "dst_url",
|
||||
"value": "127.0.0.1",
|
||||
"description": "目标ip或域名"
|
||||
},
|
||||
{
|
||||
"key": "dst_port",
|
||||
"value": "10000",
|
||||
"description": "目标端口"
|
||||
},
|
||||
{
|
||||
"key": "is_udp",
|
||||
"value": "0",
|
||||
"description": "是否为udp模式,否则为tcp模式"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "停止 发送rtp(stopSendRtp)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/stopSendRtp?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=obs",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"stopSendRtp"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "live",
|
||||
"description": "应用名,例如 live"
|
||||
},
|
||||
{
|
||||
"key": "stream",
|
||||
"value": "obs",
|
||||
"description": "流id,例如 obs"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
@ -1074,17 +1176,17 @@
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"id": "0e272976-965b-4f25-8b9e-5916c59234d7",
|
||||
"id": "ce426571-eb1e-4067-8901-01978c982fed",
|
||||
"key": "ZLMediaKit_URL",
|
||||
"value": "zlmediakit.com:8880"
|
||||
},
|
||||
{
|
||||
"id": "321374c3-3357-4405-915e-9cb524d95fbc",
|
||||
"id": "2d3dfd4a-a39c-47d8-a3e9-37d80352ea5f",
|
||||
"key": "ZLMediaKit_secret",
|
||||
"value": "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
||||
},
|
||||
{
|
||||
"id": "468ce1f6-ec79-44d2-819e-5cb9f42cd396",
|
||||
"id": "0aacc473-3a2e-4ef9-b415-e86ce71e0c42",
|
||||
"key": "defaultVhost",
|
||||
"value": "__defaultVhost__"
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "Util/File.h"
|
||||
#include "System.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
#include "Network/sockutil.h"
|
||||
|
||||
namespace FFmpeg {
|
||||
#define FFmpeg_FIELD "ffmpeg."
|
||||
@ -45,6 +46,18 @@ FFmpegSource::~FFmpegSource() {
|
||||
DebugL;
|
||||
}
|
||||
|
||||
static bool is_local_ip(const string &ip){
|
||||
if (ip == "127.0.0.1" || ip == "localhost") {
|
||||
return true;
|
||||
}
|
||||
auto ips = SockUtil::getInterfaceList();
|
||||
for (auto &obj : ips) {
|
||||
if (ip == obj["ip"]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) {
|
||||
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
|
||||
@ -60,7 +73,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_
|
||||
_process.run(cmd,ffmpeg_log.empty() ? "" : File::absolutePath("",ffmpeg_log));
|
||||
InfoL << cmd;
|
||||
|
||||
if(_media_info._host == "127.0.0.1"){
|
||||
if (is_local_ip(_media_info._host)) {
|
||||
//推流给自己的,通过判断流是否注册上来判断是否正常
|
||||
if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){
|
||||
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
|
||||
@ -179,7 +192,7 @@ void FFmpegSource::startTimer(int timeout_ms) {
|
||||
//自身已经销毁
|
||||
return false;
|
||||
}
|
||||
if (strongSelf->_media_info._host == "127.0.0.1") {
|
||||
if (is_local_ip(strongSelf->_media_info._host)) {
|
||||
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
||||
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
||||
//同步查找流
|
||||
@ -232,33 +245,19 @@ bool FFmpegSource::close(MediaSource &sender, bool force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int FFmpegSource::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if(listener){
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
return sender.readerCount();
|
||||
}
|
||||
|
||||
void FFmpegSource::onNoneReader(MediaSource &sender){
|
||||
auto listener = _listener.lock();
|
||||
if(listener){
|
||||
listener->onNoneReader(sender);
|
||||
return;
|
||||
}
|
||||
MediaSourceEvent::onNoneReader(sender);
|
||||
}
|
||||
|
||||
void FFmpegSource::onRegist(MediaSource &sender, bool regist){
|
||||
auto listener = _listener.lock();
|
||||
if(listener){
|
||||
listener->onRegist(sender, regist);
|
||||
}
|
||||
}
|
||||
|
||||
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
||||
_listener = src->getListener();
|
||||
src->setListener(shared_from_this());
|
||||
auto listener = src->getListener();
|
||||
if (listener.lock().get() != this) {
|
||||
//防止多次进入onGetMediaSource函数导致无效递归调用的bug
|
||||
_listener = listener;
|
||||
src->setListener(shared_from_this());
|
||||
} else {
|
||||
WarnL << "多次触发onGetMediaSource事件:"
|
||||
<< src->getSchema() << "/"
|
||||
<< src->getVhost() << "/"
|
||||
<< src->getApp() << "/"
|
||||
<< src->getId();
|
||||
}
|
||||
}
|
||||
|
||||
void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const function<void(bool)> &cb) {
|
||||
|
@ -40,7 +40,7 @@ private:
|
||||
~FFmpegSnap() = delete;
|
||||
};
|
||||
|
||||
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource> , public MediaSourceEvent{
|
||||
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource> , public MediaSourceEventInterceptor{
|
||||
public:
|
||||
typedef shared_ptr<FFmpegSource> Ptr;
|
||||
typedef function<void(const SockException &ex)> onPlay;
|
||||
@ -60,9 +60,6 @@ private:
|
||||
|
||||
//MediaSourceEvent override
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
void onNoneReader(MediaSource &sender) override;
|
||||
void onRegist(MediaSource &sender, bool regist) override;
|
||||
|
||||
private:
|
||||
Process _process;
|
||||
@ -72,7 +69,6 @@ private:
|
||||
string _src_url;
|
||||
string _dst_url;
|
||||
function<void()> _onClose;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
Ticker _replay_ticker;
|
||||
};
|
||||
|
||||
|
@ -144,7 +144,7 @@ static ApiArgsType getAllArgs(const Parser &parser) {
|
||||
for (auto &pr : parser.getUrlArgs()) {
|
||||
allArgs[pr.first] = pr.second;
|
||||
}
|
||||
return std::move(allArgs);
|
||||
return allArgs;
|
||||
}
|
||||
|
||||
static inline void addHttpListener(){
|
||||
@ -596,8 +596,6 @@ void installWebApi() {
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &url,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4,
|
||||
int rtp_type,
|
||||
@ -610,7 +608,7 @@ void installWebApi() {
|
||||
return;
|
||||
}
|
||||
//添加拉流代理
|
||||
PlayerProxy::Ptr player(new PlayerProxy(vhost,app,stream,enable_rtsp,enable_rtmp,enable_hls,enable_mp4));
|
||||
PlayerProxy::Ptr player(new PlayerProxy(vhost, app, stream, enable_hls, enable_mp4));
|
||||
s_proxyMap[key] = player;
|
||||
|
||||
//指定RTP over TCP(播放rtsp时有效)
|
||||
@ -636,13 +634,11 @@ void installWebApi() {
|
||||
//测试url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs
|
||||
api_regist2("/index/api/addStreamProxy",[](API_ARGS2){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
|
||||
CHECK_ARGS("vhost","app","stream","url");
|
||||
addStreamProxy(allArgs["vhost"],
|
||||
allArgs["app"],
|
||||
allArgs["stream"],
|
||||
allArgs["url"],
|
||||
allArgs["enable_rtsp"],/* 是否rtsp转发 */
|
||||
allArgs["enable_rtmp"],/* 是否rtmp转发 */
|
||||
allArgs["enable_hls"],/* 是否hls转发 */
|
||||
allArgs["enable_mp4"],/* 是否MP4录制 */
|
||||
allArgs["rtp_type"],
|
||||
@ -788,7 +784,14 @@ void installWebApi() {
|
||||
CHECK_ARGS("stream_id");
|
||||
|
||||
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
||||
val["hit"] = (int) s_rtpServerMap.erase(allArgs["stream_id"]);
|
||||
auto it = s_rtpServerMap.find(allArgs["stream_id"]);
|
||||
if(it == s_rtpServerMap.end()){
|
||||
val["hit"] = 0;
|
||||
return;
|
||||
}
|
||||
auto server = it->second;
|
||||
s_rtpServerMap.erase(it);
|
||||
val["hit"] = 1;
|
||||
});
|
||||
|
||||
api_regist1("/index/api/listRtpServer",[](API_ARGS1){
|
||||
@ -803,6 +806,39 @@ void installWebApi() {
|
||||
}
|
||||
});
|
||||
|
||||
api_regist2("/index/api/startSendRtp",[](API_ARGS2){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream", "ssrc", "dst_url", "dst_port", "is_udp");
|
||||
|
||||
auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
||||
if (!src) {
|
||||
throw ApiRetException("该媒体流不存在", API::OtherFailed);
|
||||
}
|
||||
|
||||
src->startSendRtp(allArgs["dst_url"], allArgs["dst_port"], allArgs["ssrc"], allArgs["is_udp"], [val, headerOut, invoker](const SockException &ex){
|
||||
if (ex) {
|
||||
const_cast<Value &>(val)["code"] = API::OtherFailed;
|
||||
const_cast<Value &>(val)["msg"] = ex.what();
|
||||
}
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
api_regist1("/index/api/stopSendRtp",[](API_ARGS1){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream");
|
||||
|
||||
auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
||||
if (!src) {
|
||||
throw ApiRetException("该媒体流不存在", API::OtherFailed);
|
||||
}
|
||||
|
||||
if (!src->stopSendRtp()) {
|
||||
throw ApiRetException("尚未开始推流,停止失败", API::OtherFailed);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
#endif//ENABLE_RTPPROXY
|
||||
|
||||
// 开始录制hls或MP4
|
||||
@ -1031,8 +1067,6 @@ void installWebApi() {
|
||||
allArgs["stream"],
|
||||
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
||||
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
||||
true,/* 开启rtsp转发 */
|
||||
true,/* 开启rtmp转发 */
|
||||
true,/* 开启hls转发 */
|
||||
false,/* 禁用MP4录制 */
|
||||
0,//rtp over tcp方式拉流
|
||||
|
@ -53,6 +53,7 @@ const string kOnRtspAuth = HOOK_FIELD"on_rtsp_auth";
|
||||
const string kOnStreamChanged = HOOK_FIELD"on_stream_changed";
|
||||
const string kOnStreamNotFound = HOOK_FIELD"on_stream_not_found";
|
||||
const string kOnRecordMp4 = HOOK_FIELD"on_record_mp4";
|
||||
const string kOnRecordTs = HOOK_FIELD"on_record_ts";
|
||||
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";
|
||||
@ -70,6 +71,7 @@ onceToken token([](){
|
||||
mINI::Instance()[kOnStreamChanged] = "https://127.0.0.1/index/hook/on_stream_changed";
|
||||
mINI::Instance()[kOnStreamNotFound] = "https://127.0.0.1/index/hook/on_stream_not_found";
|
||||
mINI::Instance()[kOnRecordMp4] = "https://127.0.0.1/index/hook/on_record_mp4";
|
||||
mINI::Instance()[kOnRecordTs] = "https://127.0.0.1/index/hook/on_record_ts";
|
||||
mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login";
|
||||
mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader";
|
||||
mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access";
|
||||
@ -161,7 +163,7 @@ static ArgsType make_json(const MediaInfo &args){
|
||||
body["app"] = args._app;
|
||||
body["stream"] = args._streamid;
|
||||
body["params"] = args._param_strs;
|
||||
return std::move(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
static void reportServerStarted(){
|
||||
@ -190,16 +192,16 @@ void installWebHook(){
|
||||
GET_CONFIG(string,hook_stream_chaned,Hook::kOnStreamChanged);
|
||||
GET_CONFIG(string,hook_stream_not_found,Hook::kOnStreamNotFound);
|
||||
GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4);
|
||||
GET_CONFIG(string,hook_record_ts,Hook::kOnRecordTs);
|
||||
GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin);
|
||||
GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader);
|
||||
GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
|
||||
GET_CONFIG(bool,toRtxp,General::kPublishToRtxp);
|
||||
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
||||
GET_CONFIG(bool,toMP4,General::kPublishToMP4);
|
||||
if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
||||
invoker("",toRtxp,toHls,toMP4);
|
||||
invoker("", toHls, toMP4);
|
||||
return;
|
||||
}
|
||||
//异步执行该hook api,防止阻塞NoticeCenter
|
||||
@ -211,27 +213,20 @@ void installWebHook(){
|
||||
do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
|
||||
if(err.empty()){
|
||||
//推流鉴权成功
|
||||
bool enableRtxp = toRtxp;
|
||||
bool enableHls = toHls;
|
||||
bool enableMP4 = toMP4;
|
||||
|
||||
//兼容用户不传递enableRtxp、enableHls、enableMP4参数
|
||||
if(obj.isMember("enableRtxp")){
|
||||
enableRtxp = obj["enableRtxp"].asBool();
|
||||
}
|
||||
|
||||
if(obj.isMember("enableHls")){
|
||||
//兼容用户不传递enableHls、enableMP4参数
|
||||
if (obj.isMember("enableHls")) {
|
||||
enableHls = obj["enableHls"].asBool();
|
||||
}
|
||||
|
||||
if(obj.isMember("enableMP4")){
|
||||
if (obj.isMember("enableMP4")) {
|
||||
enableMP4 = obj["enableMP4"].asBool();
|
||||
}
|
||||
|
||||
invoker(err,enableRtxp,enableHls,enableMP4);
|
||||
}else{
|
||||
invoker(err, enableHls, enableMP4);
|
||||
} else {
|
||||
//推流鉴权失败
|
||||
invoker(err,false, false, false);
|
||||
invoker(err, false, false);
|
||||
}
|
||||
|
||||
});
|
||||
@ -336,7 +331,7 @@ void installWebHook(){
|
||||
//监听播放失败(未找到特定的流)事件
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){
|
||||
if(!hook_enable || hook_stream_not_found.empty()){
|
||||
closePlayer();
|
||||
// closePlayer();
|
||||
return;
|
||||
}
|
||||
auto body = make_json(args);
|
||||
@ -347,28 +342,40 @@ void installWebHook(){
|
||||
do_http_hook(hook_stream_not_found,body, nullptr);
|
||||
});
|
||||
|
||||
static auto getRecordInfo = [](const RecordInfo &info) {
|
||||
ArgsType body;
|
||||
body["start_time"] = (Json::UInt64) info.start_time;
|
||||
body["file_size"] = (Json::UInt64) info.file_size;
|
||||
body["time_len"] = info.time_len;
|
||||
body["file_path"] = info.file_path;
|
||||
body["file_name"] = info.file_name;
|
||||
body["folder"] = info.folder;
|
||||
body["url"] = info.url;
|
||||
body["app"] = info.app;
|
||||
body["stream"] = info.stream;
|
||||
body["vhost"] = info.vhost;
|
||||
return body;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
//录制mp4文件成功后广播
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
|
||||
if(!hook_enable || hook_record_mp4.empty()){
|
||||
if (!hook_enable || hook_record_mp4.empty()) {
|
||||
return;
|
||||
}
|
||||
ArgsType body;
|
||||
body["start_time"] = (Json::UInt64)info.ui64StartedTime;
|
||||
body["time_len"] = (Json::UInt64)info.ui64TimeLen;
|
||||
body["file_size"] = (Json::UInt64)info.ui64FileSize;
|
||||
body["file_path"] = info.strFilePath;
|
||||
body["file_name"] = info.strFileName;
|
||||
body["folder"] = info.strFolder;
|
||||
body["url"] = info.strUrl;
|
||||
body["app"] = info.strAppName;
|
||||
body["stream"] = info.strStreamId;
|
||||
body["vhost"] = info.strVhost;
|
||||
//执行hook
|
||||
do_http_hook(hook_record_mp4,body, nullptr);
|
||||
do_http_hook(hook_record_mp4, getRecordInfo(info), nullptr);
|
||||
});
|
||||
#endif //ENABLE_MP4
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastRecordTs, [](BroadcastRecordTsArgs) {
|
||||
if (!hook_enable || hook_record_ts.empty()) {
|
||||
return;
|
||||
}
|
||||
// 执行 hook
|
||||
do_http_hook(hook_record_ts, getRecordInfo(info), nullptr);
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){
|
||||
if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
||||
invoker("");
|
||||
@ -407,7 +414,6 @@ void installWebHook(){
|
||||
}
|
||||
strongSrc->close(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -12,23 +12,17 @@
|
||||
#include "Util/logger.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Extension/Opus.h"
|
||||
#include "Extension/G711.h"
|
||||
#include "Extension/H264.h"
|
||||
#include "Extension/H265.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
DevChannel::DevChannel(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream_id,
|
||||
float duration,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4) :
|
||||
MultiMediaSourceMuxer(vhost, app, stream_id, duration, enable_rtsp, enable_rtmp, enable_hls, enable_mp4) {}
|
||||
DevChannel::DevChannel(const string &vhost, const string &app, const string &stream_id,
|
||||
float duration, bool enable_hls, bool enable_mp4) :
|
||||
MultiMediaSourceMuxer(vhost, app, stream_id, duration, true, true, enable_hls, enable_mp4) {}
|
||||
|
||||
DevChannel::~DevChannel() {}
|
||||
|
||||
@ -109,11 +103,12 @@ void DevChannel::inputH265(const char *data, int len, uint32_t dts, uint32_t pts
|
||||
inputFrame(frame);
|
||||
}
|
||||
|
||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
||||
class FrameAutoDelete : public FrameFromPtr{
|
||||
public:
|
||||
template <typename ... ARGS>
|
||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
||||
virtual ~AACFrameCacheAble() {
|
||||
FrameAutoDelete(ARGS && ...args) : FrameFromPtr(std::forward<ARGS>(args)...){}
|
||||
|
||||
~FrameAutoDelete() override {
|
||||
delete [] _ptr;
|
||||
};
|
||||
|
||||
@ -123,31 +118,32 @@ public:
|
||||
};
|
||||
|
||||
void DevChannel::inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header){
|
||||
if(dts == 0){
|
||||
dts = (uint32_t)_aTicker[1].elapsedTime();
|
||||
if (dts == 0) {
|
||||
dts = (uint32_t) _aTicker[1].elapsedTime();
|
||||
}
|
||||
|
||||
if(adts_header){
|
||||
if(adts_header + 7 == data_without_adts){
|
||||
if (adts_header) {
|
||||
if (adts_header + ADTS_HEADER_LEN == data_without_adts) {
|
||||
//adts头和帧在一起
|
||||
inputFrame(std::make_shared<AACFrameNoCacheAble>((char *)data_without_adts - 7, len + 7, dts, 0, 7));
|
||||
}else{
|
||||
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data_without_adts - ADTS_HEADER_LEN, len + ADTS_HEADER_LEN, dts, 0, ADTS_HEADER_LEN));
|
||||
} else {
|
||||
//adts头和帧不在一起
|
||||
char *dataWithAdts = new char[len + 7];
|
||||
memcpy(dataWithAdts, adts_header, 7);
|
||||
memcpy(dataWithAdts + 7 , data_without_adts , len);
|
||||
inputFrame(std::make_shared<AACFrameCacheAble>(dataWithAdts, len + 7, dts, 0, 7));
|
||||
char *data_with_adts = new char[len + ADTS_HEADER_LEN];
|
||||
memcpy(data_with_adts, adts_header, ADTS_HEADER_LEN);
|
||||
memcpy(data_with_adts + ADTS_HEADER_LEN, data_without_adts, len);
|
||||
inputFrame(std::make_shared<FrameAutoDelete>(_audio->codecId, data_with_adts, len + ADTS_HEADER_LEN, dts, 0, ADTS_HEADER_LEN));
|
||||
}
|
||||
} else {
|
||||
//没有adts头
|
||||
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data_without_adts, len, dts, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void DevChannel::inputG711(const char *data, int len, uint32_t dts){
|
||||
void DevChannel::inputAudio(const char *data, int len, uint32_t dts){
|
||||
if (dts == 0) {
|
||||
dts = (uint32_t)_aTicker[1].elapsedTime();
|
||||
dts = (uint32_t) _aTicker[1].elapsedTime();
|
||||
}
|
||||
auto frame = std::make_shared<G711FrameNoCacheAble>((char*)data, len, dts, 0);
|
||||
frame->setCodec(_audio->codecId);
|
||||
inputFrame(frame);
|
||||
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data, len, dts, 0));
|
||||
}
|
||||
|
||||
void DevChannel::initVideo(const VideoInfo &info) {
|
||||
@ -165,6 +161,7 @@ void DevChannel::initAudio(const AudioInfo &info) {
|
||||
case CodecAAC : addTrack(std::make_shared<AACTrack>()); break;
|
||||
case CodecG711A :
|
||||
case CodecG711U : addTrack(std::make_shared<G711Track>(info.codecId, info.iSampleRate, info.iChannel, info.iSampleBit)); break;
|
||||
case CodecOpus : addTrack(std::make_shared<OpusTrack>()); break;
|
||||
default: WarnL << "不支持该类型的音频编码类型:" << info.codecId; break;
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,9 @@
|
||||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
|
||||
#ifdef ENABLE_FAAC
|
||||
#include "Codec/AACEncoder.h"
|
||||
#endif //ENABLE_FAAC
|
||||
@ -55,16 +53,10 @@ class DevChannel : public MultiMediaSourceMuxer{
|
||||
public:
|
||||
typedef std::shared_ptr<DevChannel> Ptr;
|
||||
//fDuration<=0为直播,否则为点播
|
||||
DevChannel(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream_id,
|
||||
float duration = 0,
|
||||
bool enable_rtsp = true,
|
||||
bool enable_rtmp = true,
|
||||
bool enable_hls = true,
|
||||
bool enable_mp4 = false);
|
||||
DevChannel(const string &vhost, const string &app, const string &stream_id,
|
||||
float duration = 0, bool enable_hls = true, bool enable_mp4 = false);
|
||||
|
||||
virtual ~DevChannel();
|
||||
~DevChannel() override ;
|
||||
|
||||
/**
|
||||
* 初始化视频Track
|
||||
@ -108,12 +100,12 @@ public:
|
||||
void inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header);
|
||||
|
||||
/**
|
||||
* G711音频帧
|
||||
* 输入OPUS/G711音频帧
|
||||
* @param data 音频帧
|
||||
* @param len 帧数据长度
|
||||
* @param dts 时间戳,单位毫秒
|
||||
*/
|
||||
void inputG711(const char* data, int len, uint32_t dts);
|
||||
void inputAudio(const char *data, int len, uint32_t dts);
|
||||
|
||||
#ifdef ENABLE_X264
|
||||
/**
|
||||
|
@ -161,7 +161,7 @@ vector<Track::Ptr> MediaSink::getTracks(bool trackReady) const{
|
||||
}
|
||||
ret.emplace_back(pr.second);
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
@ -36,6 +36,11 @@ public:
|
||||
*/
|
||||
virtual void addTrack(const Track::Ptr & track) = 0;
|
||||
|
||||
/**
|
||||
* 添加所有Track完毕
|
||||
*/
|
||||
virtual void addTrackCompleted() {}
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
*/
|
||||
@ -70,7 +75,7 @@ public:
|
||||
* 这样会增加生成流的延时,如果添加了音视频双Track,那么可以不调用此方法
|
||||
* 否则为了降低流注册延时,请手动调用此方法
|
||||
*/
|
||||
void addTrackCompleted();
|
||||
void addTrackCompleted() override;
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
|
@ -8,7 +8,6 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "MediaSource.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
#include "Util/util.h"
|
||||
@ -17,16 +16,19 @@
|
||||
using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
recursive_mutex MediaSource::g_mtxMediaSrc;
|
||||
MediaSource::SchemaVhostAppStreamMap MediaSource::g_mapMediaSrc;
|
||||
recursive_mutex s_media_source_mtx;
|
||||
MediaSource::SchemaVhostAppStreamMap s_media_source_map;
|
||||
|
||||
MediaSource::MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) :
|
||||
_strSchema(strSchema), _strApp(strApp), _strId(strId) {
|
||||
if (strVhost.empty()) {
|
||||
_strVhost = DEFAULT_VHOST;
|
||||
MediaSource::MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id){
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
if (!enableVhost) {
|
||||
_vhost = DEFAULT_VHOST;
|
||||
} else {
|
||||
_strVhost = strVhost;
|
||||
_vhost = vhost.empty() ? DEFAULT_VHOST : vhost;
|
||||
}
|
||||
_schema = schema;
|
||||
_app = app;
|
||||
_stream_id = stream_id;
|
||||
}
|
||||
|
||||
MediaSource::~MediaSource() {
|
||||
@ -34,32 +36,28 @@ MediaSource::~MediaSource() {
|
||||
}
|
||||
|
||||
const string& MediaSource::getSchema() const {
|
||||
return _strSchema;
|
||||
return _schema;
|
||||
}
|
||||
|
||||
const string& MediaSource::getVhost() const {
|
||||
return _strVhost;
|
||||
return _vhost;
|
||||
}
|
||||
|
||||
const string& MediaSource::getApp() const {
|
||||
//获取该源的id
|
||||
return _strApp;
|
||||
return _app;
|
||||
}
|
||||
|
||||
const string& MediaSource::getId() const {
|
||||
return _strId;
|
||||
return _stream_id;
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MediaSource::getTracks(bool trackReady) const {
|
||||
auto strongPtr = _track_source.lock();
|
||||
if(strongPtr){
|
||||
return strongPtr->getTracks(trackReady);
|
||||
vector<Track::Ptr> MediaSource::getTracks(bool ready) const {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
|
||||
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
||||
_track_source = track_src;
|
||||
return listener->getTracks(const_cast<MediaSource &>(*this), ready);
|
||||
}
|
||||
|
||||
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
@ -77,12 +75,13 @@ int MediaSource::totalReaderCount(){
|
||||
}
|
||||
return listener->totalReaderCount(*this);
|
||||
}
|
||||
bool MediaSource::seekTo(uint32_t ui32Stamp) {
|
||||
|
||||
bool MediaSource::seekTo(uint32_t stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(*this,ui32Stamp);
|
||||
return listener->seekTo(*this, stamp);
|
||||
}
|
||||
|
||||
bool MediaSource::close(bool force) {
|
||||
@ -93,19 +92,17 @@ bool MediaSource::close(bool force) {
|
||||
return listener->close(*this,force);
|
||||
}
|
||||
|
||||
void MediaSource::onNoneReader(){
|
||||
void MediaSource::onReaderChanged(int size) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return;
|
||||
}
|
||||
if (listener->totalReaderCount(*this) == 0) {
|
||||
listener->onNoneReader(*this);
|
||||
if (listener) {
|
||||
listener->onReaderChanged(*this, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path){
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
WarnL << "未设置MediaSource的事件监听者,setupRecord失败:" << getSchema() << "/" << getVhost() << "/" << getApp() << "/" << getId();
|
||||
return false;
|
||||
}
|
||||
return listener->setupRecord(*this, type, start, custom_path);
|
||||
@ -119,13 +116,30 @@ bool MediaSource::isRecording(Recorder::type type){
|
||||
return listener->isRecording(*this, type);
|
||||
}
|
||||
|
||||
void MediaSource::startSendRtp(const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb){
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
cb(SockException(Err_other, "尚未设置事件监听器"));
|
||||
return;
|
||||
}
|
||||
return listener->startSendRtp(*this, dst_url, dst_port, ssrc, is_udp, cb);
|
||||
}
|
||||
|
||||
bool MediaSource::stopSendRtp() {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->stopSendRtp(*this);
|
||||
}
|
||||
|
||||
void MediaSource::for_each_media(const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||
decltype(g_mapMediaSrc) copy;
|
||||
decltype(s_media_source_map) copy;
|
||||
{
|
||||
//拷贝g_mapMediaSrc后再遍历,考虑到是高频使用的全局单例锁,并且在上锁时会执行回调代码
|
||||
//拷贝s_media_source_map后再遍历,考虑到是高频使用的全局单例锁,并且在上锁时会执行回调代码
|
||||
//很容易导致多个锁交叉死锁的情况,而且该函数使用频率不高,拷贝开销相对来说是可以接受的
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
copy = g_mapMediaSrc;
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
copy = s_media_source_map;
|
||||
}
|
||||
|
||||
for (auto &pr0 : copy) {
|
||||
@ -180,42 +194,76 @@ static void eraseIfEmpty(MAP &map, IT0 it0, IT1 it1, IT2 it2) {
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry, const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
auto src = MediaSource::find_l(info._schema, info._vhost, info._app, info._streamid, true);
|
||||
static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, const string &app, const string &id, bool create_new) {
|
||||
string vhost = vhost_in;
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(vhost.empty() || !enableVhost){
|
||||
vhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
MediaSource::Ptr ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
//查找某一媒体源,找到后返回
|
||||
searchMedia(s_media_source_map, schema, vhost, app, id,
|
||||
[&](MediaSource::SchemaVhostAppStreamMap::iterator &it0, MediaSource::VhostAppStreamMap::iterator &it1,
|
||||
MediaSource::AppStreamMap::iterator &it2, MediaSource::StreamMap::iterator &it3) {
|
||||
ret = it3->second.lock();
|
||||
if (!ret) {
|
||||
//该对象已经销毁
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(s_media_source_map, it0, it1, it2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(!ret && create_new && schema != HLS_SCHEMA){
|
||||
//未查找媒体源,则读取mp4创建一个
|
||||
//播放hls不触发mp4点播(因为HLS也可以用于录像,不是纯粹的直播)
|
||||
ret = MediaSource::createFromMP4(schema, vhost, app, id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry,
|
||||
const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
auto src = find_l(info._schema, info._vhost, info._app, info._streamid, true);
|
||||
if (src || !retry) {
|
||||
cb(src);
|
||||
return;
|
||||
}
|
||||
|
||||
void *listener_tag = session.get();
|
||||
weak_ptr<TcpSession> weakSession = session;
|
||||
weak_ptr<TcpSession> weak_session = session;
|
||||
|
||||
GET_CONFIG(int, maxWaitMS, General::kMaxStreamWaitTimeMS);
|
||||
auto onTimeout = session->getPoller()->doDelayTask(maxWaitMS, [cb, listener_tag]() {
|
||||
auto on_timeout = session->getPoller()->doDelayTask(maxWaitMS, [cb, listener_tag]() {
|
||||
//最多等待一定时间,如果这个时间内,流未注册上,那么返回未找到流
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
cb(nullptr);
|
||||
return 0;
|
||||
});
|
||||
|
||||
auto cancelAll = [onTimeout, listener_tag]() {
|
||||
auto cancel_all = [on_timeout, listener_tag]() {
|
||||
//取消延时任务,防止多次回调
|
||||
onTimeout->cancel();
|
||||
on_timeout->cancel();
|
||||
//取消媒体注册事件监听
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
};
|
||||
|
||||
function<void()> closePlayer = [cb, cancelAll]() {
|
||||
cancelAll();
|
||||
function<void()> close_player = [cb, cancel_all]() {
|
||||
cancel_all();
|
||||
//告诉播放器,流不存在,这样会立即断开播放器
|
||||
cb(nullptr);
|
||||
};
|
||||
|
||||
auto onRegist = [weakSession, info, cb, cancelAll](BroadcastMediaChangedArgs) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if (!strongSession) {
|
||||
auto on_regist = [weak_session, info, cb, cancel_all](BroadcastMediaChangedArgs) {
|
||||
auto strong_session = weak_session.lock();
|
||||
if (!strong_session) {
|
||||
//自己已经销毁
|
||||
cancelAll();
|
||||
cancel_all();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -228,11 +276,11 @@ void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSe
|
||||
return;
|
||||
}
|
||||
|
||||
cancelAll();
|
||||
cancel_all();
|
||||
|
||||
//播发器请求的流终于注册上了,切换到自己的线程再回复
|
||||
strongSession->async([weakSession, info, cb]() {
|
||||
auto strongSession = weakSession.lock();
|
||||
strong_session->async([weak_session, info, cb]() {
|
||||
auto strongSession = weak_session.lock();
|
||||
if (!strongSession) {
|
||||
return;
|
||||
}
|
||||
@ -243,9 +291,9 @@ void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSe
|
||||
};
|
||||
|
||||
//监听媒体注册事件
|
||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
|
||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, on_regist);
|
||||
//广播未找到流,此时可以立即去拉流,这样还来得及
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), closePlayer);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), close_player);
|
||||
}
|
||||
|
||||
void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr<TcpSession> &session,const function<void(const Ptr &src)> &cb){
|
||||
@ -256,232 +304,129 @@ MediaSource::Ptr MediaSource::find(const string &schema, const string &vhost, co
|
||||
return find_l(schema, vhost, app, id, false);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MediaSource::find_l(const string &schema, const string &vhost_tmp, const string &app, const string &id, bool bMake) {
|
||||
string vhost = vhost_tmp;
|
||||
if(vhost.empty()){
|
||||
vhost = DEFAULT_VHOST;
|
||||
MediaSource::Ptr MediaSource::find(const string &vhost, const string &app, const string &stream_id){
|
||||
auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id);
|
||||
if (src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(!enableVhost){
|
||||
vhost = DEFAULT_VHOST;
|
||||
src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id);
|
||||
if (src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
MediaSource::Ptr ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
//查找某一媒体源,找到后返回
|
||||
searchMedia(g_mapMediaSrc, schema, vhost, app, id, [&](SchemaVhostAppStreamMap::iterator &it0,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2,
|
||||
StreamMap::iterator &it3) {
|
||||
ret = it3->second.lock();
|
||||
if (!ret) {
|
||||
//该对象已经销毁
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(!ret && bMake){
|
||||
//未查找媒体源,则创建一个
|
||||
ret = createFromMP4(schema, vhost, app, id);
|
||||
}
|
||||
return ret;
|
||||
return MediaSource::find(HLS_SCHEMA, vhost, app, stream_id);
|
||||
}
|
||||
void MediaSource::regist() {
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(!enableVhost){
|
||||
_strVhost = DEFAULT_VHOST;
|
||||
}
|
||||
//注册该源,注册后服务器才能找到该源
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
g_mapMediaSrc[_strSchema][_strVhost][_strApp][_strId] = shared_from_this();
|
||||
}
|
||||
_StrPrinter codec_info;
|
||||
auto tracks = getTracks(true);
|
||||
for(auto &track : tracks) {
|
||||
auto codec_type = track->getTrackType();
|
||||
codec_info << track->getCodecName();
|
||||
switch (codec_type) {
|
||||
case TrackAudio : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
codec_info << "["
|
||||
<< audio_track->getAudioSampleRate() << "/"
|
||||
<< audio_track->getAudioChannel() << "/"
|
||||
<< audio_track->getAudioSampleBit() << "] ";
|
||||
break;
|
||||
}
|
||||
case TrackVideo : {
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
|
||||
codec_info << "["
|
||||
<< video_track->getVideoWidth() << "/"
|
||||
<< video_track->getVideoHeight() << "/"
|
||||
<< round(video_track->getVideoFps()) << "] ";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId << " " << codec_info;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, true, *this);
|
||||
|
||||
void MediaSource::emitEvent(bool regist){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(*this, true);
|
||||
//触发回调
|
||||
listener->onRegist(*this, regist);
|
||||
}
|
||||
//触发广播
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this);
|
||||
InfoL << (regist ? "媒体注册:" : "媒体注销:") << _schema << " " << _vhost << " " << _app << " " << _stream_id;
|
||||
}
|
||||
|
||||
void MediaSource::regist() {
|
||||
{
|
||||
//减小互斥锁临界区
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
s_media_source_map[_schema][_vhost][_app][_stream_id] = shared_from_this();
|
||||
}
|
||||
emitEvent(true);
|
||||
}
|
||||
|
||||
//反注册该源
|
||||
bool MediaSource::unregist() {
|
||||
bool ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
ret = searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2,
|
||||
StreamMap::iterator &it3) {
|
||||
auto strongMedia = it3->second.lock();
|
||||
if (strongMedia && this != strongMedia.get()) {
|
||||
//不是自己,不允许反注册
|
||||
return false;
|
||||
}
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
||||
return true;
|
||||
});
|
||||
//减小互斥锁临界区
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
ret = searchMedia(s_media_source_map, _schema, _vhost, _app, _stream_id,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0, VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2, StreamMap::iterator &it3) {
|
||||
auto strong_self = it3->second.lock();
|
||||
if (strong_self && this != strong_self.get()) {
|
||||
//不是自己,不允许反注册
|
||||
return false;
|
||||
}
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(s_media_source_map, it0, it1, it2);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(ret){
|
||||
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, false, *this);
|
||||
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(*this, false);
|
||||
}
|
||||
if (ret) {
|
||||
emitEvent(false);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
||||
|
||||
void MediaInfo::parse(const string &url){
|
||||
//string url = "rtsp://127.0.0.1:8554/live/id?key=val&a=1&&b=2&vhost=vhost.com";
|
||||
void MediaInfo::parse(const string &url_in){
|
||||
string url = url_in;
|
||||
auto pos = url.find("?");
|
||||
if (pos != string::npos) {
|
||||
_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);
|
||||
}else{
|
||||
if (schema_pos != string::npos) {
|
||||
_schema = url.substr(0, schema_pos);
|
||||
} else {
|
||||
schema_pos = -3;
|
||||
}
|
||||
auto split_vec = split(url.substr(schema_pos + 3),"/");
|
||||
if(split_vec.size() > 0){
|
||||
auto split_vec = split(url.substr(schema_pos + 3), "/");
|
||||
if (split_vec.size() > 0) {
|
||||
auto vhost = split_vec[0];
|
||||
auto pos = vhost.find(":");
|
||||
if(pos != string::npos){
|
||||
_host = _vhost = vhost.substr(0,pos);
|
||||
if (pos != string::npos) {
|
||||
_host = _vhost = vhost.substr(0, pos);
|
||||
_port = vhost.substr(pos + 1);
|
||||
} else{
|
||||
} else {
|
||||
_host = _vhost = vhost;
|
||||
}
|
||||
|
||||
if(_vhost == "localhost" || INADDR_NONE != inet_addr(_vhost.data())){
|
||||
if (_vhost == "localhost" || INADDR_NONE != inet_addr(_vhost.data())) {
|
||||
//如果访问的是localhost或ip,那么则为默认虚拟主机
|
||||
_vhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
}
|
||||
if(split_vec.size() > 1){
|
||||
if (split_vec.size() > 1) {
|
||||
_app = split_vec[1];
|
||||
}
|
||||
if(split_vec.size() > 2){
|
||||
string steamid;
|
||||
for(int i = 2 ; i < split_vec.size() ; ++i){
|
||||
steamid.append(split_vec[i] + "/");
|
||||
if (split_vec.size() > 2) {
|
||||
string stream_id;
|
||||
for (int i = 2; i < split_vec.size(); ++i) {
|
||||
stream_id.append(split_vec[i] + "/");
|
||||
}
|
||||
if(steamid.back() == '/'){
|
||||
steamid.pop_back();
|
||||
}
|
||||
auto pos = steamid.find("?");
|
||||
if(pos != string::npos){
|
||||
_streamid = steamid.substr(0,pos);
|
||||
_param_strs = steamid.substr(pos + 1);
|
||||
auto params = Parser::parseArgs(_param_strs);
|
||||
if(params.find(VHOST_KEY) != params.end()){
|
||||
_vhost = params[VHOST_KEY];
|
||||
}
|
||||
} else{
|
||||
_streamid = steamid;
|
||||
if (stream_id.back() == '/') {
|
||||
stream_id.pop_back();
|
||||
}
|
||||
_streamid = stream_id;
|
||||
}
|
||||
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(!enableVhost || _vhost.empty()){
|
||||
auto params = Parser::parseArgs(_param_strs);
|
||||
if (params.find(VHOST_KEY) != params.end()) {
|
||||
_vhost = params[VHOST_KEY];
|
||||
}
|
||||
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
if (!enableVhost || _vhost.empty()) {
|
||||
//如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
|
||||
_vhost = DEFAULT_VHOST;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
|
||||
|
||||
void MediaSourceEvent::onNoneReader(MediaSource &sender){
|
||||
GET_CONFIG(string, recordApp, Record::kAppName);
|
||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||
|
||||
//如果mp4点播, 无人观看时我们强制关闭点播
|
||||
bool is_mp4_vod = sender.getApp() == recordApp;
|
||||
|
||||
//没有任何人观看该视频源,表明该源可以关闭了
|
||||
weak_ptr<MediaSource> weakSender = sender.shared_from_this();
|
||||
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0, [weakSender,is_mp4_vod]() {
|
||||
auto strongSender = weakSender.lock();
|
||||
if (!strongSender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strongSender->totalReaderCount() != 0) {
|
||||
//还有人消费
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!is_mp4_vod){
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
WarnL << "无人观看事件:"
|
||||
<< strongSender->getSchema() << "/"
|
||||
<< strongSender->getVhost() << "/"
|
||||
<< strongSender->getApp() << "/"
|
||||
<< strongSender->getId();
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strongSender);
|
||||
}else{
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:"
|
||||
<< strongSender->getSchema() << "/"
|
||||
<< strongSender->getVhost() << "/"
|
||||
<< strongSender->getApp() << "/"
|
||||
<< strongSender->getId();
|
||||
strongSender->close(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &filePath , bool checkApp){
|
||||
MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &file_path , bool check_app){
|
||||
GET_CONFIG(string, appName, Record::kAppName);
|
||||
if (checkApp && app != appName) {
|
||||
if (check_app && app != appName) {
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef ENABLE_MP4
|
||||
try {
|
||||
MP4Reader::Ptr pReader(new MP4Reader(vhost, app, stream, filePath));
|
||||
MP4Reader::Ptr pReader(new MP4Reader(vhost, app, stream, file_path));
|
||||
pReader->startReadMP4();
|
||||
return MediaSource::find(schema, vhost, app, stream);
|
||||
} catch (std::exception &ex) {
|
||||
@ -494,6 +439,136 @@ MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &
|
||||
#endif //ENABLE_MP4
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
|
||||
|
||||
void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
|
||||
if (size || totalReaderCount(sender)) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return;
|
||||
}
|
||||
//没有任何人观看该视频源,表明该源可以关闭了
|
||||
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;
|
||||
weak_ptr<MediaSource> weak_sender = sender.shared_from_this();
|
||||
|
||||
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0, [weak_sender, is_mp4_vod]() {
|
||||
auto strong_sender = weak_sender.lock();
|
||||
if (!strong_sender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strong_sender->totalReaderCount()) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_mp4_vod) {
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
WarnL << "无人观看事件:"
|
||||
<< strong_sender->getSchema() << "/"
|
||||
<< strong_sender->getVhost() << "/"
|
||||
<< strong_sender->getApp() << "/"
|
||||
<< strong_sender->getId();
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
} else {
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:"
|
||||
<< strong_sender->getSchema() << "/"
|
||||
<< strong_sender->getVhost() << "/"
|
||||
<< strong_sender->getApp() << "/"
|
||||
<< strong_sender->getId();
|
||||
strong_sender->close(false);
|
||||
}
|
||||
return false;
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::seekTo(MediaSource &sender, uint32_t stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(sender, stamp);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::close(MediaSource &sender, bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->close(sender, force);
|
||||
}
|
||||
|
||||
int MediaSourceEventInterceptor::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return sender.readerCount();
|
||||
}
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::onReaderChanged(MediaSource &sender, int size) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
MediaSourceEvent::onReaderChanged(sender, size);
|
||||
} else {
|
||||
listener->onReaderChanged(sender, size);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::onRegist(MediaSource &sender, bool regist) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(sender, regist);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->setupRecord(sender, type, start, custom_path);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->isRecording(sender, type);
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MediaSourceEventInterceptor::getTracks(MediaSource &sender, bool trackReady) const {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
return listener->getTracks(sender, trackReady);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->startSendRtp(sender, dst_url, dst_port, ssrc, is_udp, cb);
|
||||
} else {
|
||||
MediaSourceEvent::startSendRtp(sender, dst_url, dst_port, ssrc, is_udp, cb);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::stopSendRtp(MediaSource &sender){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
return listener->stopSendRtp(sender);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////FlushPolicy//////////////////////////////////////
|
||||
|
||||
static bool isFlushAble_default(bool is_video, uint32_t last_stamp, uint32_t new_stamp, int cache_size) {
|
||||
if (new_stamp + 500 < last_stamp) {
|
||||
//时间戳回退比较大(可能seek中),由于rtp中时间戳是pts,是可能存在一定程度的回退的
|
||||
|
@ -44,33 +44,63 @@ public:
|
||||
virtual ~MediaSourceEvent(){};
|
||||
|
||||
// 通知拖动进度条
|
||||
virtual bool seekTo(MediaSource &sender,uint32_t ui32Stamp){ return false; }
|
||||
// 通知其停止推流
|
||||
virtual bool close(MediaSource &sender,bool force) { return false;}
|
||||
// 观看总人数
|
||||
virtual bool seekTo(MediaSource &sender, uint32_t stamp) { return false; }
|
||||
// 通知其停止产生流
|
||||
virtual bool close(MediaSource &sender, bool force) { return false; }
|
||||
// 获取观看总人数
|
||||
virtual int totalReaderCount(MediaSource &sender) = 0;
|
||||
// 通知观看人数变化
|
||||
virtual void onReaderChanged(MediaSource &sender, int size);
|
||||
//流注册或注销事件
|
||||
virtual void onRegist(MediaSource &sender, bool regist) {};
|
||||
|
||||
////////////////////////仅供MultiMediaSourceMuxer对象继承////////////////////////
|
||||
// 开启或关闭录制
|
||||
virtual bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) { return false; };
|
||||
// 获取录制状态
|
||||
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; };
|
||||
// 通知无人观看
|
||||
virtual void onNoneReader(MediaSource &sender);
|
||||
//流注册或注销事件
|
||||
virtual void onRegist(MediaSource &sender, bool regist) {};
|
||||
// 获取所有track相关信息
|
||||
virtual vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const { return vector<Track::Ptr>(); };
|
||||
// 开始发送ps-rtp
|
||||
virtual void startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb) { cb(SockException(Err_other, "not implemented"));};
|
||||
// 停止发送ps-rtp
|
||||
virtual bool stopSendRtp(MediaSource &sender) {return false; }
|
||||
|
||||
private:
|
||||
Timer::Ptr _async_close_timer;
|
||||
};
|
||||
|
||||
//该对象用于拦截感兴趣的MediaSourceEvent事件
|
||||
class MediaSourceEventInterceptor : public MediaSourceEvent{
|
||||
public:
|
||||
MediaSourceEventInterceptor(){}
|
||||
~MediaSourceEventInterceptor() override {}
|
||||
|
||||
bool seekTo(MediaSource &sender, uint32_t stamp) override;
|
||||
bool close(MediaSource &sender, bool force) override;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
void onReaderChanged(MediaSource &sender, int size) override;
|
||||
void onRegist(MediaSource &sender, bool regist) override;
|
||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) override;
|
||||
bool isRecording(MediaSource &sender, Recorder::type type) override;
|
||||
vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const override;
|
||||
void startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb) override;
|
||||
bool stopSendRtp(MediaSource &sender) override;
|
||||
|
||||
protected:
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析url获取媒体相关信息
|
||||
*/
|
||||
class MediaInfo{
|
||||
public:
|
||||
MediaInfo(){}
|
||||
~MediaInfo(){}
|
||||
MediaInfo(const string &url){ parse(url); }
|
||||
~MediaInfo() {}
|
||||
MediaInfo() {}
|
||||
MediaInfo(const string &url) { parse(url); }
|
||||
void parse(const string &url);
|
||||
|
||||
public:
|
||||
string _schema;
|
||||
string _host;
|
||||
@ -92,9 +122,11 @@ public:
|
||||
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap;
|
||||
typedef unordered_map<string, VhostAppStreamMap > SchemaVhostAppStreamMap;
|
||||
|
||||
MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) ;
|
||||
MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id) ;
|
||||
virtual ~MediaSource() ;
|
||||
|
||||
////////////////获取MediaSource相关信息////////////////
|
||||
|
||||
// 获取协议类型
|
||||
const string& getSchema() const;
|
||||
// 虚拟主机
|
||||
@ -104,13 +136,18 @@ public:
|
||||
// 流id
|
||||
const string& getId() const;
|
||||
|
||||
// 设置TrackSource
|
||||
void setTrackSource(const std::weak_ptr<TrackSource> &track_src);
|
||||
// 获取所有Track
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
vector<Track::Ptr> getTracks(bool ready = true) const override;
|
||||
|
||||
// 获取流当前时间戳
|
||||
virtual uint32_t getTimeStamp(TrackType type) { return 0; };
|
||||
// 设置时间戳
|
||||
virtual void setTimeStamp(uint32_t stamp) {};
|
||||
|
||||
////////////////MediaSourceEvent相关接口实现////////////////
|
||||
|
||||
// 设置监听者
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
// 获取监听者
|
||||
const std::weak_ptr<MediaSourceEvent>& getListener() const;
|
||||
|
||||
@ -119,48 +156,52 @@ public:
|
||||
// 观看者个数,包括(hls/rtsp/rtmp)
|
||||
virtual int totalReaderCount();
|
||||
|
||||
// 获取流当前时间戳
|
||||
virtual uint32_t getTimeStamp(TrackType trackType) { return 0; };
|
||||
// 设置时间戳
|
||||
virtual void setTimeStamp(uint32_t uiStamp) {};
|
||||
|
||||
// 拖动进度条
|
||||
bool seekTo(uint32_t ui32Stamp);
|
||||
bool seekTo(uint32_t stamp);
|
||||
// 关闭该流
|
||||
bool close(bool force);
|
||||
// 该流无人观看
|
||||
void onNoneReader();
|
||||
// 该流观看人数变化
|
||||
void onReaderChanged(int size);
|
||||
// 开启或关闭录制
|
||||
virtual bool setupRecord(Recorder::type type, bool start, const string &custom_path);
|
||||
bool setupRecord(Recorder::type type, bool start, const string &custom_path);
|
||||
// 获取录制状态
|
||||
virtual bool isRecording(Recorder::type type);
|
||||
bool isRecording(Recorder::type type);
|
||||
// 开始发送ps-rtp
|
||||
void startSendRtp(const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb);
|
||||
// 停止发送ps-rtp
|
||||
bool stopSendRtp();
|
||||
|
||||
////////////////static方法,查找或生成MediaSource////////////////
|
||||
|
||||
// 同步查找流
|
||||
static Ptr find(const string &schema, const string &vhost, const string &app, const string &id);
|
||||
|
||||
// 忽略类型,同步查找流,可能返回rtmp/rtsp/hls类型
|
||||
static Ptr find(const string &vhost, const string &app, const string &stream_id);
|
||||
|
||||
// 异步查找流
|
||||
static void findAsync(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, const function<void(const Ptr &src)> &cb);
|
||||
// 遍历所有流
|
||||
static void for_each_media(const function<void(const Ptr &src)> &cb);
|
||||
|
||||
// 从mp4文件生成MediaSource
|
||||
static MediaSource::Ptr createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &filePath = "", bool checkApp = true);
|
||||
static MediaSource::Ptr createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &file_path = "", bool check_app = true);
|
||||
|
||||
protected:
|
||||
void regist() ;
|
||||
bool unregist();
|
||||
//媒体注册
|
||||
void regist();
|
||||
|
||||
private:
|
||||
static Ptr find_l(const string &schema, const string &vhost, const string &app, const string &id, bool bMake);
|
||||
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry, const function<void(const MediaSource::Ptr &src)> &cb);
|
||||
//媒体注销
|
||||
bool unregist();
|
||||
//触发媒体事件
|
||||
void emitEvent(bool regist);
|
||||
|
||||
private:
|
||||
string _strSchema;
|
||||
string _strVhost;
|
||||
string _strApp;
|
||||
string _strId;
|
||||
string _schema;
|
||||
string _vhost;
|
||||
string _app;
|
||||
string _stream_id;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
weak_ptr<TrackSource> _track_source;
|
||||
static SchemaVhostAppStreamMap g_mapMediaSrc;
|
||||
static recursive_mutex g_mtxMediaSrc;
|
||||
};
|
||||
|
||||
///缓存刷新策略类
|
||||
@ -174,7 +215,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t getStamp(const RtmpPacket::Ptr &packet) {
|
||||
return packet->timeStamp;
|
||||
return packet->time_stamp;
|
||||
}
|
||||
|
||||
bool isFlushAble(bool is_video, bool is_key, uint32_t new_stamp, int cache_size);
|
||||
@ -208,6 +249,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clearCache() {
|
||||
_cache->clear();
|
||||
}
|
||||
|
||||
virtual void onFlush(std::shared_ptr<packet_list> &, bool key_pos) = 0;
|
||||
|
||||
private:
|
||||
@ -221,9 +266,9 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
bool _key_pos = false;
|
||||
policy _policy;
|
||||
std::shared_ptr<packet_list> _cache;
|
||||
bool _key_pos = false;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -8,36 +8,33 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "MultiMediaSourceMuxer.h"
|
||||
namespace mediakit {
|
||||
|
||||
///////////////////////////////MultiMuxerPrivate//////////////////////////////////
|
||||
|
||||
MultiMuxerPrivate::~MultiMuxerPrivate() {}
|
||||
MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4) {
|
||||
MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost, const string &app, const string &stream, float dur_sec,
|
||||
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4) {
|
||||
_stream_url = vhost + " " + app + " " + stream;
|
||||
if (enable_rtmp) {
|
||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
||||
_enable_rtxp = true;
|
||||
}
|
||||
if (enable_rtsp) {
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
||||
_enable_rtxp = true;
|
||||
}
|
||||
|
||||
if (enable_hls) {
|
||||
_hls = Recorder::createRecorder(Recorder::type_hls, vhost, app, stream);
|
||||
_enable_record = true;
|
||||
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream));
|
||||
}
|
||||
|
||||
if (enable_mp4) {
|
||||
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream);
|
||||
_enable_record = true;
|
||||
}
|
||||
|
||||
_ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream);
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream);
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::resetTracks() {
|
||||
@ -47,6 +44,12 @@ void MultiMuxerPrivate::resetTracks() {
|
||||
if (_rtsp) {
|
||||
_rtsp->resetTracks();
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->resetTracks();
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->resetTracks();
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
auto hls = _hls;
|
||||
@ -61,24 +64,32 @@ void MultiMuxerPrivate::resetTracks() {
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
_listener = listener;
|
||||
if (_rtmp) {
|
||||
_rtmp->setListener(listener);
|
||||
}
|
||||
|
||||
if (_rtsp) {
|
||||
_rtsp->setListener(listener);
|
||||
}
|
||||
|
||||
auto hls_src = getHlsMediaSource();
|
||||
if (hls_src) {
|
||||
hls_src->setListener(listener);
|
||||
if (_ts) {
|
||||
_ts->setListener(listener);
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->setListener(listener);
|
||||
}
|
||||
auto hls = _hls;
|
||||
if (hls) {
|
||||
hls->setListener(listener);
|
||||
}
|
||||
_meida_listener = listener;
|
||||
}
|
||||
|
||||
int MultiMuxerPrivate::totalReaderCount() const {
|
||||
auto hls_src = getHlsMediaSource();
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
||||
auto hls = _hls;
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) +
|
||||
(_rtmp ? _rtmp->readerCount() : 0) +
|
||||
(_ts ? _ts->readerCount() : 0) +
|
||||
(_fmp4 ? _fmp4->readerCount() : 0) +
|
||||
(hls ? hls->readerCount() : 0);
|
||||
}
|
||||
|
||||
static std::shared_ptr<MediaSinkInterface> makeRecorder(const vector<Track::Ptr> &tracks, Recorder::type type, const string &custom_path, MediaSource &sender){
|
||||
@ -95,18 +106,16 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
||||
case Recorder::type_hls : {
|
||||
if (start && !_hls) {
|
||||
//开始录制
|
||||
_hls = makeRecorder(getTracks(true), type, custom_path, sender);
|
||||
auto hls_src = getHlsMediaSource();
|
||||
if (hls_src) {
|
||||
auto hls = dynamic_pointer_cast<HlsRecorder>(makeRecorder(getTracks(true), type, custom_path, sender));
|
||||
if (hls) {
|
||||
//设置HlsMediaSource的事件监听器
|
||||
hls_src->setListener(_meida_listener);
|
||||
hls_src->setTrackSource(shared_from_this());
|
||||
hls->setListener(_listener);
|
||||
}
|
||||
_hls = hls;
|
||||
} else if (!start && _hls) {
|
||||
//停止录制
|
||||
_hls = nullptr;
|
||||
}
|
||||
_enable_record = _hls || _mp4;
|
||||
return true;
|
||||
}
|
||||
case Recorder::type_mp4 : {
|
||||
@ -117,7 +126,6 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
||||
//停止录制
|
||||
_mp4 = nullptr;
|
||||
}
|
||||
_enable_record = _hls || _mp4;
|
||||
return true;
|
||||
}
|
||||
default : return false;
|
||||
@ -146,7 +154,7 @@ void MultiMuxerPrivate::setTimeStamp(uint32_t stamp) {
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::setTrackListener(Listener *listener) {
|
||||
_listener = listener;
|
||||
_track_listener = listener;
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||
@ -156,6 +164,12 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||
if (_rtsp) {
|
||||
_rtsp->addTrack(track);
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->addTrack(track);
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->addTrack(track);
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
auto hls = _hls;
|
||||
@ -169,7 +183,12 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||
}
|
||||
|
||||
bool MultiMuxerPrivate::isEnabled(){
|
||||
return _enable_rtxp || _enable_record;
|
||||
auto hls = _hls;
|
||||
return (_rtmp ? _rtmp->isEnabled() : false) ||
|
||||
(_rtsp ? _rtsp->isEnabled() : false) ||
|
||||
(_ts ? _ts->isEnabled() : false) ||
|
||||
(_fmp4 ? _fmp4->isEnabled() : false) ||
|
||||
(hls ? hls->isEnabled() : false) || _mp4;
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||
@ -179,6 +198,13 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||
if (_rtsp) {
|
||||
_rtsp->inputFrame(frame);
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->inputFrame(frame);
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->inputFrame(frame);
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
|
||||
auto hls = _hls;
|
||||
@ -191,51 +217,70 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
static string getTrackInfoStr(const TrackSource *track_src){
|
||||
_StrPrinter codec_info;
|
||||
auto tracks = track_src->getTracks(true);
|
||||
for (auto &track : tracks) {
|
||||
auto codec_type = track->getTrackType();
|
||||
codec_info << track->getCodecName();
|
||||
switch (codec_type) {
|
||||
case TrackAudio : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
codec_info << "["
|
||||
<< audio_track->getAudioSampleRate() << "/"
|
||||
<< audio_track->getAudioChannel() << "/"
|
||||
<< audio_track->getAudioSampleBit() << "] ";
|
||||
break;
|
||||
}
|
||||
case TrackVideo : {
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
|
||||
codec_info << "["
|
||||
<< video_track->getVideoWidth() << "/"
|
||||
<< video_track->getVideoHeight() << "/"
|
||||
<< round(video_track->getVideoFps()) << "] ";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return codec_info;
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::onAllTrackReady() {
|
||||
if (_rtmp) {
|
||||
_rtmp->setTrackSource(shared_from_this());
|
||||
_rtmp->onAllTrackReady();
|
||||
}
|
||||
if (_rtsp) {
|
||||
_rtsp->setTrackSource(shared_from_this());
|
||||
_rtsp->onAllTrackReady();
|
||||
}
|
||||
|
||||
auto hls_src = getHlsMediaSource();
|
||||
if (hls_src) {
|
||||
hls_src->setTrackSource(shared_from_this());
|
||||
if (_fmp4) {
|
||||
_fmp4->onAllTrackReady();
|
||||
}
|
||||
|
||||
if (_listener) {
|
||||
_listener->onAllTrackReady();
|
||||
if (_track_listener) {
|
||||
_track_listener->onAllTrackReady();
|
||||
}
|
||||
InfoL << "stream: " << _stream_url << " , codec info: " << getTrackInfoStr(this);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MultiMuxerPrivate::getHlsMediaSource() const {
|
||||
auto recorder = dynamic_pointer_cast<HlsRecorder>(_hls);
|
||||
if (recorder) {
|
||||
return recorder->getMediaSource();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////MultiMediaSourceMuxer//////////////////////////////////
|
||||
|
||||
MultiMediaSourceMuxer::~MultiMediaSourceMuxer() {}
|
||||
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4) {
|
||||
|
||||
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec,
|
||||
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4) {
|
||||
_muxer.reset(new MultiMuxerPrivate(vhost, app, stream, dur_sec, enable_rtsp, enable_rtmp, enable_hls, enable_mp4));
|
||||
_muxer->setTrackListener(this);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
_listener = listener;
|
||||
//拦截事件
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<MultiMuxerPrivate::Listener> &listener) {
|
||||
_track_listener = listener;
|
||||
}
|
||||
|
||||
int MultiMediaSourceMuxer::totalReaderCount() const {
|
||||
@ -246,62 +291,57 @@ void MultiMediaSourceMuxer::setTimeStamp(uint32_t stamp) {
|
||||
_muxer->setTimeStamp(stamp);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::setTrackListener(Listener *listener) {
|
||||
_muxer->setTrackListener(listener);
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MultiMediaSourceMuxer::getTracks(bool trackReady) const {
|
||||
vector<Track::Ptr> MultiMediaSourceMuxer::getTracks(MediaSource &sender, bool trackReady) const {
|
||||
return _muxer->getTracks(trackReady);
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::seekTo(MediaSource &sender, uint32_t ui32Stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(sender, ui32Stamp);
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::close(MediaSource &sender, bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->close(sender, force);
|
||||
}
|
||||
|
||||
int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return _muxer->totalReaderCount();
|
||||
return totalReaderCount();
|
||||
}
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::onNoneReader(MediaSource &sender){
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
MediaSourceEvent::onNoneReader(sender);
|
||||
return;
|
||||
}
|
||||
listener->onNoneReader(sender);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::onRegist(MediaSource &sender, bool regist){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(sender, regist);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) {
|
||||
return _muxer->setupRecord(sender,type,start,custom_path);
|
||||
return _muxer->setupRecord(sender, type, start, custom_path);
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
return _muxer->isRecording(sender,type);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb){
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
auto ps_rtp_sender = std::make_shared<PSRtpSender>(ssrc);
|
||||
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
|
||||
ps_rtp_sender->startSend(dst_url, dst_port, is_udp, [weak_self, ps_rtp_sender, cb](const SockException &ex) {
|
||||
cb(ex);
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self || ex) {
|
||||
return;
|
||||
}
|
||||
for (auto &track : strong_self->_muxer->getTracks(false)) {
|
||||
ps_rtp_sender->addTrack(track);
|
||||
}
|
||||
ps_rtp_sender->addTrackCompleted();
|
||||
strong_self->_ps_rtp_sender = ps_rtp_sender;
|
||||
});
|
||||
#else
|
||||
cb(SockException(Err_other, "该功能未启用,编译时请打开ENABLE_RTPPROXY宏"));
|
||||
#endif//ENABLE_RTPPROXY
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::stopSendRtp(MediaSource &sender){
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
if (_ps_rtp_sender) {
|
||||
_ps_rtp_sender = nullptr;
|
||||
return true;
|
||||
}
|
||||
#endif//ENABLE_RTPPROXY
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::addTrack(const Track::Ptr &track) {
|
||||
_muxer->addTrack(track);
|
||||
}
|
||||
@ -310,6 +350,14 @@ void MultiMediaSourceMuxer::addTrackCompleted() {
|
||||
_muxer->addTrackCompleted();
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::onAllTrackReady(){
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
auto listener = _track_listener.lock();
|
||||
if(listener){
|
||||
listener->onAllTrackReady();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::resetTracks() {
|
||||
_muxer->resetTracks();
|
||||
}
|
||||
@ -361,27 +409,36 @@ public:
|
||||
return _frame->getCodecId();
|
||||
}
|
||||
private:
|
||||
Frame::Ptr _frame;
|
||||
int64_t _dts;
|
||||
int64_t _pts;
|
||||
Frame::Ptr _frame;
|
||||
};
|
||||
|
||||
void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(bool,modify_stamp,General::kModifyStamp);
|
||||
if(!modify_stamp){
|
||||
//未开启时间戳覆盖
|
||||
_muxer->inputFrame(frame);
|
||||
}else{
|
||||
void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame_in) {
|
||||
GET_CONFIG(bool, modify_stamp, General::kModifyStamp);
|
||||
auto frame = frame_in;
|
||||
if (modify_stamp) {
|
||||
//开启了时间戳覆盖
|
||||
FrameModifyStamp::Ptr new_frame = std::make_shared<FrameModifyStamp>(frame,_stamp[frame->getTrackType()]);
|
||||
//输入时间戳覆盖后的帧
|
||||
_muxer->inputFrame(new_frame);
|
||||
frame = std::make_shared<FrameModifyStamp>(frame, _stamp[frame->getTrackType()]);
|
||||
}
|
||||
_muxer->inputFrame(frame);
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
auto ps_rtp_sender = _ps_rtp_sender;
|
||||
if (ps_rtp_sender) {
|
||||
ps_rtp_sender->inputFrame(frame);
|
||||
}
|
||||
#endif //ENABLE_RTPPROXY
|
||||
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::isEnabled(){
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
return (_muxer->isEnabled() || _ps_rtp_sender);
|
||||
#else
|
||||
return _muxer->isEnabled();
|
||||
#endif //ENABLE_RTPPROXY
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
}//namespace mediakit
|
||||
|
@ -10,14 +10,20 @@
|
||||
|
||||
#ifndef ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
|
||||
#define ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
|
||||
|
||||
#include "Common/Stamp.h"
|
||||
#include "Rtp/PSRtpSender.h"
|
||||
#include "Record/Recorder.h"
|
||||
#include "Record/HlsRecorder.h"
|
||||
#include "Record/HlsMediaSource.h"
|
||||
#include "Rtsp/RtspMediaSourceMuxer.h"
|
||||
#include "Rtmp/RtmpMediaSourceMuxer.h"
|
||||
#include "Record/Recorder.h"
|
||||
#include "Record/HlsMediaSource.h"
|
||||
#include "Record/HlsRecorder.h"
|
||||
#include "TS/TSMediaSourceMuxer.h"
|
||||
#include "FMP4/FMP4MediaSourceMuxer.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class MultiMuxerPrivate : public MediaSink , public std::enable_shared_from_this<MultiMuxerPrivate>{
|
||||
class MultiMuxerPrivate : public MediaSink, public std::enable_shared_from_this<MultiMuxerPrivate>{
|
||||
public:
|
||||
friend class MultiMediaSourceMuxer;
|
||||
typedef std::shared_ptr<MultiMuxerPrivate> Ptr;
|
||||
@ -27,17 +33,12 @@ public:
|
||||
virtual ~Listener() = default;
|
||||
virtual void onAllTrackReady() = 0;
|
||||
};
|
||||
~MultiMuxerPrivate() override ;
|
||||
private:
|
||||
MultiMuxerPrivate(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4);
|
||||
|
||||
~MultiMuxerPrivate() override;
|
||||
|
||||
private:
|
||||
MultiMuxerPrivate(const string &vhost,const string &app, const string &stream,float dur_sec,
|
||||
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4);
|
||||
void resetTracks() override;
|
||||
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
int totalReaderCount() const;
|
||||
@ -46,83 +47,67 @@ private:
|
||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path);
|
||||
bool isRecording(MediaSource &sender, Recorder::type type);
|
||||
bool isEnabled();
|
||||
private:
|
||||
void onTrackReady(const Track::Ptr & track) override;
|
||||
void onTrackFrame(const Frame::Ptr &frame) override;
|
||||
void onAllTrackReady() override;
|
||||
MediaSource::Ptr getHlsMediaSource() const;
|
||||
|
||||
private:
|
||||
string _stream_url;
|
||||
Listener *_track_listener = nullptr;
|
||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||
MediaSinkInterface::Ptr _hls;
|
||||
HlsRecorder::Ptr _hls;
|
||||
MediaSinkInterface::Ptr _mp4;
|
||||
Listener *_listener = nullptr;
|
||||
std::weak_ptr<MediaSourceEvent> _meida_listener;
|
||||
bool _enable_rtxp = false;
|
||||
bool _enable_record = false;
|
||||
TSMediaSourceMuxer::Ptr _ts;
|
||||
FMP4MediaSourceMuxer::Ptr _fmp4;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
};
|
||||
|
||||
class MultiMediaSourceMuxer : public MediaSourceEvent, public MediaSinkInterface, public TrackSource, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
||||
class MultiMediaSourceMuxer : public MediaSourceEventInterceptor, public MediaSinkInterface, public MultiMuxerPrivate::Listener, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
||||
public:
|
||||
typedef MultiMuxerPrivate::Listener Listener;
|
||||
typedef std::shared_ptr<MultiMediaSourceMuxer> Ptr;
|
||||
|
||||
~MultiMediaSourceMuxer() override;
|
||||
MultiMediaSourceMuxer(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec = 0.0,
|
||||
bool enable_rtsp = true,
|
||||
bool enable_rtmp = true,
|
||||
bool enable_hls = true,
|
||||
bool enable_mp4 = false);
|
||||
MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec = 0.0,
|
||||
bool enable_rtsp = true, bool enable_rtmp = true, bool enable_hls = true, bool enable_mp4 = false);
|
||||
|
||||
/**
|
||||
* 设置事件监听器
|
||||
* @param listener
|
||||
* @param listener 监听器
|
||||
*/
|
||||
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
|
||||
/**
|
||||
* 随着Track就绪事件监听器
|
||||
* @param listener 事件监听器
|
||||
*/
|
||||
void setTrackListener(const std::weak_ptr<MultiMuxerPrivate::Listener> &listener);
|
||||
|
||||
/**
|
||||
* 返回总的消费者个数
|
||||
* @return
|
||||
*/
|
||||
int totalReaderCount() const;
|
||||
|
||||
/**
|
||||
* 判断是否生效(是否正在转其他协议)
|
||||
*/
|
||||
bool isEnabled();
|
||||
|
||||
/**
|
||||
* 设置MediaSource时间戳
|
||||
* @param stamp 时间戳
|
||||
*/
|
||||
void setTimeStamp(uint32_t stamp);
|
||||
|
||||
/**
|
||||
* 随着Track就绪事件监听器
|
||||
* @param listener 事件监听器
|
||||
*/
|
||||
void setTrackListener(Listener *listener);
|
||||
/////////////////////////////////MediaSourceEvent override/////////////////////////////////
|
||||
|
||||
/**
|
||||
* 获取所有Track
|
||||
* @param trackReady 是否筛选过滤未就绪的track
|
||||
* @return 所有Track
|
||||
*/
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
|
||||
/**
|
||||
* 通知拖动进度条
|
||||
* @param sender 事件发送者
|
||||
* @param ui32Stamp 目标时间戳
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||
|
||||
/**
|
||||
* 通知停止流生成
|
||||
* @param sender 事件发送者
|
||||
* @param force 是否强制关闭
|
||||
* @return 成功与否
|
||||
*/
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const override;
|
||||
|
||||
/**
|
||||
* 观看总人数
|
||||
@ -131,19 +116,6 @@ public:
|
||||
*/
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
|
||||
/**
|
||||
* 触发无人观看事件
|
||||
* @param sender 触发者
|
||||
*/
|
||||
void onNoneReader(MediaSource &sender) override;
|
||||
|
||||
/**
|
||||
* 媒体注册注销事件
|
||||
* @param sender 触发者
|
||||
* @param regist 是否为注册事件
|
||||
*/
|
||||
void onRegist(MediaSource &sender, bool regist) override;
|
||||
|
||||
/**
|
||||
* 设置录制状态
|
||||
* @param type 录制类型
|
||||
@ -160,18 +132,35 @@ public:
|
||||
*/
|
||||
bool isRecording(MediaSource &sender, Recorder::type type) override;
|
||||
|
||||
/**
|
||||
* 开始发送ps-rtp流
|
||||
* @param dst_url 目标ip或域名
|
||||
* @param dst_port 目标端口
|
||||
* @param ssrc rtp的ssrc
|
||||
* @param is_udp 是否为udp
|
||||
* @param cb 启动成功或失败回调
|
||||
*/
|
||||
void startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb) override;
|
||||
|
||||
/**
|
||||
* 停止ps-rtp发送
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool stopSendRtp(MediaSource &sender) override;
|
||||
|
||||
/////////////////////////////////MediaSinkInterface override/////////////////////////////////
|
||||
|
||||
/**
|
||||
* 添加track,内部会调用Track的clone方法
|
||||
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
|
||||
* @param track
|
||||
* @param track 添加音频或视频轨道
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
void addTrack(const Track::Ptr &track) override;
|
||||
|
||||
/**
|
||||
* 添加track完毕
|
||||
* @param track
|
||||
*/
|
||||
void addTrackCompleted();
|
||||
void addTrackCompleted() override;
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
@ -184,14 +173,20 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/////////////////////////////////MultiMuxerPrivate::Listener override/////////////////////////////////
|
||||
|
||||
/**
|
||||
* 判断是否生效(是否正在转其他协议)
|
||||
* 所有track全部就绪
|
||||
*/
|
||||
bool isEnabled();
|
||||
void onAllTrackReady() override;
|
||||
|
||||
private:
|
||||
MultiMuxerPrivate::Ptr _muxer;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
Stamp _stamp[2];
|
||||
MultiMuxerPrivate::Ptr _muxer;
|
||||
std::weak_ptr<MultiMuxerPrivate::Listener> _track_listener;
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
PSRtpSender::Ptr _ps_rtp_sender;
|
||||
#endif //ENABLE_RTPPROXY
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -44,77 +44,79 @@ void Stamp::setPlayBack(bool playback) {
|
||||
|
||||
void Stamp::syncTo(Stamp &other){
|
||||
_sync_master = &other;
|
||||
_sync_finished = false;
|
||||
}
|
||||
|
||||
//限制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(_sync_finished || modifyStamp || _playback){
|
||||
//自动生成时间戳或回放或同步完毕
|
||||
if(dts_out < 0) { dts_out = 0; }
|
||||
if(pts_out < 0) { pts_out = 0; }
|
||||
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
if (_playback) {
|
||||
//回放允许时间戳回退
|
||||
return;
|
||||
}
|
||||
|
||||
if(_sync_master && _sync_master->_last_dts){
|
||||
if (dts_out < _last_dts_out) {
|
||||
WarnL << "dts回退:" << dts_out << " < " << _last_dts_out;
|
||||
dts_out = _last_dts_out;
|
||||
pts_out = _last_pts_out;
|
||||
return;
|
||||
}
|
||||
_last_dts_out = dts_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) {
|
||||
revise_l2(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
if (!_sync_master || modifyStamp || _playback) {
|
||||
//自动生成时间戳或回放或同步完毕
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sync_master && _sync_master->_last_dts_in) {
|
||||
//音视频dts当前时间差
|
||||
int64_t dts_diff = _last_dts - _sync_master->_last_dts;
|
||||
if(ABS(dts_diff) < 5000){
|
||||
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||
if (ABS(dts_diff) < 5000) {
|
||||
//如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||
_last_relativeStamp = _relativeStamp;
|
||||
_relativeStamp = _sync_master->_relativeStamp + dts_diff;
|
||||
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||
}
|
||||
//下次不用再强制同步
|
||||
_sync_master = nullptr;
|
||||
}
|
||||
|
||||
if (dts_out < 0 || dts_out < _last_relativeStamp) {
|
||||
//相对时间戳小于0,或者小于上次的时间戳,
|
||||
//那么说明是同步时间戳导致的,在这个过渡期内,我们一直返回上次的结果(目的是为了防止时间戳回退)
|
||||
pts_out = _last_relativeStamp + (pts_out - dts_out);
|
||||
dts_out = _last_relativeStamp;
|
||||
} else if(!_sync_master){
|
||||
//音视频同步过渡期完毕
|
||||
_sync_finished = true;
|
||||
}
|
||||
|
||||
if(pts_out < 0){
|
||||
pts_out = dts_out;
|
||||
}
|
||||
}
|
||||
|
||||
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
if(!pts){
|
||||
//求取相对时间戳
|
||||
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){
|
||||
if (_playback) {
|
||||
//这是点播
|
||||
dts_out = dts;
|
||||
pts_out = pts;
|
||||
_relativeStamp = dts_out;
|
||||
_last_dts = dts;
|
||||
_relative_stamp = dts_out;
|
||||
_last_dts_in = dts;
|
||||
return;
|
||||
}
|
||||
|
||||
//pts和dts的差值
|
||||
int pts_dts_diff = pts - dts;
|
||||
|
||||
if(_last_dts != dts){
|
||||
if (_last_dts_in != dts) {
|
||||
//时间戳发生变更
|
||||
if(modifyStamp){
|
||||
if (modifyStamp) {
|
||||
//内部自己生产时间戳
|
||||
_relativeStamp = _ticker.elapsedTime();
|
||||
}else{
|
||||
_relativeStamp += deltaStamp(dts);
|
||||
_relative_stamp = _ticker.elapsedTime();
|
||||
} else {
|
||||
_relative_stamp += deltaStamp(dts);
|
||||
}
|
||||
_last_dts = dts;
|
||||
_last_dts_in = dts;
|
||||
}
|
||||
dts_out = _relativeStamp;
|
||||
dts_out = _relative_stamp;
|
||||
|
||||
//////////////以下是播放时间戳的计算//////////////////
|
||||
if(ABS(pts_dts_diff) > MAX_CTS){
|
||||
if (ABS(pts_dts_diff) > MAX_CTS) {
|
||||
//如果差值太大,则认为由于回环导致时间戳错乱了
|
||||
pts_dts_diff = 0;
|
||||
}
|
||||
@ -123,11 +125,11 @@ void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_ou
|
||||
}
|
||||
|
||||
void Stamp::setRelativeStamp(int64_t relativeStamp) {
|
||||
_relativeStamp = relativeStamp;
|
||||
_relative_stamp = relativeStamp;
|
||||
}
|
||||
|
||||
int64_t Stamp::getRelativeStamp() const {
|
||||
return _relativeStamp;
|
||||
return _relative_stamp;
|
||||
}
|
||||
|
||||
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
||||
|
@ -29,6 +29,7 @@ public:
|
||||
* @return 时间戳增量
|
||||
*/
|
||||
int64_t deltaStamp(int64_t stamp);
|
||||
|
||||
private:
|
||||
int64_t _last_stamp = 0;
|
||||
};
|
||||
@ -41,7 +42,7 @@ public:
|
||||
~Stamp() = default;
|
||||
|
||||
/**
|
||||
* 修正时间戳
|
||||
* 求取相对时间戳,同时实现了音视频同步、限制dts回退等功能
|
||||
* @param dts 输入dts,如果为0则根据系统时间戳生成
|
||||
* @param pts 输入pts,如果为0则等于dts
|
||||
* @param dts_out 输出dts
|
||||
@ -75,15 +76,20 @@ public:
|
||||
void syncTo(Stamp &other);
|
||||
|
||||
private:
|
||||
//主要实现音视频时间戳同步功能
|
||||
void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
||||
|
||||
//主要实现获取相对时间戳功能
|
||||
void revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
||||
|
||||
private:
|
||||
int64_t _relativeStamp = 0;
|
||||
int64_t _last_relativeStamp = 0;
|
||||
int64_t _last_dts = 0;
|
||||
int64_t _relative_stamp = 0;
|
||||
int64_t _last_dts_in = 0;
|
||||
int64_t _last_dts_out = 0;
|
||||
int64_t _last_pts_out = 0;
|
||||
SmoothTicker _ticker;
|
||||
bool _playback = false;
|
||||
Stamp *_sync_master = nullptr;
|
||||
bool _sync_finished = true;
|
||||
};
|
||||
|
||||
//dts生成器,
|
||||
@ -93,8 +99,10 @@ public:
|
||||
DtsGenerator() = default;
|
||||
~DtsGenerator() = default;
|
||||
bool getDts(uint32_t pts, uint32_t &dts);
|
||||
|
||||
private:
|
||||
bool getDts_l(uint32_t pts, uint32_t &dts);
|
||||
|
||||
private:
|
||||
uint32_t _dts_pts_offset = 0;
|
||||
uint32_t _last_dts = 0;
|
||||
|
@ -40,6 +40,7 @@ bool loadIniConfig(const char *ini_path){
|
||||
namespace Broadcast {
|
||||
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
||||
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
||||
const string kBroadcastRecordTs = "kBroadcastRecoredTs";
|
||||
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
||||
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
||||
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
|
||||
@ -63,7 +64,6 @@ const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
|
||||
const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
||||
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
||||
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
||||
const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
|
||||
const string kPublishToHls = GENERAL_FIELD"publishToHls";
|
||||
const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
|
||||
const string kMergeWriteMS = GENERAL_FIELD"mergeWriteMS";
|
||||
@ -76,7 +76,6 @@ onceToken token([](){
|
||||
mINI::Instance()[kEnableVhost] = 0;
|
||||
mINI::Instance()[kAddMuteAudio] = 1;
|
||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||
mINI::Instance()[kPublishToRtxp] = 1;
|
||||
mINI::Instance()[kPublishToHls] = 1;
|
||||
mINI::Instance()[kPublishToMP4] = 0;
|
||||
mINI::Instance()[kMergeWriteMS] = 0;
|
||||
@ -253,6 +252,8 @@ const string kSegmentRetain = HLS_FIELD"segRetain";
|
||||
const string kFileBufSize = HLS_FIELD"fileBufSize";
|
||||
//录制文件路径
|
||||
const string kFilePath = HLS_FIELD"filePath";
|
||||
// 是否广播 ts 切片完成通知
|
||||
const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kSegmentDuration] = 2;
|
||||
@ -260,6 +261,7 @@ onceToken token([](){
|
||||
mINI::Instance()[kSegmentRetain] = 5;
|
||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||
mINI::Instance()[kFilePath] = "./www";
|
||||
mINI::Instance()[kBroadcastRecordTs] = false;
|
||||
},nullptr);
|
||||
} //namespace Hls
|
||||
|
||||
|
@ -47,6 +47,8 @@ bool loadIniConfig(const char *ini_path = nullptr);
|
||||
#define RTSP_SCHEMA "rtsp"
|
||||
#define RTMP_SCHEMA "rtmp"
|
||||
#define HLS_SCHEMA "hls"
|
||||
#define TS_SCHEMA "ts"
|
||||
#define FMP4_SCHEMA "fmp4"
|
||||
#define DEFAULT_VHOST "__defaultVhost__"
|
||||
|
||||
////////////广播名称///////////
|
||||
@ -58,7 +60,11 @@ extern const string kBroadcastMediaChanged;
|
||||
|
||||
//录制mp4文件成功后广播
|
||||
extern const string kBroadcastRecordMP4;
|
||||
#define BroadcastRecordMP4Args const MP4Info &info
|
||||
#define BroadcastRecordMP4Args const RecordInfo &info
|
||||
|
||||
// 录制 ts 文件后广播
|
||||
extern const string kBroadcastRecordTs;
|
||||
#define BroadcastRecordTsArgs const RecordInfo &info
|
||||
|
||||
//收到http api请求广播
|
||||
extern const string kBroadcastHttpRequest;
|
||||
@ -86,8 +92,7 @@ extern const string kBroadcastOnRtspAuth;
|
||||
//如果errMessage为空则代表鉴权成功
|
||||
//enableHls: 是否允许转换hls
|
||||
//enableMP4: 是否运行MP4录制
|
||||
//enableRtxp: rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp
|
||||
typedef std::function<void(const string &errMessage,bool enableRtxp,bool enableHls,bool enableMP4)> PublishAuthInvoker;
|
||||
typedef std::function<void(const string &errMessage, bool enableHls, bool enableMP4)> PublishAuthInvoker;
|
||||
|
||||
//收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
||||
extern const string kBroadcastMediaPublish;
|
||||
@ -165,8 +170,6 @@ extern const string kAddMuteAudio;
|
||||
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
extern const string kResetWhenRePlay;
|
||||
//是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToRtxp ;
|
||||
//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToHls ;
|
||||
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||
@ -284,6 +287,8 @@ extern const string kSegmentRetain;
|
||||
extern const string kFileBufSize;
|
||||
//录制文件路径
|
||||
extern const string kFilePath;
|
||||
// 是否广播 ts 切片完成通知
|
||||
extern const string kBroadcastRecordTs;
|
||||
} //namespace Hls
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
|
@ -92,6 +92,16 @@ static void parseAacConfig(const string &config, AdtsHeader &adts) {
|
||||
adts.no_raw_data_blocks_in_frame = 0;
|
||||
}
|
||||
|
||||
int getAacFrameLength(const uint8_t *data, int bytes) {
|
||||
uint16_t len;
|
||||
if (bytes < 7) return -1;
|
||||
if (0xFF != data[0] || 0xF0 != (data[1] & 0xF0)) {
|
||||
return -1;
|
||||
}
|
||||
len = ((uint16_t) (data[3] & 0x03) << 11) | ((uint16_t) data[4] << 3) | ((uint16_t) (data[5] >> 5) & 0x07);
|
||||
return len;
|
||||
}
|
||||
|
||||
string makeAacConfig(const uint8_t *hex, int length){
|
||||
#ifndef ENABLE_MP4
|
||||
if (!(hex[0] == 0xFF && (hex[1] & 0xF0) == 0xF0)) {
|
||||
@ -134,7 +144,7 @@ int dumpAacConfig(const string &config, int length, uint8_t *out, int out_size)
|
||||
#ifndef ENABLE_MP4
|
||||
AdtsHeader header;
|
||||
parseAacConfig(config, header);
|
||||
header.aac_frame_length = length;
|
||||
header.aac_frame_length = ADTS_HEADER_LEN + length;
|
||||
dumpAdtsHeader(header, out);
|
||||
return ADTS_HEADER_LEN;
|
||||
#else
|
||||
|
@ -18,44 +18,10 @@
|
||||
namespace mediakit{
|
||||
|
||||
string makeAacConfig(const uint8_t *hex, int length);
|
||||
int getAacFrameLength(const uint8_t *hex, int length);
|
||||
int dumpAacConfig(const string &config, int length, uint8_t *out, int out_size);
|
||||
bool parseAacConfig(const string &config, int &samplerate, int &channels);
|
||||
|
||||
/**
|
||||
* aac帧,包含adts头
|
||||
*/
|
||||
class AACFrame : public FrameImp {
|
||||
public:
|
||||
typedef std::shared_ptr<AACFrame> Ptr;
|
||||
AACFrame(){
|
||||
_codecid = CodecAAC;
|
||||
}
|
||||
};
|
||||
|
||||
class AACFrameNoCacheAble : public FrameFromPtr {
|
||||
public:
|
||||
typedef std::shared_ptr<AACFrameNoCacheAble> Ptr;
|
||||
|
||||
AACFrameNoCacheAble(char *ptr,uint32_t size,uint32_t dts,uint32_t pts = 0,int prefix_size = ADTS_HEADER_LEN){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecAAC;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* aac音频通道
|
||||
*/
|
||||
@ -135,6 +101,28 @@ public:
|
||||
* @param frame 数据帧
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
if (frame->prefixSize()) {
|
||||
//有adts头,尝试分帧
|
||||
auto ptr = frame->data();
|
||||
auto end = frame->data() + frame->size();
|
||||
while (ptr < end) {
|
||||
auto frame_len = getAacFrameLength((uint8_t *) ptr, end - ptr);
|
||||
if (frame_len < ADTS_HEADER_LEN) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto sub_frame = std::make_shared<FrameInternal<FrameFromPtr> >(frame, (char *) ptr, frame_len, ADTS_HEADER_LEN);
|
||||
ptr += frame_len;
|
||||
sub_frame->setCodecId(CodecAAC);
|
||||
inputFrame_l(sub_frame);
|
||||
}
|
||||
} else {
|
||||
inputFrame_l(frame);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void inputFrame_l(const Frame::Ptr &frame) {
|
||||
if (_cfg.empty()) {
|
||||
//未获取到aac_cfg信息
|
||||
if (frame->prefixSize()) {
|
||||
@ -151,7 +139,6 @@ public:
|
||||
AudioTrack::inputFrame(frame);
|
||||
}
|
||||
}
|
||||
private:
|
||||
/**
|
||||
* 解析2个字节的aac配置
|
||||
*/
|
||||
|
@ -21,29 +21,30 @@ static string getAacCfg(const RtmpPacket &thiz) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.strBuf.size() < 4) {
|
||||
if (thiz.buffer.size() < 4) {
|
||||
WarnL << "bad aac cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret = thiz.strBuf.substr(2);
|
||||
ret = thiz.buffer.substr(2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool) {
|
||||
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
_aac_cfg = getAacCfg(*pkt);
|
||||
onGetAAC(nullptr, 0, 0);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_aac_cfg.empty()) {
|
||||
onGetAAC(pkt->strBuf.data() + 2, pkt->strBuf.size() - 2, pkt->timeStamp);
|
||||
onGetAAC(pkt->buffer.data() + 2, pkt->buffer.size() - 2, pkt->time_stamp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AACRtmpDecoder::onGetAAC(const char* data, int len, uint32_t stamp) {
|
||||
auto frame = ResourcePoolHelper<AACFrame>::obtainObj();
|
||||
auto frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
frame->_codec_id = CodecAAC;
|
||||
|
||||
//生成adts头
|
||||
char adts_header[32] = {0};
|
||||
auto size = dumpAacConfig(_aac_cfg, len, (uint8_t *) adts_header, sizeof(adts_header));
|
||||
@ -95,43 +96,43 @@ void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
if(!_aac_cfg.empty()){
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
uint8_t is_config = false;
|
||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
|
||||
//aac data
|
||||
rtmpPkt->strBuf.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = frame->dts();
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = frame->dts();
|
||||
rtmpPkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
}
|
||||
}
|
||||
|
||||
void AACRtmpEncoder::makeAudioConfigPkt() {
|
||||
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
uint8_t is_config = true;
|
||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//aac config
|
||||
rtmpPkt->strBuf.append(_aac_cfg);
|
||||
rtmpPkt->buffer.append(_aac_cfg);
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = 0;
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -19,7 +19,7 @@ namespace mediakit{
|
||||
/**
|
||||
* aac Rtmp转adts类
|
||||
*/
|
||||
class AACRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<AACFrame> {
|
||||
class AACRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr<AACRtmpDecoder> Ptr;
|
||||
|
||||
@ -28,10 +28,9 @@ public:
|
||||
|
||||
/**
|
||||
* 输入Rtmp并解码
|
||||
* @param Rtmp Rtmp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
* @param rtmp Rtmp数据包
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &Rtmp, bool key_pos = false) override;
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecAAC;
|
||||
|
@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
#include "AACRtp.h"
|
||||
#define AAC_MAX_FRAME_SIZE (2 * 1024)
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
@ -68,60 +67,73 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) {
|
||||
} else {
|
||||
_aac_cfg = aacTrack->getAacCfg();
|
||||
}
|
||||
_frame = obtainFrame();
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
AACRtpDecoder::AACRtpDecoder() {
|
||||
_frame = obtainFrame();
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
AACFrame::Ptr AACRtpDecoder::obtainFrame() {
|
||||
void AACRtpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<AACFrame>::obtainObj();
|
||||
frame->_prefix_size = 0;
|
||||
frame->_buffer.clear();
|
||||
return frame;
|
||||
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
_frame->_prefix_size = 0;
|
||||
_frame->_buffer.clear();
|
||||
_frame->_codec_id = CodecAAC;
|
||||
}
|
||||
|
||||
bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) {
|
||||
//rtp数据开始部分
|
||||
uint8_t *ptr = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||
//rtp数据末尾
|
||||
const uint8_t *end = (uint8_t *) rtppack->data() + rtppack->size();
|
||||
|
||||
uint8_t *end = (uint8_t *) rtppack->data() + rtppack->size();
|
||||
//首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数
|
||||
const uint16_t au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
||||
//忽略Au-Header区
|
||||
ptr += 2 + au_header_count * 2;
|
||||
uint16_t au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
||||
//记录au_header起始指针
|
||||
uint8_t *au_header_ptr = ptr + 2;
|
||||
ptr = au_header_ptr + au_header_count * 2;
|
||||
|
||||
while (ptr < end) {
|
||||
auto size = (uint32_t) (end - ptr);
|
||||
if (size > AAC_MAX_FRAME_SIZE) {
|
||||
size = AAC_MAX_FRAME_SIZE;
|
||||
if (end < ptr) {
|
||||
//数据不够
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_last_dts) {
|
||||
//记录第一个时间戳
|
||||
_last_dts = rtppack->timeStamp;
|
||||
}
|
||||
|
||||
//每个audio unit时间戳增量
|
||||
auto dts_inc = (rtppack->timeStamp - _last_dts) / au_header_count;
|
||||
if (dts_inc < 0 && dts_inc > 100) {
|
||||
//时间戳增量异常,忽略
|
||||
dts_inc = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < au_header_count; ++i) {
|
||||
// 之后的2字节是AU_HEADER,其中高13位表示一帧AAC负载的字节长度,低3位无用
|
||||
uint16_t size = ((au_header_ptr[0] << 8) | au_header_ptr[1]) >> 3;
|
||||
if (ptr + size > end) {
|
||||
//数据不够
|
||||
break;
|
||||
}
|
||||
if (_frame->size() + size > AAC_MAX_FRAME_SIZE) {
|
||||
//数据太多了,先清空
|
||||
|
||||
if (size) {
|
||||
//设置aac数据
|
||||
_frame->_buffer.assign((char *) ptr, size);
|
||||
//设置当前audio unit时间戳
|
||||
_frame->_dts = _last_dts + i * dts_inc;
|
||||
ptr += size;
|
||||
au_header_ptr += 2;
|
||||
flushData();
|
||||
}
|
||||
//追加aac数据
|
||||
_frame->_buffer.append((char *) ptr, size);
|
||||
_frame->_dts = rtppack->timeStamp;
|
||||
ptr += size;
|
||||
}
|
||||
|
||||
if (rtppack->mark) {
|
||||
//最后一个rtp分片
|
||||
flushData();
|
||||
}
|
||||
//记录上次时间戳
|
||||
_last_dts = rtppack->timeStamp;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AACRtpDecoder::flushData() {
|
||||
if (_frame->_buffer.empty()) {
|
||||
//没有有效数据
|
||||
return;
|
||||
}
|
||||
|
||||
//插入adts头
|
||||
char adts_header[32] = {0};
|
||||
auto size = dumpAacConfig(_aac_cfg, _frame->_buffer.size(), (uint8_t *) adts_header, sizeof(adts_header));
|
||||
@ -131,11 +143,7 @@ void AACRtpDecoder::flushData() {
|
||||
_frame->_prefix_size = size;
|
||||
}
|
||||
RtpCodec::inputFrame(_frame);
|
||||
_frame = obtainFrame();
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
|
||||
|
||||
}//namespace mediakit
|
@ -17,7 +17,7 @@ namespace mediakit{
|
||||
/**
|
||||
* aac rtp转adts类
|
||||
*/
|
||||
class AACRtpDecoder : public RtpCodec , public ResourcePoolHelper<AACFrame> {
|
||||
class AACRtpDecoder : public RtpCodec , public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr<AACRtpDecoder> Ptr;
|
||||
|
||||
@ -39,12 +39,13 @@ protected:
|
||||
AACRtpDecoder();
|
||||
|
||||
private:
|
||||
AACFrame::Ptr obtainFrame();
|
||||
void obtainFrame();
|
||||
void flushData();
|
||||
|
||||
private:
|
||||
AACFrame::Ptr _frame;
|
||||
FrameImp::Ptr _frame;
|
||||
string _aac_cfg;
|
||||
uint32_t _last_dts = 0;
|
||||
};
|
||||
|
||||
|
||||
|
66
src/Extension/CommonRtmp.cpp
Normal file
66
src/Extension/CommonRtmp.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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 "CommonRtmp.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
CommonRtmpDecoder::CommonRtmpDecoder(CodecId codec) {
|
||||
_codec = codec;
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
CodecId CommonRtmpDecoder::getCodecId() const {
|
||||
return _codec;
|
||||
}
|
||||
|
||||
void CommonRtmpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
_frame->_buffer.clear();
|
||||
_frame->_codec_id = _codec;
|
||||
_frame->_prefix_size = 0;
|
||||
}
|
||||
|
||||
void CommonRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp) {
|
||||
//拷贝负载
|
||||
_frame->_buffer.assign(rtmp->buffer.data() + 1, rtmp->buffer.size() - 1);
|
||||
_frame->_dts = rtmp->time_stamp;
|
||||
//写入环形缓存
|
||||
RtmpCodec::inputFrame(_frame);
|
||||
//创建下一帧
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CommonRtmpEncoder::CommonRtmpEncoder(const Track::Ptr &track) : CommonRtmpDecoder(track->getCodecId()) {
|
||||
_audio_flv_flags = getAudioRtmpFlags(track);
|
||||
}
|
||||
|
||||
void CommonRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
if (!_audio_flv_flags) {
|
||||
return;
|
||||
}
|
||||
RtmpPacket::Ptr rtmp = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmp->buffer.clear();
|
||||
//header
|
||||
rtmp->buffer.push_back(_audio_flv_flags);
|
||||
//data
|
||||
rtmp->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
rtmp->body_size = rtmp->buffer.size();
|
||||
rtmp->chunk_id = CHUNK_AUDIO;
|
||||
rtmp->stream_index = STREAM_MEDIA;
|
||||
rtmp->time_stamp = frame->dts();
|
||||
rtmp->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmp);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
73
src/Extension/CommonRtmp.h
Normal file
73
src/Extension/CommonRtmp.h
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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_COMMONRTMP_H
|
||||
#define ZLMEDIAKIT_COMMONRTMP_H
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Rtmp/RtmpCodec.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* 通用 rtmp解码类
|
||||
*/
|
||||
class CommonRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr<CommonRtmpDecoder> Ptr;
|
||||
|
||||
~CommonRtmpDecoder() override {}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param codec 编码id
|
||||
*/
|
||||
CommonRtmpDecoder(CodecId codec);
|
||||
|
||||
/**
|
||||
* 返回编码类型ID
|
||||
*/
|
||||
CodecId getCodecId() const override;
|
||||
|
||||
/**
|
||||
* 输入Rtmp并解码
|
||||
* @param rtmp Rtmp数据包
|
||||
*/
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
private:
|
||||
void obtainFrame();
|
||||
|
||||
private:
|
||||
CodecId _codec;
|
||||
FrameImp::Ptr _frame;
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用 rtmp编码类
|
||||
*/
|
||||
class CommonRtmpEncoder : public CommonRtmpDecoder , public ResourcePoolHelper<RtmpPacket> {
|
||||
public:
|
||||
typedef std::shared_ptr<CommonRtmpEncoder> Ptr;
|
||||
|
||||
CommonRtmpEncoder(const Track::Ptr &track);
|
||||
~CommonRtmpEncoder() override{}
|
||||
|
||||
/**
|
||||
* 输入帧数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
private:
|
||||
uint8_t _audio_flv_flags = 0;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_COMMONRTMP_H
|
86
src/Extension/CommonRtp.cpp
Normal file
86
src/Extension/CommonRtp.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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 "CommonRtp.h"
|
||||
|
||||
CommonRtpDecoder::CommonRtpDecoder(CodecId codec, int max_frame_size ){
|
||||
_codec = codec;
|
||||
_max_frame_size = max_frame_size;
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
CodecId CommonRtpDecoder::getCodecId() const {
|
||||
return _codec;
|
||||
}
|
||||
|
||||
void CommonRtpDecoder::obtainFrame() {
|
||||
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
_frame->_buffer.clear();
|
||||
_frame->_prefix_size = 0;
|
||||
_frame->_dts = 0;
|
||||
_frame->_codec_id = _codec;
|
||||
}
|
||||
|
||||
bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
|
||||
auto payload = rtp->data() + rtp->offset;
|
||||
auto size = rtp->size() - rtp->offset;
|
||||
if (size <= 0) {
|
||||
//无实际负载
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_frame->_dts != rtp->timeStamp || _frame->_buffer.size() > _max_frame_size) {
|
||||
//时间戳发生变化或者缓存超过MAX_FRAME_SIZE,则清空上帧数据
|
||||
if (!_frame->_buffer.empty()) {
|
||||
//有有效帧,则输出
|
||||
RtpCodec::inputFrame(_frame);
|
||||
}
|
||||
|
||||
//新的一帧数据
|
||||
obtainFrame();
|
||||
_frame->_dts = rtp->timeStamp;
|
||||
_drop_flag = false;
|
||||
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != rtp->sequence) {
|
||||
//时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃
|
||||
WarnL << "rtp丢包:" << _last_seq << " -> " << rtp->sequence;
|
||||
_drop_flag = true;
|
||||
_frame->_buffer.clear();
|
||||
}
|
||||
|
||||
if (!_drop_flag) {
|
||||
_frame->_buffer.append(payload, size);
|
||||
}
|
||||
|
||||
_last_seq = rtp->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
CommonRtpEncoder::CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_size,
|
||||
uint32_t sample_rate, uint8_t payload_type, uint8_t interleaved)
|
||||
: CommonRtpDecoder(codec), RtpInfo(ssrc, mtu_size, sample_rate, payload_type, interleaved) {
|
||||
}
|
||||
|
||||
void CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){
|
||||
GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS);
|
||||
auto stamp = frame->dts() % cycleMS;
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto remain_size = len;
|
||||
const auto max_rtp_size = _ui32MtuSize - 20;
|
||||
|
||||
while (remain_size > 0) {
|
||||
auto rtp_size = remain_size > max_rtp_size ? max_rtp_size : remain_size;
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), ptr, rtp_size, false, stamp), false);
|
||||
ptr += rtp_size;
|
||||
remain_size -= rtp_size;
|
||||
}
|
||||
}
|
85
src/Extension/CommonRtp.h
Normal file
85
src/Extension/CommonRtp.h
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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_COMMONRTP_H
|
||||
#define ZLMEDIAKIT_COMMONRTP_H
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Rtsp/RtpCodec.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* 通用 rtp解码类
|
||||
*/
|
||||
class CommonRtpDecoder : public RtpCodec, public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr <CommonRtpDecoder> Ptr;
|
||||
|
||||
~CommonRtpDecoder() override {}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param codec 编码id
|
||||
* @param max_frame_size 允许的最大帧大小
|
||||
*/
|
||||
CommonRtpDecoder(CodecId codec, int max_frame_size = 2 * 1024);
|
||||
|
||||
/**
|
||||
* 返回编码类型ID
|
||||
*/
|
||||
CodecId getCodecId() const override;
|
||||
|
||||
/**
|
||||
* 输入rtp并解码
|
||||
* @param rtp rtp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
*/
|
||||
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = false) override;
|
||||
|
||||
private:
|
||||
void obtainFrame();
|
||||
|
||||
private:
|
||||
bool _drop_flag = false;
|
||||
uint16_t _last_seq = 0;
|
||||
int _max_frame_size;
|
||||
CodecId _codec;
|
||||
FrameImp::Ptr _frame;
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用 rtp编码类
|
||||
*/
|
||||
class CommonRtpEncoder : public CommonRtpDecoder, public RtpInfo {
|
||||
public:
|
||||
typedef std::shared_ptr <CommonRtpEncoder> Ptr;
|
||||
|
||||
~CommonRtpEncoder() override {}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param codec 编码类型
|
||||
* @param ssrc ssrc
|
||||
* @param mtu_size mtu 大小
|
||||
* @param sample_rate 采样率
|
||||
* @param payload_type pt类型
|
||||
* @param interleaved rtsp interleaved 值
|
||||
*/
|
||||
CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_size, uint32_t sample_rate, uint8_t payload_type, uint8_t interleaved);
|
||||
|
||||
/**
|
||||
* 输入帧数据并编码成rtp
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_COMMONRTP_H
|
@ -13,11 +13,13 @@
|
||||
#include "H264Rtmp.h"
|
||||
#include "H265Rtmp.h"
|
||||
#include "AACRtmp.h"
|
||||
#include "G711Rtmp.h"
|
||||
#include "CommonRtmp.h"
|
||||
#include "H264Rtp.h"
|
||||
#include "AACRtp.h"
|
||||
#include "G711Rtp.h"
|
||||
#include "H265Rtp.h"
|
||||
#include "CommonRtp.h"
|
||||
#include "Opus.h"
|
||||
#include "G711.h"
|
||||
#include "Common/Parser.h"
|
||||
|
||||
namespace mediakit{
|
||||
@ -42,6 +44,10 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
||||
return std::make_shared<AACTrack>(aac_cfg);
|
||||
}
|
||||
|
||||
if (strcasecmp(track->_codec.data(), "opus") == 0) {
|
||||
return std::make_shared<OpusTrack>();
|
||||
}
|
||||
|
||||
if (strcasecmp(track->_codec.data(), "PCMA") == 0) {
|
||||
return std::make_shared<G711Track>(CodecG711A, track->_samplerate, track->_channel, 16);
|
||||
}
|
||||
@ -114,11 +120,12 @@ RtpCodec::Ptr Factory::getRtpEncoderBySdp(const Sdp::Ptr &sdp) {
|
||||
auto interleaved = sdp->getTrackType() * 2;
|
||||
auto codec_id = sdp->getCodecId();
|
||||
switch (codec_id){
|
||||
case CodecH264 : return std::make_shared<H264RtpEncoder>(ssrc,mtu,sample_rate,pt,interleaved);
|
||||
case CodecH265 : return std::make_shared<H265RtpEncoder>(ssrc,mtu,sample_rate,pt,interleaved);
|
||||
case CodecAAC : return std::make_shared<AACRtpEncoder>(ssrc,mtu,sample_rate,pt,interleaved);
|
||||
case CodecH264 : return std::make_shared<H264RtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecH265 : return std::make_shared<H265RtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecAAC : return std::make_shared<AACRtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecOpus :
|
||||
case CodecG711A :
|
||||
case CodecG711U : return std::make_shared<G711RtpEncoder>(codec_id, ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecG711U : return std::make_shared<CommonRtpEncoder>(codec_id, ssrc, mtu, sample_rate, pt, interleaved);
|
||||
default : WarnL << "暂不支持该CodecId:" << codec_id; return nullptr;
|
||||
}
|
||||
}
|
||||
@ -128,8 +135,9 @@ RtpCodec::Ptr Factory::getRtpDecoderByTrack(const Track::Ptr &track) {
|
||||
case CodecH264 : return std::make_shared<H264RtpDecoder>();
|
||||
case CodecH265 : return std::make_shared<H265RtpDecoder>();
|
||||
case CodecAAC : return std::make_shared<AACRtpDecoder>(track->clone());
|
||||
case CodecOpus :
|
||||
case CodecG711A :
|
||||
case CodecG711U : return std::make_shared<G711RtpDecoder>(track->getCodecId());
|
||||
case CodecG711U : return std::make_shared<CommonRtpDecoder>(track->getCodecId());
|
||||
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
||||
}
|
||||
}
|
||||
@ -137,40 +145,35 @@ RtpCodec::Ptr Factory::getRtpDecoderByTrack(const Track::Ptr &track) {
|
||||
/////////////////////////////rtmp相关///////////////////////////////////////////
|
||||
|
||||
static CodecId getVideoCodecIdByAmf(const AMFValue &val){
|
||||
if (val.type() == AMF_STRING){
|
||||
if (val.type() == AMF_STRING) {
|
||||
auto str = val.as_string();
|
||||
if(str == "avc1"){
|
||||
if (str == "avc1") {
|
||||
return CodecH264;
|
||||
}
|
||||
if(str == "mp4a"){
|
||||
return CodecAAC;
|
||||
}
|
||||
if(str == "hev1" || str == "hvc1"){
|
||||
if (str == "hev1" || str == "hvc1") {
|
||||
return CodecH265;
|
||||
}
|
||||
WarnL << "暂不支持该Amf:" << str;
|
||||
WarnL << "暂不支持该视频Amf:" << str;
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
if (val.type() != AMF_NULL){
|
||||
if (val.type() != AMF_NULL) {
|
||||
auto type_id = val.as_integer();
|
||||
switch (type_id){
|
||||
case FLV_CODEC_H264: return CodecH264;
|
||||
case FLV_CODEC_AAC: return CodecAAC;
|
||||
case FLV_CODEC_H265: return CodecH265;
|
||||
default : WarnL << "暂不支持该Amf:" << type_id; return CodecInvalid;
|
||||
switch (type_id) {
|
||||
case FLV_CODEC_H264 : return CodecH264;
|
||||
case FLV_CODEC_H265 : return CodecH265;
|
||||
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
|
||||
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
|
||||
switch (codecId){
|
||||
case CodecH264 : return std::make_shared<H264Track>();
|
||||
case CodecH265 : return std::make_shared<H265Track>();
|
||||
case CodecAAC : return std::make_shared<AACTrack>();
|
||||
case CodecOpus: return std::make_shared<OpusTrack>();
|
||||
case CodecG711A :
|
||||
case CodecG711U : return (sample_rate && channels && sample_bit) ? std::make_shared<G711Track>(codecId, sample_rate, channels, sample_bit) : nullptr;
|
||||
default : WarnL << "暂不支持该CodecId:" << codecId; return nullptr;
|
||||
@ -191,7 +194,7 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
||||
if (str == "mp4a") {
|
||||
return CodecAAC;
|
||||
}
|
||||
WarnL << "暂不支持该Amf:" << str;
|
||||
WarnL << "暂不支持该音频Amf:" << str;
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
@ -201,7 +204,8 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
||||
case FLV_CODEC_AAC : return CodecAAC;
|
||||
case FLV_CODEC_G711A : return CodecG711A;
|
||||
case FLV_CODEC_G711U : return CodecG711U;
|
||||
default : WarnL << "暂不支持该Amf:" << type_id; return CodecInvalid;
|
||||
case FLV_CODEC_OPUS : return CodecOpus;
|
||||
default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +225,7 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
|
||||
case CodecH264 : return std::make_shared<H264RtmpEncoder>(track);
|
||||
case CodecAAC : return std::make_shared<AACRtmpEncoder>(track);
|
||||
case CodecH265 : return std::make_shared<H265RtmpEncoder>(track);
|
||||
case CodecOpus : return std::make_shared<CommonRtmpEncoder>(track);
|
||||
case CodecG711A :
|
||||
case CodecG711U : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
@ -235,7 +240,7 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
|
||||
<< ",该音频已被忽略";
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_shared<G711RtmpEncoder>(track);
|
||||
return std::make_shared<CommonRtmpEncoder>(track);
|
||||
}
|
||||
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
||||
}
|
||||
@ -248,6 +253,7 @@ AMFValue Factory::getAmfByCodecId(CodecId codecId) {
|
||||
case CodecH265: return AMFValue(FLV_CODEC_H265);
|
||||
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
|
||||
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
|
||||
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
|
||||
default: return AMFValue(AMF_NULL);
|
||||
}
|
||||
}
|
||||
|
@ -35,12 +35,12 @@ public:
|
||||
_dts = frame->dts();
|
||||
_pts = frame->pts();
|
||||
_prefix_size = frame->prefixSize();
|
||||
_codecid = frame->getCodecId();
|
||||
_codec_id = frame->getCodecId();
|
||||
_key = frame->keyFrame();
|
||||
_config = frame->configFrame();
|
||||
}
|
||||
|
||||
virtual ~FrameCacheAble() = default;
|
||||
~FrameCacheAble() override = default;
|
||||
|
||||
/**
|
||||
* 可以被缓存
|
||||
@ -49,10 +49,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecid;
|
||||
}
|
||||
|
||||
bool keyFrame() const override{
|
||||
return _key;
|
||||
}
|
||||
@ -60,10 +56,10 @@ public:
|
||||
bool configFrame() const override{
|
||||
return _config;
|
||||
}
|
||||
|
||||
private:
|
||||
Frame::Ptr _frame;
|
||||
BufferRaw::Ptr _buffer;
|
||||
CodecId _codecid;
|
||||
bool _key;
|
||||
bool _config;
|
||||
};
|
||||
|
@ -148,7 +148,7 @@ public:
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecid;
|
||||
return _codec_id;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@ -160,7 +160,7 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
CodecId _codecid = CodecInvalid;
|
||||
CodecId _codec_id = CodecInvalid;
|
||||
string _buffer;
|
||||
uint32_t _dts = 0;
|
||||
uint32_t _pts = 0;
|
||||
@ -314,9 +314,24 @@ private:
|
||||
class FrameFromPtr : public Frame{
|
||||
public:
|
||||
typedef std::shared_ptr<FrameFromPtr> Ptr;
|
||||
|
||||
FrameFromPtr(CodecId codec_id, char *ptr, uint32_t size, uint32_t dts, uint32_t pts = 0, int prefix_size = 0)
|
||||
: FrameFromPtr(ptr, size, dts, pts, prefix_size) {
|
||||
_codec_id = codec_id;
|
||||
}
|
||||
|
||||
FrameFromPtr(char *ptr, uint32_t size, uint32_t dts, uint32_t pts = 0, int prefix_size = 0){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_pts = pts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
char *data() const override{
|
||||
return _ptr;
|
||||
}
|
||||
|
||||
uint32_t size() const override {
|
||||
return _size;
|
||||
}
|
||||
@ -336,12 +351,80 @@ public:
|
||||
bool cacheAble() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override {
|
||||
if (_codec_id == CodecInvalid) {
|
||||
throw std::invalid_argument("FrameFromPtr对象未设置codec类型");
|
||||
}
|
||||
return _codec_id;
|
||||
}
|
||||
|
||||
void setCodecId(CodecId codec_id) {
|
||||
_codec_id = codec_id;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
FrameFromPtr() {}
|
||||
|
||||
protected:
|
||||
char *_ptr;
|
||||
uint32_t _size;
|
||||
uint32_t _dts;
|
||||
uint32_t _pts = 0;
|
||||
uint32_t _prefix_size;
|
||||
CodecId _codec_id = CodecInvalid;
|
||||
};
|
||||
|
||||
/**
|
||||
* 该对象可以把Buffer对象转换成可缓存的Frame对象
|
||||
*/
|
||||
template <typename Parent>
|
||||
class FrameWrapper : public Parent{
|
||||
public:
|
||||
~FrameWrapper() = default;
|
||||
|
||||
/**
|
||||
* 构造frame
|
||||
* @param buf 数据缓存
|
||||
* @param dts 解码时间戳
|
||||
* @param pts 显示时间戳
|
||||
* @param prefix 帧前缀长度
|
||||
* @param offset buffer有效数据偏移量
|
||||
*/
|
||||
FrameWrapper(const Buffer::Ptr &buf, int64_t dts, int64_t pts, int prefix, int offset) : Parent(buf->data() + offset, buf->size() - offset, dts, pts, prefix){
|
||||
_buf = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造frame
|
||||
* @param buf 数据缓存
|
||||
* @param dts 解码时间戳
|
||||
* @param pts 显示时间戳
|
||||
* @param prefix 帧前缀长度
|
||||
* @param offset buffer有效数据偏移量
|
||||
* @param codec 帧类型
|
||||
*/
|
||||
FrameWrapper(const Buffer::Ptr &buf, int64_t dts, int64_t pts, int prefix, int offset, CodecId codec) : Parent(codec, buf->data() + offset, buf->size() - offset, dts, pts, prefix){
|
||||
_buf = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该帧可缓存
|
||||
*/
|
||||
bool cacheAble() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Buffer::Ptr _buf;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -16,47 +16,6 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* G711帧
|
||||
*/
|
||||
class G711Frame : public FrameImp {
|
||||
public:
|
||||
G711Frame(){
|
||||
_codecid = CodecG711A;
|
||||
}
|
||||
};
|
||||
|
||||
class G711FrameNoCacheAble : public FrameFromPtr {
|
||||
public:
|
||||
typedef std::shared_ptr<G711FrameNoCacheAble> Ptr;
|
||||
|
||||
G711FrameNoCacheAble(char *ptr,uint32_t size,uint32_t dts, uint32_t pts = 0,int prefix_size = 0){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
void setCodec(CodecId codecId){
|
||||
_codecId = codecId;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecId;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
CodecId _codecId;
|
||||
};
|
||||
|
||||
/**
|
||||
* G711音频通道
|
||||
*/
|
||||
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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 "G711Rtmp.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
G711RtmpDecoder::G711RtmpDecoder(CodecId codecId) {
|
||||
_frame = obtainFrame();
|
||||
_codecId = codecId;
|
||||
}
|
||||
|
||||
G711Frame::Ptr G711RtmpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<G711Frame>::obtainObj();
|
||||
frame->_buffer.clear();
|
||||
frame->_codecid = _codecId;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool G711RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool) {
|
||||
//拷贝G711负载
|
||||
_frame->_buffer.assign(pkt->strBuf.data() + 1, pkt->strBuf.size() - 1);
|
||||
_frame->_dts = pkt->timeStamp;
|
||||
//写入环形缓存
|
||||
RtmpCodec::inputFrame(_frame);
|
||||
_frame = obtainFrame();
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
G711RtmpEncoder::G711RtmpEncoder(const Track::Ptr &track) : G711RtmpDecoder(track->getCodecId()) {
|
||||
_audio_flv_flags = getAudioRtmpFlags(track);
|
||||
}
|
||||
|
||||
void G711RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
if(!_audio_flv_flags){
|
||||
return;
|
||||
}
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
//header
|
||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
||||
|
||||
//g711 data
|
||||
rtmpPkt->strBuf.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = frame->dts();
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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_G711RTMPCODEC_H
|
||||
#define ZLMEDIAKIT_G711RTMPCODEC_H
|
||||
|
||||
#include "Rtmp/RtmpCodec.h"
|
||||
#include "Extension/Track.h"
|
||||
#include "Extension/G711.h"
|
||||
|
||||
namespace mediakit{
|
||||
/**
|
||||
* G711 Rtmp转G711 Frame类
|
||||
*/
|
||||
class G711RtmpDecoder : public RtmpCodec , public ResourcePoolHelper<G711Frame> {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtmpDecoder> Ptr;
|
||||
|
||||
G711RtmpDecoder(CodecId codecId);
|
||||
~G711RtmpDecoder() {}
|
||||
|
||||
/**
|
||||
* 输入Rtmp并解码
|
||||
* @param Rtmp Rtmp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &Rtmp, bool key_pos = false) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecId;
|
||||
}
|
||||
private:
|
||||
G711Frame::Ptr obtainFrame();
|
||||
private:
|
||||
G711Frame::Ptr _frame;
|
||||
CodecId _codecId;
|
||||
};
|
||||
|
||||
/**
|
||||
* G711 RTMP打包类
|
||||
*/
|
||||
class G711RtmpEncoder : public G711RtmpDecoder , public ResourcePoolHelper<RtmpPacket> {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtmpEncoder> Ptr;
|
||||
|
||||
G711RtmpEncoder(const Track::Ptr &track);
|
||||
~G711RtmpEncoder() {}
|
||||
|
||||
/**
|
||||
* 输入G711 数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
private:
|
||||
uint8_t _audio_flv_flags = 0;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_G711RTMPCODEC_H
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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 "G711Rtp.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
G711RtpDecoder::G711RtpDecoder(CodecId codecid){
|
||||
_codecid = codecid;
|
||||
_frame = obtainFrame();
|
||||
}
|
||||
|
||||
G711Frame::Ptr G711RtpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<G711Frame>::obtainObj();
|
||||
frame->_buffer.clear();
|
||||
frame->_codecid = _codecid;
|
||||
frame->_dts = 0;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool G711RtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool) {
|
||||
// 获取rtp数据长度
|
||||
int length = rtppack->size() - rtppack->offset;
|
||||
// 获取rtp数据
|
||||
const char *rtp_packet_buf = rtppack->data() + rtppack->offset;
|
||||
|
||||
if (rtppack->timeStamp != _frame->_dts) {
|
||||
//时间戳变更,清空上一帧
|
||||
onGetG711(_frame);
|
||||
}
|
||||
|
||||
//追加数据
|
||||
_frame->_buffer.append(rtp_packet_buf, length);
|
||||
//赋值时间戳
|
||||
_frame->_dts = rtppack->timeStamp;
|
||||
|
||||
if (rtppack->mark || _frame->_buffer.size() > 10 * 1024) {
|
||||
//标记为mark时,或者内存快溢出时,我们认为这是该帧最后一个包
|
||||
onGetG711(_frame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void G711RtpDecoder::onGetG711(const G711Frame::Ptr &frame) {
|
||||
if(!frame->_buffer.empty()){
|
||||
//写入环形缓存
|
||||
RtpCodec::inputFrame(frame);
|
||||
_frame = obtainFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
G711RtpEncoder::G711RtpEncoder(CodecId codecid, uint32_t ui32Ssrc, uint32_t ui32MtuSize,
|
||||
uint32_t ui32SampleRate, uint8_t ui8PayloadType, uint8_t ui8Interleaved) :
|
||||
G711RtpDecoder(codecid),
|
||||
RtpInfo(ui32Ssrc, ui32MtuSize, ui32SampleRate, ui8PayloadType, ui8Interleaved) {
|
||||
}
|
||||
|
||||
void G711RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS);
|
||||
auto uiStamp = frame->dts();
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
|
||||
uiStamp %= cycleMS;
|
||||
char *ptr = (char *) pcData;
|
||||
int iSize = iLen;
|
||||
while (iSize > 0) {
|
||||
if (iSize <= _ui32MtuSize - 20) {
|
||||
makeG711Rtp(ptr, iSize, true, uiStamp);
|
||||
break;
|
||||
}
|
||||
makeG711Rtp(ptr, _ui32MtuSize - 20, false, uiStamp);
|
||||
ptr += (_ui32MtuSize - 20);
|
||||
iSize -= (_ui32MtuSize - 20);
|
||||
}
|
||||
}
|
||||
|
||||
void G711RtpEncoder::makeG711Rtp(const void *data, unsigned int len, bool mark, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), false);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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_G711RTPCODEC_H
|
||||
#define ZLMEDIAKIT_G711RTPCODEC_H
|
||||
#include "Rtsp/RtpCodec.h"
|
||||
#include "Extension/G711.h"
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* rtp转G711类
|
||||
*/
|
||||
class G711RtpDecoder : public RtpCodec , public ResourcePoolHelper<G711Frame> {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtpDecoder> Ptr;
|
||||
|
||||
G711RtpDecoder(CodecId codecid);
|
||||
~G711RtpDecoder() {}
|
||||
|
||||
/**
|
||||
* 输入rtp并解码
|
||||
* @param rtp rtp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
*/
|
||||
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = false) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecid;
|
||||
}
|
||||
|
||||
private:
|
||||
void onGetG711(const G711Frame::Ptr &frame);
|
||||
G711Frame::Ptr obtainFrame();
|
||||
|
||||
private:
|
||||
G711Frame::Ptr _frame;
|
||||
CodecId _codecid;
|
||||
};
|
||||
|
||||
/**
|
||||
* g711 转rtp类
|
||||
*/
|
||||
class G711RtpEncoder : public G711RtpDecoder , public RtpInfo {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtpEncoder> Ptr;
|
||||
|
||||
/**
|
||||
* @param ui32Ssrc ssrc
|
||||
* @param ui32MtuSize mtu 大小
|
||||
* @param ui32SampleRate 采样率
|
||||
* @param ui8PayloadType pt类型
|
||||
* @param ui8Interleaved rtsp interleaved 值
|
||||
*/
|
||||
G711RtpEncoder(CodecId codecid,
|
||||
uint32_t ui32Ssrc,
|
||||
uint32_t ui32MtuSize,
|
||||
uint32_t ui32SampleRate,
|
||||
uint8_t ui8PayloadType = 0,
|
||||
uint8_t ui8Interleaved = TrackAudio * 2);
|
||||
~G711RtpEncoder() {}
|
||||
|
||||
/**
|
||||
* @param frame g711数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
private:
|
||||
void makeG711Rtp(const void *pData, unsigned int uiLen, bool bMark, uint32_t uiStamp);
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_G711RTPCODEC_H
|
@ -30,14 +30,16 @@ public:
|
||||
typedef std::shared_ptr<H264Frame> Ptr;
|
||||
|
||||
typedef enum {
|
||||
NAL_SPS = 7,
|
||||
NAL_PPS = 8,
|
||||
NAL_IDR = 5,
|
||||
NAL_SEI = 6,
|
||||
NAL_SPS = 7,
|
||||
NAL_PPS = 8,
|
||||
NAL_AUD = 9,
|
||||
NAL_B_P = 1,
|
||||
} NalType;
|
||||
|
||||
H264Frame(){
|
||||
_codecid = CodecH264;
|
||||
_codec_id = CodecH264;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@ -68,10 +70,7 @@ public:
|
||||
_dts = dts;
|
||||
_pts = pts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH264;
|
||||
_codec_id = CodecH264;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@ -182,8 +181,8 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
int type = H264_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
||||
if(type == H264Frame::NAL_SPS || type == H264Frame::NAL_SEI){
|
||||
//有些设备会把SPS PPS IDR帧当做一个帧打包,所以我们要split一下
|
||||
if(type != H264Frame::NAL_B_P && type != H264Frame::NAL_IDR){
|
||||
//非I/B/P帧情况下,split一下,防止多个帧粘合在一起
|
||||
splitH264(frame->data(), frame->size(), frame->prefixSize(), [&](const char *ptr, int len, int prefix) {
|
||||
H264FrameInternal::Ptr sub_frame = std::make_shared<H264FrameInternal>(frame, (char *)ptr, len, prefix);
|
||||
inputFrame_l(sub_frame);
|
||||
@ -227,6 +226,10 @@ private:
|
||||
VideoTrack::inputFrame(frame);
|
||||
}
|
||||
break;
|
||||
case H264Frame::NAL_AUD:{
|
||||
//忽略AUD帧;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
VideoTrack::inputFrame(frame);
|
||||
|
@ -23,10 +23,6 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
||||
return decodeRtmp(rtmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回不带0x00 00 00 01头的sps
|
||||
* @return
|
||||
@ -39,18 +35,18 @@ static string getH264SPS(const RtmpPacket &thiz) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.strBuf.size() < 13) {
|
||||
if (thiz.buffer.size() < 13) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t sps_size ;
|
||||
memcpy(&sps_size, thiz.strBuf.data() + 11,2);
|
||||
memcpy(&sps_size, thiz.buffer.data() + 11, 2);
|
||||
sps_size = ntohs(sps_size);
|
||||
if ((int) thiz.strBuf.size() < 13 + sps_size) {
|
||||
if ((int) thiz.buffer.size() < 13 + sps_size) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret.assign(thiz.strBuf.data() + 13, sps_size);
|
||||
ret.assign(thiz.buffer.data() + 13, sps_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -66,60 +62,59 @@ static string getH264PPS(const RtmpPacket &thiz) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.strBuf.size() < 13) {
|
||||
if (thiz.buffer.size() < 13) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t sps_size ;
|
||||
memcpy(&sps_size,thiz.strBuf.data() + 11,2);
|
||||
memcpy(&sps_size, thiz.buffer.data() + 11, 2);
|
||||
sps_size = ntohs(sps_size);
|
||||
|
||||
if ((int) thiz.strBuf.size() < 13 + sps_size + 1 + 2) {
|
||||
if ((int) thiz.buffer.size() < 13 + sps_size + 1 + 2) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t pps_size ;
|
||||
memcpy(&pps_size, thiz.strBuf.data() + 13 + sps_size + 1,2);
|
||||
memcpy(&pps_size, thiz.buffer.data() + 13 + sps_size + 1, 2);
|
||||
pps_size = ntohs(pps_size);
|
||||
|
||||
if ((int) thiz.strBuf.size() < 13 + sps_size + 1 + 2 + pps_size) {
|
||||
if ((int) thiz.buffer.size() < 13 + sps_size + 1 + 2 + pps_size) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret.assign(thiz.strBuf.data() + 13 + sps_size + 1 + 2, pps_size);
|
||||
ret.assign(thiz.buffer.data() + 13 + sps_size + 1 + 2, pps_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
//缓存sps pps,后续插入到I帧之前
|
||||
_sps = getH264SPS(*pkt);
|
||||
_pps = getH264PPS(*pkt);
|
||||
onGetH264(_sps.data(), _sps.size(), pkt->timeStamp , pkt->timeStamp);
|
||||
onGetH264(_pps.data(), _pps.size(), pkt->timeStamp , pkt->timeStamp);
|
||||
return false;
|
||||
onGetH264(_sps.data(), _sps.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
onGetH264(_pps.data(), _pps.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt->strBuf.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->strBuf.size();
|
||||
if (pkt->buffer.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->buffer.size();
|
||||
uint32_t iOffset = 5;
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->strBuf.data() + 2);
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2);
|
||||
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
|
||||
auto pts = pkt->timeStamp + cts;
|
||||
auto pts = pkt->time_stamp + cts;
|
||||
|
||||
while(iOffset + 4 < iTotalLen){
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pkt->strBuf.data() + iOffset, 4);
|
||||
memcpy(&iFrameLen, pkt->buffer.data() + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
iOffset += 4;
|
||||
if(iFrameLen + iOffset > iTotalLen){
|
||||
break;
|
||||
}
|
||||
onGetH264(pkt->strBuf.data() + iOffset, iFrameLen, pkt->timeStamp , pts);
|
||||
onGetH264(pkt->buffer.data() + iOffset, iFrameLen, pkt->time_stamp , pts);
|
||||
iOffset += iFrameLen;
|
||||
}
|
||||
}
|
||||
return pkt->isVideoKeyFrame();
|
||||
}
|
||||
|
||||
inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
||||
@ -190,8 +185,8 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
||||
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
|
||||
@ -202,23 +197,23 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
|
||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
_lastPacket->strBuf.clear();
|
||||
_lastPacket->strBuf.push_back(flags);
|
||||
_lastPacket->strBuf.push_back(!is_config);
|
||||
_lastPacket->buffer.clear();
|
||||
_lastPacket->buffer.push_back(flags);
|
||||
_lastPacket->buffer.push_back(!is_config);
|
||||
auto cts = frame->pts() - frame->dts();
|
||||
cts = htonl(cts);
|
||||
_lastPacket->strBuf.append((char *)&cts + 1, 3);
|
||||
_lastPacket->buffer.append((char *)&cts + 1, 3);
|
||||
|
||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
||||
_lastPacket->streamId = STREAM_MEDIA;
|
||||
_lastPacket->timeStamp = frame->dts();
|
||||
_lastPacket->typeId = MSG_VIDEO;
|
||||
_lastPacket->chunk_id = CHUNK_VIDEO;
|
||||
_lastPacket->stream_index = STREAM_MEDIA;
|
||||
_lastPacket->time_stamp = frame->dts();
|
||||
_lastPacket->type_id = MSG_VIDEO;
|
||||
|
||||
}
|
||||
auto size = htonl(iLen);
|
||||
_lastPacket->strBuf.append((char *) &size, 4);
|
||||
_lastPacket->strBuf.append(pcData, iLen);
|
||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
||||
_lastPacket->buffer.append((char *) &size, 4);
|
||||
_lastPacket->buffer.append(pcData, iLen);
|
||||
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
@ -227,39 +222,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
bool is_config = true;
|
||||
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
rtmpPkt->strBuf.push_back(flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//cts
|
||||
rtmpPkt->strBuf.append("\x0\x0\x0", 3);
|
||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||
|
||||
//AVCDecoderConfigurationRecord start
|
||||
rtmpPkt->strBuf.push_back(1); // version
|
||||
rtmpPkt->strBuf.push_back(_sps[1]); // profile
|
||||
rtmpPkt->strBuf.push_back(_sps[2]); // compat
|
||||
rtmpPkt->strBuf.push_back(_sps[3]); // level
|
||||
rtmpPkt->strBuf.push_back(0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||
rtmpPkt->strBuf.push_back(0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||
rtmpPkt->buffer.push_back(1); // version
|
||||
rtmpPkt->buffer.push_back(_sps[1]); // profile
|
||||
rtmpPkt->buffer.push_back(_sps[2]); // compat
|
||||
rtmpPkt->buffer.push_back(_sps[3]); // level
|
||||
rtmpPkt->buffer.push_back(0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||
rtmpPkt->buffer.push_back(0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||
//sps
|
||||
uint16_t size = _sps.size();
|
||||
size = htons(size);
|
||||
rtmpPkt->strBuf.append((char *) &size, 2);
|
||||
rtmpPkt->strBuf.append(_sps);
|
||||
rtmpPkt->buffer.append((char *) &size, 2);
|
||||
rtmpPkt->buffer.append(_sps);
|
||||
//pps
|
||||
rtmpPkt->strBuf.push_back(1); // version
|
||||
rtmpPkt->buffer.push_back(1); // version
|
||||
size = _pps.size();
|
||||
size = htons(size);
|
||||
rtmpPkt->strBuf.append((char *) &size, 2);
|
||||
rtmpPkt->strBuf.append(_pps);
|
||||
rtmpPkt->buffer.append((char *) &size, 2);
|
||||
rtmpPkt->buffer.append(_pps);
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_VIDEO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = 0;
|
||||
rtmpPkt->typeId = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -32,17 +32,17 @@ public:
|
||||
/**
|
||||
* 输入264 Rtmp包
|
||||
* @param rtmp Rtmp包
|
||||
* @param key_pos 此参数忽略之
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos = true) override;
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH264;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool decodeRtmp(const RtmpPacket::Ptr &Rtmp);
|
||||
void onGetH264(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
||||
H264Frame::Ptr obtainFrame();
|
||||
|
||||
protected:
|
||||
H264Frame::Ptr _h264frame;
|
||||
string _sps;
|
||||
|
@ -12,13 +12,6 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned forbidden_zero_bit :1;
|
||||
unsigned nal_ref_idc :2;
|
||||
unsigned type :5;
|
||||
} NALU;
|
||||
|
||||
typedef struct {
|
||||
unsigned S :1;
|
||||
unsigned E :1;
|
||||
@ -26,15 +19,6 @@ typedef struct {
|
||||
unsigned type :5;
|
||||
} FU;
|
||||
|
||||
static bool MakeNalu(uint8_t in, NALU &nal) {
|
||||
nal.forbidden_zero_bit = in >> 7;
|
||||
if (nal.forbidden_zero_bit) {
|
||||
return false;
|
||||
}
|
||||
nal.nal_ref_idc = (in & 0x60) >> 5;
|
||||
nal.type = in & 0x1f;
|
||||
return true;
|
||||
}
|
||||
static bool MakeFU(uint8_t in, FU &fu) {
|
||||
fu.S = in >> 7;
|
||||
fu.E = (in >> 6) & 0x01;
|
||||
@ -86,30 +70,28 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
28 FU-A Fragmentation unit 5.8
|
||||
29 FU-B Fragmentation unit 5.8
|
||||
30-31 undefined -
|
||||
|
||||
|
||||
*/
|
||||
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||
int length = rtppack->size() - rtppack->offset;
|
||||
NALU nal;
|
||||
MakeNalu(*frame, nal);
|
||||
int nal_type = *frame & 0x1F;
|
||||
int nal_suffix = *frame & (~0x1F);
|
||||
|
||||
if (nal.type >= 0 && nal.type < 24) {
|
||||
if (nal_type >= 0 && nal_type < 24) {
|
||||
//a full frame
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.append((char *)frame, length);
|
||||
_h264frame->_buffer.append((char *) frame, length);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
auto key = _h264frame->keyFrame();
|
||||
onGetH264(_h264frame);
|
||||
return (key); //i frame
|
||||
}
|
||||
|
||||
switch (nal.type){
|
||||
switch (nal_type){
|
||||
case 24:{
|
||||
// 24 STAP-A 单一时间的组合包
|
||||
bool haveIDR = false;
|
||||
auto ptr = frame + 1;
|
||||
while(true){
|
||||
while (true) {
|
||||
int off = ptr - frame;
|
||||
if (off >= length) {
|
||||
break;
|
||||
@ -121,14 +103,12 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
if (off + len > length) {
|
||||
break;
|
||||
}
|
||||
if(len >= 10){
|
||||
//过小的帧丢弃
|
||||
NALU nal;
|
||||
MakeNalu(ptr[0], nal);
|
||||
if (len > 0) {
|
||||
//有有效数据
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.append((char *)ptr, len);
|
||||
_h264frame->_buffer.append((char *) ptr, len);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
if(nal.type == H264Frame::NAL_IDR){
|
||||
if ((ptr[0] & 0x1F) == H264Frame::NAL_IDR) {
|
||||
haveIDR = true;
|
||||
}
|
||||
onGetH264(_h264frame);
|
||||
@ -144,10 +124,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
MakeFU(frame[1], fu);
|
||||
if (fu.S) {
|
||||
//该帧的第一个rtp包 FU-A start
|
||||
char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type);
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.push_back(tmp);
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.push_back(nal_suffix | fu.type);
|
||||
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
@ -163,20 +142,20 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
|
||||
if (!fu.E) {
|
||||
//该帧的中间rtp包 FU-A mid
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
//该帧最后一个rtp包 FU-A end
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
onGetH264(_h264frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
default:{
|
||||
default: {
|
||||
// 29 FU-B 单NAL单元B模式
|
||||
// 25 STAP-B 单一时间的组合包
|
||||
// 26 MTAP16 多个时间的组合包
|
||||
@ -184,7 +163,7 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
// 0 udef
|
||||
// 30 udef
|
||||
// 31 udef
|
||||
WarnL << "不支持的rtp类型:" << (int)nal.type << " " << rtppack->sequence;
|
||||
WarnL << "不支持的rtp类型:" << (int) nal_type << " " << rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -215,63 +194,62 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ui32Ssrc,
|
||||
|
||||
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS);
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto uiStamp = frame->pts();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
//获取NALU的5bit 帧类型
|
||||
unsigned char naluType = H264_TYPE(pcData[0]);
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto pts = frame->pts() % cycleMS;
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto nal_type = H264_TYPE(ptr[0]);
|
||||
auto max_rtp_size = _ui32MtuSize - 2;
|
||||
|
||||
uiStamp %= cycleMS;
|
||||
int iSize = _ui32MtuSize - 2;
|
||||
//超过MTU则按照FU-A模式打包
|
||||
if (iLen > iSize) {
|
||||
if (len > max_rtp_size) {
|
||||
//最高位bit为forbidden_zero_bit,
|
||||
//后面2bit为nal_ref_idc(帧重要程度),00:可以丢,11:不能丢
|
||||
//末尾5bit为nalu type,固定为28(FU-A)
|
||||
unsigned char f_nri_flags = (*((unsigned char *) pcData) & 0x60) | 28;
|
||||
unsigned char nal_fu_a = (*((unsigned char *) ptr) & (~0x1F)) | 28;
|
||||
unsigned char s_e_r_flags;
|
||||
bool bFirst = true;
|
||||
bool mark = false;
|
||||
int nOffset = 1;
|
||||
while (!mark) {
|
||||
if (iLen <= nOffset + iSize) {
|
||||
bool fu_a_start = true;
|
||||
bool mark_bit = false;
|
||||
int offset = 1;
|
||||
while (!mark_bit) {
|
||||
if (len <= offset + max_rtp_size) {
|
||||
//已经拆分结束
|
||||
iSize = iLen - nOffset;
|
||||
mark = true;
|
||||
max_rtp_size = len - offset;
|
||||
mark_bit = true;
|
||||
//FU-A end
|
||||
s_e_r_flags = (1 << 6) | naluType;
|
||||
} else if (bFirst) {
|
||||
s_e_r_flags = (1 << 6) | nal_type;
|
||||
} else if (fu_a_start) {
|
||||
//FU-A start
|
||||
s_e_r_flags = (1 << 7) | naluType;
|
||||
s_e_r_flags = (1 << 7) | nal_type;
|
||||
} else {
|
||||
//FU-A mid
|
||||
s_e_r_flags = naluType;
|
||||
s_e_r_flags = nal_type;
|
||||
}
|
||||
|
||||
{
|
||||
//传入nullptr先不做payload的内存拷贝
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, iSize + 2, mark, uiStamp);
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, max_rtp_size + 2, mark_bit, pts);
|
||||
//rtp payload 负载部分
|
||||
uint8_t *payload = (uint8_t*)rtp->data() + rtp->offset;
|
||||
//FU-A 第1个字节
|
||||
payload[0] = f_nri_flags;
|
||||
payload[0] = nal_fu_a;
|
||||
//FU-A 第2个字节
|
||||
payload[1] = s_e_r_flags;
|
||||
//H264 数据
|
||||
memcpy(payload + 2, (unsigned char *) pcData + nOffset, iSize);
|
||||
memcpy(payload + 2, (unsigned char *) ptr + offset, max_rtp_size);
|
||||
//输入到rtp环形缓存
|
||||
RtpCodec::inputRtp(rtp,bFirst && naluType == H264Frame::NAL_IDR);
|
||||
RtpCodec::inputRtp(rtp, fu_a_start && nal_type == H264Frame::NAL_IDR);
|
||||
}
|
||||
nOffset += iSize;
|
||||
bFirst = false;
|
||||
offset += max_rtp_size;
|
||||
fu_a_start = false;
|
||||
}
|
||||
} else {
|
||||
makeH264Rtp(naluType,pcData, iLen, true, true, uiStamp);
|
||||
//如果帧长度不超过mtu, 则按照Single NAL unit packet per H.264 方式打包
|
||||
makeH264Rtp(ptr, len, false, false, pts);
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtpEncoder::makeH264Rtp(int nal_type,const void* data, unsigned int len, bool mark, bool first_packet, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(),data,len,mark,uiStamp),first_packet && nal_type == H264Frame::NAL_IDR);
|
||||
void H264RtpEncoder::makeH264Rtp(const void* data, unsigned int len, bool mark, bool gop_pos, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), gop_pos);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -78,7 +78,7 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
private:
|
||||
void makeH264Rtp(int nal_type,const void *pData, unsigned int uiLen, bool bMark, bool first_packet, uint32_t uiStamp);
|
||||
void makeH264Rtp(const void *pData, unsigned int uiLen, bool bMark, bool gop_pos, uint32_t uiStamp);
|
||||
};
|
||||
|
||||
}//namespace mediakit{
|
||||
|
@ -61,7 +61,7 @@ public:
|
||||
} NaleType;
|
||||
|
||||
H265Frame(){
|
||||
_codecid = CodecH265;
|
||||
_codec_id = CodecH265;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@ -92,10 +92,7 @@ public:
|
||||
_dts = dts;
|
||||
_pts = pts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override {
|
||||
return CodecH265;
|
||||
_codec_id = CodecH265;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
|
@ -27,10 +27,6 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
||||
return decodeRtmp(rtmp);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
/**
|
||||
* 返回不带0x00 00 00 01头的sps
|
||||
@ -43,61 +39,60 @@ static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return false;
|
||||
}
|
||||
if (thiz.strBuf.size() < 6) {
|
||||
if (thiz.buffer.size() < 6) {
|
||||
WarnL << "bad H265 cfg!";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto extra = thiz.strBuf.data() + 5;
|
||||
auto bytes = thiz.strBuf.size() - 5;
|
||||
auto extra = thiz.buffer.data() + 5;
|
||||
auto bytes = thiz.buffer.size() - 5;
|
||||
|
||||
struct mpeg4_hevc_t hevc = {0};
|
||||
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) {
|
||||
uint8_t config[1024] = {0};
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config));
|
||||
uint8_t *config = new uint8_t[bytes * 2];
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
|
||||
if (size > 4) {
|
||||
frame.assign((char *) config + 4, size - 4);
|
||||
return true;
|
||||
}
|
||||
delete [] config;
|
||||
return size > 4;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool H265RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
#ifdef ENABLE_MP4
|
||||
string config;
|
||||
if(getH265ConfigFrame(*pkt,config)){
|
||||
onGetH265(config.data(), config.size(), pkt->timeStamp , pkt->timeStamp);
|
||||
onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
}
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
#endif
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt->strBuf.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->strBuf.size();
|
||||
if (pkt->buffer.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->buffer.size();
|
||||
uint32_t iOffset = 5;
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->strBuf.data() + 2);
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2);
|
||||
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
|
||||
auto pts = pkt->timeStamp + cts;
|
||||
auto pts = pkt->time_stamp + cts;
|
||||
|
||||
while(iOffset + 4 < iTotalLen){
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pkt->strBuf.data() + iOffset, 4);
|
||||
memcpy(&iFrameLen, pkt->buffer.data() + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
iOffset += 4;
|
||||
if(iFrameLen + iOffset > iTotalLen){
|
||||
break;
|
||||
}
|
||||
onGetH265(pkt->strBuf.data() + iOffset, iFrameLen, pkt->timeStamp , pts);
|
||||
onGetH265(pkt->buffer.data() + iOffset, iFrameLen, pkt->time_stamp , pts);
|
||||
iOffset += iFrameLen;
|
||||
}
|
||||
}
|
||||
return pkt->isVideoKeyFrame();
|
||||
}
|
||||
|
||||
inline void H265RtmpDecoder::onGetH265(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
||||
@ -176,8 +171,8 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
||||
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
|
||||
@ -188,23 +183,23 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
|
||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
_lastPacket->strBuf.clear();
|
||||
_lastPacket->strBuf.push_back(flags);
|
||||
_lastPacket->strBuf.push_back(!is_config);
|
||||
_lastPacket->buffer.clear();
|
||||
_lastPacket->buffer.push_back(flags);
|
||||
_lastPacket->buffer.push_back(!is_config);
|
||||
auto cts = frame->pts() - frame->dts();
|
||||
cts = htonl(cts);
|
||||
_lastPacket->strBuf.append((char *)&cts + 1, 3);
|
||||
_lastPacket->buffer.append((char *)&cts + 1, 3);
|
||||
|
||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
||||
_lastPacket->streamId = STREAM_MEDIA;
|
||||
_lastPacket->timeStamp = frame->dts();
|
||||
_lastPacket->typeId = MSG_VIDEO;
|
||||
_lastPacket->chunk_id = CHUNK_VIDEO;
|
||||
_lastPacket->stream_index = STREAM_MEDIA;
|
||||
_lastPacket->time_stamp = frame->dts();
|
||||
_lastPacket->type_id = MSG_VIDEO;
|
||||
|
||||
}
|
||||
auto size = htonl(iLen);
|
||||
_lastPacket->strBuf.append((char *) &size, 4);
|
||||
_lastPacket->strBuf.append(pcData, iLen);
|
||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
||||
_lastPacket->buffer.append((char *) &size, 4);
|
||||
_lastPacket->buffer.append(pcData, iLen);
|
||||
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||
}
|
||||
|
||||
void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
@ -214,13 +209,13 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
bool is_config = true;
|
||||
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
rtmpPkt->strBuf.push_back(flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//cts
|
||||
rtmpPkt->strBuf.append("\x0\x0\x0", 3);
|
||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||
|
||||
struct mpeg4_hevc_t hevc = {0};
|
||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
|
||||
@ -235,14 +230,14 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
}
|
||||
|
||||
//HEVCDecoderConfigurationRecord
|
||||
rtmpPkt->strBuf.append((char *)extra_data, extra_data_size);
|
||||
rtmpPkt->buffer.append((char *)extra_data, extra_data_size);
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_VIDEO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = 0;
|
||||
rtmpPkt->typeId = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
#endif
|
||||
|
@ -32,17 +32,17 @@ public:
|
||||
/**
|
||||
* 输入265 Rtmp包
|
||||
* @param rtmp Rtmp包
|
||||
* @param key_pos 此参数忽略之
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos = true) override;
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH265;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool decodeRtmp(const RtmpPacket::Ptr &Rtmp);
|
||||
void onGetH265(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
||||
H265Frame::Ptr obtainFrame();
|
||||
|
||||
protected:
|
||||
H265Frame::Ptr _h265frame;
|
||||
};
|
||||
|
@ -200,7 +200,7 @@ void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
bFirst = false;
|
||||
}
|
||||
} else {
|
||||
makeH265Rtp(naluType,pcData, iLen, true, true, uiStamp);
|
||||
makeH265Rtp(naluType,pcData, iLen, false, true, uiStamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,52 +16,13 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* Opus帧
|
||||
*/
|
||||
class OpusFrame : public FrameImp {
|
||||
public:
|
||||
typedef std::shared_ptr<OpusFrame> Ptr;
|
||||
|
||||
OpusFrame(){
|
||||
_codecid = CodecOpus;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 不可缓存的Opus帧
|
||||
*/
|
||||
class OpusFrameNoCacheAble : public FrameFromPtr {
|
||||
public:
|
||||
typedef std::shared_ptr<OpusFrameNoCacheAble> Ptr;
|
||||
|
||||
OpusFrameNoCacheAble(char *ptr,uint32_t size,uint32_t dts, uint32_t pts = 0,int prefix_size = 0){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecOpus;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opus帧音频通道
|
||||
*/
|
||||
class OpusTrack : public AudioTrackImp{
|
||||
public:
|
||||
typedef std::shared_ptr<OpusTrack> Ptr;
|
||||
OpusTrack(int sample_rate, int channels, int sample_bit) : AudioTrackImp(CodecOpus,sample_rate,channels,sample_bit){}
|
||||
OpusTrack() : AudioTrackImp(CodecOpus,48000,2,16){}
|
||||
|
||||
private:
|
||||
//克隆该Track
|
||||
|
148
src/FMP4/FMP4MediaSource.h
Normal file
148
src/FMP4/FMP4MediaSource.h
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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_FMP4MEDIASOURCE_H
|
||||
#define ZLMEDIAKIT_FMP4MEDIASOURCE_H
|
||||
|
||||
#include "Common/MediaSource.h"
|
||||
using namespace toolkit;
|
||||
#define FMP4_GOP_SIZE 512
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
//FMP4直播数据包
|
||||
class FMP4Packet : public BufferString{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<FMP4Packet>;
|
||||
|
||||
template<typename ...ARGS>
|
||||
FMP4Packet(ARGS && ...args) : BufferString(std::forward<ARGS>(args)...) {};
|
||||
~FMP4Packet() override = default;
|
||||
|
||||
public:
|
||||
uint32_t time_stamp = 0;
|
||||
};
|
||||
|
||||
//FMP4直播合并写策略类
|
||||
class FMP4FlushPolicy : public FlushPolicy{
|
||||
public:
|
||||
FMP4FlushPolicy() = default;
|
||||
~FMP4FlushPolicy() = default;
|
||||
|
||||
uint32_t getStamp(const FMP4Packet::Ptr &packet) {
|
||||
return packet->time_stamp;
|
||||
}
|
||||
};
|
||||
|
||||
//FMP4直播源
|
||||
class FMP4MediaSource : public MediaSource, public RingDelegate<FMP4Packet::Ptr>, public PacketCache<FMP4Packet, FMP4FlushPolicy>{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<FMP4MediaSource>;
|
||||
using RingDataType = std::shared_ptr<List<FMP4Packet::Ptr> >;
|
||||
using RingType = RingBuffer<RingDataType>;
|
||||
|
||||
FMP4MediaSource(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream_id,
|
||||
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
|
||||
|
||||
~FMP4MediaSource() override = default;
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
*/
|
||||
const RingType::Ptr &getRing() const {
|
||||
return _ring;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取fmp4 init segment
|
||||
*/
|
||||
const string &getInitSegment() const{
|
||||
return _init_segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置fmp4 init segment
|
||||
* @param str init segment
|
||||
*/
|
||||
void setInitSegment(string str) {
|
||||
_init_segment = std::move(str);
|
||||
if (_ring) {
|
||||
regist();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取播放器个数
|
||||
*/
|
||||
int readerCount() override {
|
||||
return _ring ? _ring->readerCount() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入FMP4包
|
||||
* @param packet FMP4包
|
||||
* @param key 是否为关键帧第一个包
|
||||
*/
|
||||
void onWrite(const FMP4Packet::Ptr &packet, bool key) override {
|
||||
if (!_ring) {
|
||||
createRing();
|
||||
}
|
||||
if (key) {
|
||||
_have_video = true;
|
||||
}
|
||||
PacketCache<FMP4Packet, FMP4FlushPolicy>::inputPacket(true, packet, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 情况GOP缓存
|
||||
*/
|
||||
void clearCache() override {
|
||||
PacketCache<FMP4Packet, FMP4FlushPolicy>::clearCache();
|
||||
_ring->clearCache();
|
||||
}
|
||||
|
||||
private:
|
||||
void createRing(){
|
||||
weak_ptr<FMP4MediaSource> weak_self = dynamic_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) {
|
||||
return;
|
||||
}
|
||||
strong_self->onReaderChanged(size);
|
||||
});
|
||||
onReaderChanged(0);
|
||||
if (!_init_segment.empty()) {
|
||||
regist();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并写回调
|
||||
* @param packet_list 合并写缓存列队
|
||||
* @param key_pos 是否包含关键帧
|
||||
*/
|
||||
void onFlush(std::shared_ptr<List<FMP4Packet::Ptr> > &packet_list, bool key_pos) override {
|
||||
//如果不存在视频,那么就没有存在GOP缓存的意义,所以确保一直清空GOP缓存
|
||||
_ring->write(packet_list, _have_video ? key_pos : true);
|
||||
}
|
||||
|
||||
private:
|
||||
bool _have_video = false;
|
||||
int _ring_size;
|
||||
string _init_segment;
|
||||
RingType::Ptr _ring;
|
||||
};
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_FMP4MEDIASOURCE_H
|
85
src/FMP4/FMP4MediaSourceMuxer.h
Normal file
85
src/FMP4/FMP4MediaSourceMuxer.h
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/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_FMP4MEDIASOURCEMUXER_H
|
||||
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
||||
|
||||
#include "FMP4MediaSource.h"
|
||||
#include "Record/MP4Muxer.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class FMP4MediaSourceMuxer : public MP4MuxerMemory, public MediaSourceEventInterceptor,
|
||||
public std::enable_shared_from_this<FMP4MediaSourceMuxer> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<FMP4MediaSourceMuxer>;
|
||||
|
||||
FMP4MediaSourceMuxer(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream_id) {
|
||||
_media_src = std::make_shared<FMP4MediaSource>(vhost, app, stream_id);
|
||||
}
|
||||
|
||||
~FMP4MediaSourceMuxer() override = default;
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
_media_src->setListener(shared_from_this());
|
||||
}
|
||||
|
||||
int readerCount() const{
|
||||
return _media_src->readerCount();
|
||||
}
|
||||
|
||||
void onReaderChanged(MediaSource &sender, int size) override {
|
||||
_enabled = size;
|
||||
if (!size) {
|
||||
_clear_cache = true;
|
||||
}
|
||||
MediaSourceEventInterceptor::onReaderChanged(sender, size);
|
||||
}
|
||||
|
||||
void inputFrame(const Frame::Ptr &frame) override {
|
||||
if (_clear_cache) {
|
||||
_clear_cache = false;
|
||||
_media_src->clearCache();
|
||||
}
|
||||
if (_enabled) {
|
||||
MP4MuxerMemory::inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
//缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存
|
||||
return _clear_cache ? true : _enabled;
|
||||
}
|
||||
|
||||
void onAllTrackReady() {
|
||||
_media_src->setInitSegment(getInitSegment());
|
||||
}
|
||||
|
||||
protected:
|
||||
void onSegmentData(const string &string, uint32_t stamp, bool key_frame) override {
|
||||
if (string.empty()) {
|
||||
return;
|
||||
}
|
||||
FMP4Packet::Ptr packet = std::make_shared<FMP4Packet>(std::move(string));
|
||||
packet->time_stamp = stamp;
|
||||
_media_src->onWrite(packet, key_frame);
|
||||
}
|
||||
|
||||
private:
|
||||
bool _enabled = true;
|
||||
bool _clear_cache = false;
|
||||
FMP4MediaSource::Ptr _media_src;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
@ -13,7 +13,7 @@ namespace mediakit {
|
||||
|
||||
HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller){
|
||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
|
||||
}
|
||||
|
||||
HlsPlayer::~HlsPlayer() {}
|
||||
@ -63,6 +63,15 @@ void HlsPlayer::playNextTs(bool force){
|
||||
std::shared_ptr<Ticker> ticker(new Ticker);
|
||||
|
||||
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller(), false);
|
||||
|
||||
_http_ts_player->setOnCreateSocket([weakSelf](const EventPoller::Ptr &poller) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (strongSelf) {
|
||||
return strongSelf->createSocket();
|
||||
}
|
||||
return Socket::createSocket(poller, true);
|
||||
});
|
||||
|
||||
_http_ts_player->setOnDisconnect([weakSelf, ticker, ts_duration](const SockException &err) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
@ -84,6 +93,7 @@ void HlsPlayer::playNextTs(bool force){
|
||||
}, strongSelf->getPoller()));
|
||||
}
|
||||
});
|
||||
|
||||
_http_ts_player->setOnPacket([weakSelf](const char *data, uint64_t len) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
@ -94,9 +104,10 @@ void HlsPlayer::playNextTs(bool force){
|
||||
});
|
||||
|
||||
_http_ts_player->setMethod("GET");
|
||||
if(!(*this)[kNetAdapter].empty()) {
|
||||
if (!(*this)[kNetAdapter].empty()) {
|
||||
_http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]);
|
||||
}
|
||||
|
||||
_http_ts_player->sendRequest(_ts_list.front().url, 2 * _ts_list.front().duration);
|
||||
_ts_list.pop_front();
|
||||
}
|
||||
@ -254,11 +265,15 @@ void HlsPlayerImp::onAllTrackReady() {
|
||||
}
|
||||
|
||||
void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
||||
if(ex){
|
||||
if (ex) {
|
||||
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
|
||||
}else{
|
||||
} else {
|
||||
_frame_cache.clear();
|
||||
_stamp[TrackAudio].setRelativeStamp(0);
|
||||
_stamp[TrackVideo].setRelativeStamp(0);
|
||||
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
|
||||
_ticker.resetTime();
|
||||
setPlayPosition(0);
|
||||
|
||||
weak_ptr<HlsPlayerImp> weakSelf = dynamic_pointer_cast<HlsPlayerImp>(shared_from_this());
|
||||
//每50毫秒执行一次
|
||||
_timer = std::make_shared<Timer>(0.05, [weakSelf]() {
|
||||
@ -288,25 +303,47 @@ void HlsPlayerImp::inputFrame(const Frame::Ptr &frame) {
|
||||
//根据时间戳缓存frame
|
||||
_frame_cache.emplace(dts, Frame::getCacheAbleFrame(frame));
|
||||
|
||||
while (!_frame_cache.empty()) {
|
||||
if (_frame_cache.rbegin()->first - _frame_cache.begin()->first > 30 * 1000) {
|
||||
//缓存超过30秒,强制消费掉
|
||||
if (getBufferMS() > 30 * 1000) {
|
||||
//缓存超过30秒,强制消费至15秒(减少延时或内存占用)
|
||||
while (getBufferMS() > 15 * 1000) {
|
||||
MediaSink::inputFrame(_frame_cache.begin()->second);
|
||||
_frame_cache.erase(_frame_cache.begin());
|
||||
continue;
|
||||
}
|
||||
//缓存小于30秒
|
||||
break;
|
||||
//接着播放缓存中最早的帧
|
||||
setPlayPosition(_frame_cache.begin()->first);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t HlsPlayerImp::getPlayPosition(){
|
||||
return _ticker.elapsedTime() + _ticker_offset;
|
||||
}
|
||||
|
||||
int64_t HlsPlayerImp::getBufferMS(){
|
||||
if(_frame_cache.empty()){
|
||||
return 0;
|
||||
}
|
||||
return _frame_cache.rbegin()->first - _frame_cache.begin()->first;
|
||||
}
|
||||
|
||||
void HlsPlayerImp::setPlayPosition(int64_t pos){
|
||||
_ticker.resetTime();
|
||||
_ticker_offset = pos;
|
||||
}
|
||||
|
||||
void HlsPlayerImp::onTick() {
|
||||
auto it = _frame_cache.begin();
|
||||
while (it != _frame_cache.end()) {
|
||||
if (it->first > _ticker.elapsedTime()) {
|
||||
if (it->first > getPlayPosition()) {
|
||||
//这些帧还未到时间播放
|
||||
break;
|
||||
}
|
||||
|
||||
if (getBufferMS() < 3 * 1000) {
|
||||
//缓存小于3秒,那么降低定时器消费速度(让剩余的数据在3秒后消费完毕)
|
||||
//目的是为了防止定时器长时间干等后,数据瞬间消费完毕
|
||||
setPlayPosition(_frame_cache.begin()->first);
|
||||
}
|
||||
|
||||
//消费掉已经到期的帧
|
||||
MediaSink::inputFrame(it->second);
|
||||
it = _frame_cache.erase(it);
|
||||
|
@ -138,13 +138,19 @@ private:
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
void onShutdown(const SockException &ex) override;
|
||||
void onTick();
|
||||
|
||||
int64_t getPlayPosition();
|
||||
void setPlayPosition(int64_t pos);
|
||||
int64_t getBufferMS();
|
||||
|
||||
private:
|
||||
TSSegment::onSegment _on_ts;
|
||||
DecoderImp::Ptr _decoder;
|
||||
multimap<int64_t, Frame::Ptr> _frame_cache;
|
||||
Timer::Ptr _timer;
|
||||
int64_t _ticker_offset = 0;
|
||||
Ticker _ticker;
|
||||
Stamp _stamp[2];
|
||||
Timer::Ptr _timer;
|
||||
DecoderImp::Ptr _decoder;
|
||||
TSSegment::onSegment _on_ts;
|
||||
multimap<int64_t, Frame::Ptr> _frame_cache;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -135,7 +135,7 @@ Buffer::Ptr HttpFileBody::readData(uint32_t size) {
|
||||
//读到数据了
|
||||
ret->setSize(iRead);
|
||||
_offset += iRead;
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
//读取文件异常,文件真实长度小于声明长度
|
||||
_offset = _max_size;
|
||||
@ -146,7 +146,7 @@ Buffer::Ptr HttpFileBody::readData(uint32_t size) {
|
||||
//mmap模式
|
||||
auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
|
||||
_offset += size;
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
@ -100,7 +100,7 @@ void HttpClient::onConnect(const SockException &ex) {
|
||||
}
|
||||
|
||||
//先假设http客户端只会接收一点点数据(只接受http头,节省内存)
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||
|
||||
_totalBodySize = 0;
|
||||
_recvedBodySize = 0;
|
||||
@ -157,7 +157,7 @@ int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
|
||||
|
||||
if(_parser["Transfer-Encoding"] == "chunked"){
|
||||
//我们认为这种情况下后面应该有大量的数据过来,加大接收缓存提高性能
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
|
||||
//如果Transfer-Encoding字段等于chunked,则认为后续的content是不限制长度的
|
||||
_totalBodySize = -1;
|
||||
@ -185,9 +185,9 @@ int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
|
||||
_recvedBodySize = 0;
|
||||
if(_totalBodySize > 0){
|
||||
//根据_totalBodySize设置接收缓存大小
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(_totalBodySize + 1,256 * 1024)));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(MIN(_totalBodySize + 1,256 * 1024)));
|
||||
}else{
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
@ -27,7 +27,7 @@ static int kHlsCookieSecond = 60;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kHlsSuffix = "/hls.m3u8";
|
||||
|
||||
class HttpCookieAttachment{
|
||||
class HttpCookieAttachment {
|
||||
public:
|
||||
HttpCookieAttachment() {};
|
||||
~HttpCookieAttachment() {};
|
||||
@ -160,7 +160,7 @@ const string &HttpFileManager::getContentType(const char *name) {
|
||||
dot = strrchr(name, '.');
|
||||
static StrCaseMap mapType;
|
||||
static onceToken token([&]() {
|
||||
for (unsigned int i = 0; i < sizeof (s_mime_src) / sizeof (s_mime_src[0]); ++i) {
|
||||
for (unsigned int i = 0; i < sizeof(s_mime_src) / sizeof(s_mime_src[0]); ++i) {
|
||||
mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]);
|
||||
}
|
||||
});
|
||||
@ -183,8 +183,8 @@ static string searchIndexFile(const string &dir){
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"};
|
||||
if(indexSet.find(pDirent->d_name) != indexSet.end()){
|
||||
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
|
||||
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
|
||||
string ret = pDirent->d_name;
|
||||
closedir(pDir);
|
||||
return ret;
|
||||
@ -196,16 +196,16 @@ static string searchIndexFile(const string &dir){
|
||||
|
||||
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
|
||||
GET_CONFIG(bool, dirMenu, Http::kDirMenu);
|
||||
if(!dirMenu){
|
||||
if (!dirMenu) {
|
||||
//不允许浏览文件夹
|
||||
return false;
|
||||
}
|
||||
string strPathPrefix(strFullPath);
|
||||
string last_dir_name;
|
||||
if(strPathPrefix.back() == '/'){
|
||||
if (strPathPrefix.back() == '/') {
|
||||
strPathPrefix.pop_back();
|
||||
}else{
|
||||
last_dir_name = split(strPathPrefix,"/").back();
|
||||
} else {
|
||||
last_dir_name = split(strPathPrefix, "/").back();
|
||||
}
|
||||
|
||||
if (!File::is_dir(strPathPrefix.data())) {
|
||||
@ -249,24 +249,24 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
if (File::is_special_dir(pDirent->d_name)) {
|
||||
continue;
|
||||
}
|
||||
if(pDirent->d_name[0] == '.'){
|
||||
if (pDirent->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
setFile.emplace(pDirent->d_name);
|
||||
}
|
||||
int i = 0;
|
||||
for(auto &strFile :setFile ){
|
||||
for (auto &strFile :setFile) {
|
||||
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
||||
bool isDir = File::is_dir(strAbsolutePath.data());
|
||||
ss << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
if (!last_dir_name.empty()) {
|
||||
ss << last_dir_name << "/" << strFile;
|
||||
}else{
|
||||
} else {
|
||||
ss << strFile;
|
||||
}
|
||||
|
||||
if(isDir){
|
||||
if (isDir) {
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
@ -307,11 +307,16 @@ static bool end_of(const string &str, const string &substr){
|
||||
//拦截hls的播放请求
|
||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){
|
||||
Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err,"",kHlsCookieSecond);
|
||||
invoker(err, "", kHlsCookieSecond);
|
||||
};
|
||||
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,static_cast<SockInfo &>(sender));
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, mediaInfo, auth_invoker, static_cast<SockInfo &>(sender));
|
||||
if (!flag) {
|
||||
//未开启鉴权,那么允许播放
|
||||
auth_invoker("");
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
class SockInfoImp : public SockInfo{
|
||||
@ -320,23 +325,23 @@ public:
|
||||
SockInfoImp() = default;
|
||||
~SockInfoImp() override = default;
|
||||
|
||||
string get_local_ip() override{
|
||||
string get_local_ip() override {
|
||||
return _local_ip;
|
||||
}
|
||||
|
||||
uint16_t get_local_port() override{
|
||||
uint16_t get_local_port() override {
|
||||
return _local_port;
|
||||
}
|
||||
|
||||
string get_peer_ip() override{
|
||||
string get_peer_ip() override {
|
||||
return _peer_ip;
|
||||
}
|
||||
|
||||
uint16_t get_peer_port() override{
|
||||
uint16_t get_peer_port() override {
|
||||
return _peer_port;
|
||||
}
|
||||
|
||||
string getIdentifier() const override{
|
||||
string getIdentifier() const override {
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
@ -379,7 +384,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
//上次cookie是限定本目录
|
||||
if (attachment._err_msg.empty()) {
|
||||
//上次鉴权成功
|
||||
if(attachment._is_hls){
|
||||
if (attachment._is_hls) {
|
||||
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
||||
cookie->updateTime();
|
||||
cookie_from_header = false;
|
||||
@ -429,7 +434,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
attachment._err_msg = errMsg;
|
||||
//记录访问的是否为hls
|
||||
attachment._is_hls = is_hls;
|
||||
if(is_hls){
|
||||
if (is_hls) {
|
||||
//hls相关信息
|
||||
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
|
||||
//hls未查找MediaSource
|
||||
@ -437,13 +442,14 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
}
|
||||
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
|
||||
callback(errMsg, cookie);
|
||||
}else{
|
||||
} else {
|
||||
callback(errMsg, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
if (is_hls && emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender)) {
|
||||
if (is_hls) {
|
||||
//是hls的播放鉴权,拦截之
|
||||
emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -459,15 +465,15 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
* 发送404 Not Found
|
||||
*/
|
||||
static void sendNotFound(const HttpFileManager::invoker &cb) {
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
cb("404 Not Found","text/html",StrCaseMap(),std::make_shared<HttpStringBody>(notFound));
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
cb("404 Not Found", "text/html", StrCaseMap(), std::make_shared<HttpStringBody>(notFound));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接文件路径
|
||||
*/
|
||||
static string pathCat(const string &a, const string &b){
|
||||
if(a.back() == '/'){
|
||||
if (a.back() == '/') {
|
||||
return a + b;
|
||||
}
|
||||
return a + '/' + b;
|
||||
@ -490,7 +496,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_hls){
|
||||
if (is_hls) {
|
||||
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
|
||||
@ -500,7 +506,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
//判断是否有权限访问该文件
|
||||
canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if(!strongSession){
|
||||
if (!strongSession) {
|
||||
//http客户端已经断开,不需要回复
|
||||
return;
|
||||
}
|
||||
@ -508,6 +514,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
//文件鉴权失败
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
@ -517,11 +524,12 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
|
||||
StrCaseMap httpHeader;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||
if (cookie && file_exist) {
|
||||
cookie->getLock();
|
||||
auto lck = cookie->getLock();
|
||||
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
|
||||
if (is_hls) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
|
||||
@ -535,26 +543,47 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
if (!is_hls) {
|
||||
//不是hls,直接回复文件或404
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
} else {
|
||||
//是hls直播,判断是否存在
|
||||
bool have_find_media_src = false;
|
||||
if(cookie){
|
||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
||||
if(!have_find_media_src){
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//是hls直播,判断HLS直播流是否已经注册
|
||||
bool have_find_media_src = false;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
||||
if (!have_find_media_src) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
||||
}
|
||||
if(have_find_media_src){
|
||||
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
|
||||
}
|
||||
if (have_find_media_src) {
|
||||
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
//hls文件不存在,我们等待其生成并延后回复
|
||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
//尝试添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(0);
|
||||
}
|
||||
if (src && File::is_file(strFile.data())) {
|
||||
//流和m3u8文件都存在,那么直接返回文件
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
//hls文件不存在,我们等待其生成并延后回复
|
||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
||||
//hls已经生成或者超时后仍未生成,那么不管怎么样都返回客户端
|
||||
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
|
||||
if (!hls) {
|
||||
//流不存在,那么直接返回文件(相当于纯粹的HLS文件服务器,但是会挂起播放器15秒左右(用于等待HLS流的注册))
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
|
||||
//流存在,但是m3u8文件不存在,那么等待生成m3u8文件(HLS源注册后,并不会立即生成HLS文件,有人观看才会按需生成HLS文件)
|
||||
hls->waitForFile([response_file, cookie, cb, strFile, parser]() {
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -563,7 +592,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSe
|
||||
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -615,13 +644,13 @@ void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const Htt
|
||||
////////////////////////////////////HttpResponseInvokerImp//////////////////////////////////////
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
|
||||
if(_lambad){
|
||||
_lambad(codeOut,headerOut,body);
|
||||
if (_lambad) {
|
||||
_lambad(codeOut, headerOut, body);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{
|
||||
this->operator()(codeOut,headerOut,std::make_shared<HttpStringBody>(body));
|
||||
this->operator()(codeOut, headerOut, std::make_shared<HttpStringBody>(body));
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
|
||||
@ -629,23 +658,23 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
|
||||
if(!lambda){
|
||||
if (!lambda) {
|
||||
_lambad = nullptr;
|
||||
return;
|
||||
}
|
||||
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){
|
||||
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||
string str;
|
||||
if(body && body->remainSize()){
|
||||
if (body && body->remainSize()) {
|
||||
str = body->readData(body->remainSize())->toString();
|
||||
}
|
||||
lambda(codeOut,headerOut,str);
|
||||
lambda(codeOut, headerOut, str);
|
||||
};
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
const StrCaseMap &responseHeader,
|
||||
const string &filePath) const {
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap&>(responseHeader);
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
@ -654,8 +683,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
|
||||
if (!fp) {
|
||||
//打开文件失败
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
|
||||
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
||||
httpHeader["Content-Type"] = strContentType;
|
||||
@ -665,14 +694,14 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||
int64_t iRangeStart = 0;
|
||||
int64_t iRangeEnd = 0 ;
|
||||
int64_t iRangeEnd = 0;
|
||||
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
|
||||
|
||||
const char *pcHttpResult = NULL;
|
||||
if (strRange.size() == 0) {
|
||||
//全部下载
|
||||
pcHttpResult = "200 OK";
|
||||
iRangeEnd = fileSize - 1;
|
||||
iRangeEnd = fileSize - 1;
|
||||
} else {
|
||||
//分节下载
|
||||
pcHttpResult = "206 Partial Content";
|
||||
|
@ -8,15 +8,9 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <dirent.h>
|
||||
#endif //!defined(_WIN32)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "strCoding.h"
|
||||
#include "HttpSession.h"
|
||||
@ -96,10 +90,10 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||
}
|
||||
|
||||
void HttpSession::onError(const SockException& err) {
|
||||
if(_is_flv_stream){
|
||||
if(_is_live_stream){
|
||||
uint64_t duration = _ticker.createdTime()/1000;
|
||||
//flv播放器
|
||||
WarnP(this) << "FLV播放器("
|
||||
//flv/ts播放器
|
||||
WarnP(this) << "FLV/TS/FMP4播放器("
|
||||
<< _mediaInfo._vhost << "/"
|
||||
<< _mediaInfo._app << "/"
|
||||
<< _mediaInfo._streamid
|
||||
@ -107,8 +101,8 @@ void HttpSession::onError(const SockException& err) {
|
||||
<< ",耗时(s):" << duration;
|
||||
|
||||
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
|
||||
if(_ui64TotalBytes > iFlowThreshold * 1024){
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration , true, static_cast<SockInfo &>(*this));
|
||||
if(_total_bytes_usage > iFlowThreshold * 1024){
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration , true, static_cast<SockInfo &>(*this));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -132,7 +126,7 @@ void HttpSession::onManager() {
|
||||
|
||||
bool HttpSession::checkWebSocket(){
|
||||
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
||||
if(Sec_WebSocket_Key.empty()){
|
||||
if (Sec_WebSocket_Key.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
||||
@ -141,99 +135,95 @@ bool HttpSession::checkWebSocket(){
|
||||
headerOut["Upgrade"] = "websocket";
|
||||
headerOut["Connection"] = "Upgrade";
|
||||
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
|
||||
if(!_parser["Sec-WebSocket-Protocol"].empty()){
|
||||
if (!_parser["Sec-WebSocket-Protocol"].empty()) {
|
||||
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
||||
}
|
||||
|
||||
auto res_cb = [this,headerOut](){
|
||||
_flv_over_websocket = true;
|
||||
sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr, true);
|
||||
auto res_cb = [this, headerOut]() {
|
||||
_live_over_websocket = true;
|
||||
sendResponse("101 Switching Protocols", false, nullptr, headerOut, nullptr, true);
|
||||
};
|
||||
|
||||
//判断是否为websocket-flv
|
||||
if(checkLiveFlvStream(res_cb)){
|
||||
if (checkLiveStreamFlv(res_cb)) {
|
||||
//这里是websocket-flv直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接
|
||||
if(!onWebSocketConnect(_parser)){
|
||||
sendResponse("501 Not Implemented",true, nullptr, headerOut);
|
||||
//判断是否为websocket-ts
|
||||
if (checkLiveStreamTS(res_cb)) {
|
||||
//这里是websocket-ts直播请求
|
||||
return true;
|
||||
}
|
||||
sendResponse("101 Switching Protocols",false, nullptr,headerOut);
|
||||
|
||||
//判断是否为websocket-fmp4
|
||||
if (checkLiveStreamFMP4(res_cb)) {
|
||||
//这里是websocket-fmp4直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//这是普通的websocket连接
|
||||
if (!onWebSocketConnect(_parser)) {
|
||||
sendResponse("501 Not Implemented", true, nullptr, headerOut);
|
||||
return true;
|
||||
}
|
||||
sendResponse("101 Switching Protocols", false, nullptr, headerOut, nullptr, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
||||
bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
auto pos = strrchr(_parser.Url().data(),'.');
|
||||
if(!pos){
|
||||
//未找到".flv"后缀
|
||||
return false;
|
||||
}
|
||||
if(strcasecmp(pos,".flv") != 0){
|
||||
//未找到".flv"后缀
|
||||
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
auto pos = strcasestr(_parser.Url().data(), url_suffix.data());
|
||||
if (!pos || pos + url_suffix.size() != 1 + &_parser.Url().back()) {
|
||||
//未找到后缀
|
||||
return false;
|
||||
}
|
||||
|
||||
//这是个.flv的流
|
||||
_mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
|
||||
if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
|
||||
//这是个符合后缀的直播的流
|
||||
_mediaInfo.parse(schema + "://" + _parser["Host"] + _parser.FullUrl());
|
||||
if (_mediaInfo._app.empty() || _mediaInfo._streamid.size() < url_suffix.size() + 1) {
|
||||
//url不合法
|
||||
return false;
|
||||
}
|
||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
//去除后缀
|
||||
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
//流id去除后缀
|
||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - url_suffix.size());
|
||||
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
|
||||
//鉴权结果回调
|
||||
auto onRes = [cb, weakSelf, bClose](const string &err){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
auto onRes = [cb, weak_self, close_flag](const string &err) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
|
||||
if(!err.empty()){
|
||||
if (!err.empty()) {
|
||||
//播放鉴权失败
|
||||
strongSelf->sendResponse("401 Unauthorized", bClose, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
strong_self->sendResponse("401 Unauthorized", close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
return;
|
||||
}
|
||||
|
||||
//异步查找rtmp流
|
||||
MediaSource::findAsync(strongSelf->_mediaInfo, strongSelf, [weakSelf, bClose, cb](const MediaSource::Ptr &src) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//异步查找直播流
|
||||
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;
|
||||
}
|
||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
if (!rtmp_src) {
|
||||
if (!src) {
|
||||
//未找到该流
|
||||
strongSelf->sendNotFound(bClose);
|
||||
strong_self->sendNotFound(close_flag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cb) {
|
||||
//找到rtmp源,发送http头,负载后续发送
|
||||
strongSelf->sendResponse("200 OK", false, "video/x-flv", KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//http-flv直播牺牲延时提升发送性能
|
||||
strongSelf->setSocketFlags();
|
||||
strongSelf->start(strongSelf->getPoller(), rtmp_src);
|
||||
strongSelf->_is_flv_stream = true;
|
||||
strong_self->_is_live_stream = true;
|
||||
//触发回调
|
||||
cb(src);
|
||||
});
|
||||
};
|
||||
|
||||
Broadcast::AuthInvoker invoker = [weakSelf, onRes](const string &err) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
Broadcast::AuthInvoker invoker = [weak_self, onRes](const string &err) {
|
||||
auto strongSelf = weak_self.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
@ -250,34 +240,142 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
return true;
|
||||
}
|
||||
|
||||
//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头,负载后续发送
|
||||
sendResponse("200 OK", false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
|
||||
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
_fmp4_reader = fmp4_src->getRing()->attach(getPoller());
|
||||
_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"));
|
||||
});
|
||||
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
int size = fmp4_list->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){
|
||||
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头,负载后续发送
|
||||
sendResponse("200 OK", false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
_ts_reader = ts_src->getRing()->attach(getPoller());
|
||||
_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"));
|
||||
});
|
||||
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
int size = ts_list->size();
|
||||
ts_list->for_each([&](const TSPacket::Ptr &ts) {
|
||||
strong_self->onWrite(ts, ++i == size);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
|
||||
return checkLiveStream(RTMP_SCHEMA, ".flv", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
assert(rtmp_src);
|
||||
if (!cb) {
|
||||
//找到源,发送http头,负载后续发送
|
||||
sendResponse("200 OK", false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
//直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
start(getPoller(), rtmp_src);
|
||||
});
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
Handle_Req_GET_l(content_len, true);
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
|
||||
//先看看是否为WebSocket请求
|
||||
if(checkWebSocket()){
|
||||
if (checkWebSocket()) {
|
||||
content_len = -1;
|
||||
_contentCallBack = [this](const char *data,uint64_t len){
|
||||
WebSocketSplitter::decode((uint8_t *)data,len);
|
||||
_contentCallBack = [this](const char *data, uint64_t len) {
|
||||
WebSocketSplitter::decode((uint8_t *) data, len);
|
||||
//_contentCallBack是可持续的,后面还要处理后续数据
|
||||
return true;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if(emitHttpEvent(false)){
|
||||
if (emitHttpEvent(false)) {
|
||||
//拦截http api事件
|
||||
return;
|
||||
}
|
||||
|
||||
if(checkLiveFlvStream()){
|
||||
if (checkLiveStreamFlv()) {
|
||||
//拦截http-flv播放器
|
||||
return;
|
||||
}
|
||||
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
if (checkLiveStreamTS()) {
|
||||
//拦截http-ts播放器
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamFMP4()) {
|
||||
//拦截http-fmp4播放器
|
||||
return;
|
||||
}
|
||||
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type,
|
||||
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
||||
@ -389,7 +487,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
const char *pcContentType,
|
||||
const HttpSession::KeyValue &header,
|
||||
const HttpBody::Ptr &body,
|
||||
bool is_http_flv ){
|
||||
bool no_content_length ){
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||
|
||||
@ -400,7 +498,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
size = body->remainSize();
|
||||
}
|
||||
|
||||
if(is_http_flv){
|
||||
if(no_content_length){
|
||||
//http-flv直播是Keep-Alive类型
|
||||
bClose = false;
|
||||
}else if(size >= INT64_MAX){
|
||||
@ -425,7 +523,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
headerOut.emplace(kAccessControlAllowCredentials, "true");
|
||||
}
|
||||
|
||||
if(!is_http_flv && size >= 0 && size < INT64_MAX){
|
||||
if(!no_content_length && size >= 0 && size < INT64_MAX){
|
||||
//文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||
headerOut[kContentLength] = to_string(size);
|
||||
}
|
||||
@ -475,7 +573,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
|
||||
//发送http body
|
||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(),body,bClose);
|
||||
_sock->setOnFlush([data](){
|
||||
getSock()->setOnFlush([data](){
|
||||
return AsyncSender::onSocketFlushed(data);
|
||||
});
|
||||
AsyncSender::onSocketFlushed(data);
|
||||
@ -542,10 +640,10 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
||||
|
||||
//根据Content-Length设置接收缓存大小
|
||||
if(totalContentLen > 0){
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
||||
}else{
|
||||
//不定长度的Content-Length
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
}
|
||||
|
||||
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
||||
@ -609,7 +707,7 @@ void HttpSession::setSocketFlags(){
|
||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||
if(mergeWriteMS > 0) {
|
||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||
}
|
||||
@ -622,29 +720,44 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||
}
|
||||
|
||||
_ticker.resetTime();
|
||||
if(!_flv_over_websocket){
|
||||
_ui64TotalBytes += buffer->size();
|
||||
if (!_live_over_websocket) {
|
||||
_total_bytes_usage += buffer->size();
|
||||
send(buffer);
|
||||
}else{
|
||||
} else {
|
||||
WebSocketHeader header;
|
||||
header._fin = true;
|
||||
header._reserved = 0;
|
||||
header._opcode = WebSocketHeader::BINARY;
|
||||
header._mask_flag = false;
|
||||
WebSocketSplitter::encode(header,buffer);
|
||||
WebSocketSplitter::encode(header, buffer);
|
||||
}
|
||||
|
||||
if(flush){
|
||||
if (flush) {
|
||||
//本次刷新缓存后,下次不用刷新缓存
|
||||
HttpSession::setSendFlushFlag(false);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
||||
_ui64TotalBytes += buffer->size();
|
||||
_total_bytes_usage += buffer->size();
|
||||
send(buffer);
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
|
||||
WebSocketHeader& header = const_cast<WebSocketHeader&>(header_in);
|
||||
header._mask_flag = false;
|
||||
|
||||
switch (header._opcode) {
|
||||
case WebSocketHeader::CLOSE: {
|
||||
encode(header, nullptr);
|
||||
shutdown(SockException(Err_shutdown, "recv close request from client"));
|
||||
break;
|
||||
}
|
||||
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onDetach() {
|
||||
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "WebSocketSplitter.h"
|
||||
#include "HttpCookieManager.h"
|
||||
#include "HttpFileManager.h"
|
||||
#include "TS/TSMediaSource.h"
|
||||
#include "FMP4/FMP4MediaSource.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
@ -47,6 +49,7 @@ public:
|
||||
void onError(const SockException &err) override;
|
||||
void onManager() override;
|
||||
static string urlDecode(const string &str);
|
||||
|
||||
protected:
|
||||
//FlvMuxer override
|
||||
void onWrite(const Buffer::Ptr &data, bool flush) override ;
|
||||
@ -90,35 +93,49 @@ protected:
|
||||
* @param buffer websocket协议数据
|
||||
*/
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
*/
|
||||
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override;
|
||||
|
||||
private:
|
||||
void Handle_Req_GET(int64_t &content_len);
|
||||
void Handle_Req_GET_l(int64_t &content_len, bool sendBody);
|
||||
void Handle_Req_POST(int64_t &content_len);
|
||||
void Handle_Req_HEAD(int64_t &content_len);
|
||||
|
||||
bool checkLiveFlvStream(const function<void()> &cb = nullptr);
|
||||
bool checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb);
|
||||
|
||||
bool checkLiveStreamFlv(const function<void()> &cb = nullptr);
|
||||
bool checkLiveStreamTS(const function<void()> &cb = nullptr);
|
||||
bool checkLiveStreamFMP4(const function<void()> &fmp4_list = nullptr);
|
||||
|
||||
bool checkWebSocket();
|
||||
bool emitHttpEvent(bool doInvoke);
|
||||
void urlDecode(Parser &parser);
|
||||
void sendNotFound(bool bClose);
|
||||
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
||||
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
||||
const HttpBody::Ptr &body = nullptr,bool is_http_flv = false);
|
||||
const HttpBody::Ptr &body = nullptr, bool no_content_length = false);
|
||||
|
||||
//设置socket标志
|
||||
void setSocketFlags();
|
||||
|
||||
private:
|
||||
bool _is_live_stream = false;
|
||||
bool _live_over_websocket = false;
|
||||
//消耗的总流量
|
||||
uint64_t _total_bytes_usage = 0;
|
||||
string _origin;
|
||||
Parser _parser;
|
||||
Ticker _ticker;
|
||||
//消耗的总流量
|
||||
uint64_t _ui64TotalBytes = 0;
|
||||
//flv over http
|
||||
MediaInfo _mediaInfo;
|
||||
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
||||
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
||||
//处理content数据的callback
|
||||
function<bool (const char *data,uint64_t len) > _contentCallBack;
|
||||
bool _flv_over_websocket = false;
|
||||
bool _is_flv_stream = false;
|
||||
};
|
||||
|
||||
|
||||
|
@ -12,9 +12,9 @@
|
||||
namespace mediakit {
|
||||
|
||||
HttpTSPlayer::HttpTSPlayer(const EventPoller::Ptr &poller, bool split_ts){
|
||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
_split_ts = split_ts;
|
||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
|
||||
}
|
||||
|
||||
HttpTSPlayer::~HttpTSPlayer() {}
|
||||
@ -25,8 +25,8 @@ int64_t HttpTSPlayer::onResponseHeader(const string &status, const HttpClient::H
|
||||
shutdown(SockException(Err_other, StrPrinter << "bad http status code:" + status));
|
||||
return 0;
|
||||
}
|
||||
auto contet_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"];
|
||||
if (contet_type.find("video/mp2t") == 0 || contet_type.find("video/mpeg") == 0) {
|
||||
auto content_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"];
|
||||
if (content_type.find("video/mp2t") == 0 || content_type.find("video/mpeg") == 0) {
|
||||
_is_ts_content = true;
|
||||
}
|
||||
|
||||
|
@ -38,11 +38,10 @@ public:
|
||||
template<typename ...ArgsType>
|
||||
ClientTypeImp(ArgsType &&...args): ClientType(std::forward<ArgsType>(args)...){}
|
||||
~ClientTypeImp() override {};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 发送前拦截并打包为websocket协议
|
||||
* @param buf
|
||||
* @return
|
||||
*/
|
||||
int send(const Buffer::Ptr &buf) override{
|
||||
if(_beforeSendCB){
|
||||
@ -50,6 +49,7 @@ protected:
|
||||
}
|
||||
return ClientType::send(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置发送数据截取回调函数
|
||||
* @param cb 截取回调函数
|
||||
@ -57,6 +57,7 @@ protected:
|
||||
void setOnBeforeSendCB(const onBeforeSendCB &cb){
|
||||
_beforeSendCB = cb;
|
||||
}
|
||||
|
||||
private:
|
||||
onBeforeSendCB _beforeSendCB;
|
||||
};
|
||||
@ -73,7 +74,7 @@ public:
|
||||
|
||||
HttpWsClient(ClientTypeImp<ClientType,DataType> &delegate) : _delegate(delegate){
|
||||
_Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false)));
|
||||
_poller = delegate.getPoller();
|
||||
setPoller(delegate.getPoller());
|
||||
}
|
||||
~HttpWsClient(){}
|
||||
|
||||
@ -108,6 +109,7 @@ public:
|
||||
header._mask_flag = true;
|
||||
WebSocketSplitter::encode(header, nullptr);
|
||||
}
|
||||
|
||||
protected:
|
||||
//HttpClientImp override
|
||||
|
||||
@ -124,6 +126,8 @@ protected:
|
||||
if(Sec_WebSocket_Accept == const_cast<HttpHeader &>(headers)["Sec-WebSocket-Accept"]){
|
||||
//success
|
||||
onWebSocketException(SockException());
|
||||
//防止ws服务器返回Content-Length
|
||||
const_cast<HttpHeader &>(headers).erase("Content-Length");
|
||||
//后续全是websocket负载数据
|
||||
return -1;
|
||||
}
|
||||
@ -180,7 +184,6 @@ protected:
|
||||
|
||||
/**
|
||||
* tcp连接结果
|
||||
* @param ex
|
||||
*/
|
||||
void onConnect(const SockException &ex) override{
|
||||
if(ex){
|
||||
@ -194,7 +197,6 @@ protected:
|
||||
|
||||
/**
|
||||
* tcp连接断开
|
||||
* @param ex
|
||||
*/
|
||||
void onErr(const SockException &ex) override{
|
||||
//tcp断开或者shutdown导致的断开
|
||||
@ -208,7 +210,7 @@ protected:
|
||||
* @param header 数据包头
|
||||
*/
|
||||
void onWebSocketDecodeHeader(const WebSocketHeader &header) override{
|
||||
_payload.clear();
|
||||
_payload_section.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,10 +221,9 @@ protected:
|
||||
* @param recved 已接收数据长度(包含本次数据长度),等于header._payload_len时则接受完毕
|
||||
*/
|
||||
void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) override{
|
||||
_payload.append((char *)ptr,len);
|
||||
_payload_section.append((char *)ptr,len);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
@ -238,28 +239,46 @@ protected:
|
||||
//服务器主动关闭
|
||||
WebSocketSplitter::encode(header,nullptr);
|
||||
shutdown(SockException(Err_eof,"websocket server close the connection"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketHeader::PING:{
|
||||
//心跳包
|
||||
header._opcode = WebSocketHeader::PONG;
|
||||
WebSocketSplitter::encode(header,std::make_shared<BufferString>(std::move(_payload)));
|
||||
}
|
||||
WebSocketSplitter::encode(header,std::make_shared<BufferString>(std::move(_payload_section)));
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:{
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:
|
||||
case WebSocketHeader::TEXT:
|
||||
case WebSocketHeader::BINARY:{
|
||||
//接收完毕websocket数据包,触发onRecv事件
|
||||
_delegate.onRecv(std::make_shared<BufferString>(std::move(_payload)));
|
||||
if (!header._fin) {
|
||||
//还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (_payload_cache.size() < MAX_WS_PACKET) {
|
||||
//还有内存容量缓存分片数据
|
||||
break;
|
||||
}
|
||||
//分片缓存太大,需要清空
|
||||
}
|
||||
|
||||
//最后一个包
|
||||
if (_payload_cache.empty()) {
|
||||
//这个包是唯一个分片
|
||||
_delegate.onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
|
||||
break;
|
||||
}
|
||||
|
||||
//这个包由多个分片组成
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
_delegate.onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
|
||||
_payload_cache.clear();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
_payload.clear();
|
||||
_payload_section.clear();
|
||||
header._mask_flag = flag;
|
||||
}
|
||||
|
||||
@ -271,6 +290,7 @@ protected:
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||
HttpClientImp::send(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
void onWebSocketException(const SockException &ex){
|
||||
if(!ex){
|
||||
@ -292,7 +312,7 @@ private:
|
||||
});
|
||||
|
||||
//设置sock,否则shutdown等接口都无效
|
||||
_delegate.setSock(HttpClientImp::_sock);
|
||||
_delegate.setSock(HttpClientImp::getSock());
|
||||
//触发连接成功事件
|
||||
_delegate.onConnect(ex);
|
||||
//拦截websocket数据接收
|
||||
@ -319,10 +339,10 @@ private:
|
||||
string _Sec_WebSocket_Key;
|
||||
function<void(const char *data, int len)> _onRecv;
|
||||
ClientTypeImp<ClientType,DataType> &_delegate;
|
||||
string _payload;
|
||||
string _payload_section;
|
||||
string _payload_cache;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tcp客户端转WebSocket客户端模板,
|
||||
* 通过该模板,开发者再不修改TcpClient派生类任何代码的情况下快速实现WebSocket协议的包装
|
||||
@ -365,6 +385,7 @@ public:
|
||||
void startWebSocket(const string &ws_url,float fTimeOutSec = 3){
|
||||
_wsClient->startWsClient(ws_url,fTimeOutSec);
|
||||
}
|
||||
|
||||
private:
|
||||
typename HttpWsClient<ClientType,DataType>::Ptr _wsClient;
|
||||
};
|
||||
|
@ -78,7 +78,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 通过该模板类可以透明化WebSocket协议,
|
||||
* 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等
|
||||
@ -107,8 +106,9 @@ public:
|
||||
|
||||
void attachServer(const TcpServer &server) override{
|
||||
HttpSessionType::attachServer(server);
|
||||
_weakServer = const_cast<TcpServer &>(server).shared_from_this();
|
||||
_weak_server = const_cast<TcpServer &>(server).shared_from_this();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* websocket客户端连接上事件
|
||||
@ -117,12 +117,12 @@ protected:
|
||||
*/
|
||||
bool onWebSocketConnect(const Parser &header) override{
|
||||
//创建websocket session类
|
||||
_session = _creator(header, *this,HttpSessionType::_sock);
|
||||
_session = _creator(header, *this,HttpSessionType::getSock());
|
||||
if(!_session){
|
||||
//此url不允许创建websocket连接
|
||||
return false;
|
||||
}
|
||||
auto strongServer = _weakServer.lock();
|
||||
auto strongServer = _weak_server.lock();
|
||||
if(strongServer){
|
||||
_session->attachServer(*strongServer);
|
||||
}
|
||||
@ -145,24 +145,20 @@ protected:
|
||||
//允许websocket客户端
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始收到一个webSocket数据包
|
||||
* @param packet
|
||||
*/
|
||||
void onWebSocketDecodeHeader(const WebSocketHeader &packet) override{
|
||||
//新包,原来的包残余数据清空掉
|
||||
_remian_data.clear();
|
||||
_payload_section.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到websocket数据包负载
|
||||
* @param packet
|
||||
* @param ptr
|
||||
* @param len
|
||||
* @param recved
|
||||
*/
|
||||
void onWebSocketDecodePayload(const WebSocketHeader &packet,const uint8_t *ptr,uint64_t len,uint64_t recved) override {
|
||||
_remian_data.append((char *)ptr,len);
|
||||
_payload_section.append((char *)ptr,len);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,39 +173,60 @@ protected:
|
||||
switch (header._opcode){
|
||||
case WebSocketHeader::CLOSE:{
|
||||
HttpSessionType::encode(header,nullptr);
|
||||
}
|
||||
HttpSessionType::shutdown(SockException(Err_shutdown, "recv close request from client"));
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketHeader::PING:{
|
||||
header._opcode = WebSocketHeader::PONG;
|
||||
HttpSessionType::encode(header,std::make_shared<BufferString>(_remian_data));
|
||||
}
|
||||
HttpSessionType::encode(header,std::make_shared<BufferString>(_payload_section));
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:{
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case WebSocketHeader::CONTINUATION:
|
||||
case WebSocketHeader::TEXT:
|
||||
case WebSocketHeader::BINARY:{
|
||||
_session->onRecv(std::make_shared<BufferString>(_remian_data));
|
||||
if (!header._fin) {
|
||||
//还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (_payload_cache.size() < MAX_WS_PACKET) {
|
||||
//还有内存容量缓存分片数据
|
||||
break;
|
||||
}
|
||||
//分片缓存太大,需要清空
|
||||
}
|
||||
|
||||
//最后一个包
|
||||
if (_payload_cache.empty()) {
|
||||
//这个包是唯一个分片
|
||||
_session->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
|
||||
break;
|
||||
}
|
||||
|
||||
//这个包由多个分片组成
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
_session->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
|
||||
_payload_cache.clear();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
_remian_data.clear();
|
||||
_payload_section.clear();
|
||||
header._mask_flag = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
* @param buffer
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
*/
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||
HttpSessionType::send(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
string _remian_data;
|
||||
weak_ptr<TcpServer> _weakServer;
|
||||
string _payload_cache;
|
||||
string _payload_section;
|
||||
weak_ptr<TcpServer> _weak_server;
|
||||
TcpSession::Ptr _session;
|
||||
Creator _creator;
|
||||
};
|
||||
|
@ -16,10 +16,12 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "Network/Buffer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
//websocket组合包最大不得超过4MB(防止内存爆炸)
|
||||
#define MAX_WS_PACKET (4 * 1024 * 1024)
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class WebSocketHeader {
|
||||
@ -44,6 +46,7 @@ public:
|
||||
CONTROL_RSVF = 0xF
|
||||
} Type;
|
||||
public:
|
||||
|
||||
WebSocketHeader() : _mask(4){
|
||||
//获取_mask内部buffer的内存地址,该内存是malloc开辟的,地址为随机
|
||||
uint64_t ptr = (uint64_t)(&_mask[0]);
|
||||
@ -51,6 +54,7 @@ public:
|
||||
_mask.assign((uint8_t*)(&ptr), (uint8_t*)(&ptr) + 4);
|
||||
}
|
||||
virtual ~WebSocketHeader(){}
|
||||
|
||||
public:
|
||||
bool _fin;
|
||||
uint8_t _reserved;
|
||||
@ -60,6 +64,26 @@ public:
|
||||
vector<uint8_t > _mask;
|
||||
};
|
||||
|
||||
//websocket协议收到的字符串类型缓存,用户协议层获取该数据传输的方式
|
||||
class WebSocketBuffer : public BufferString {
|
||||
public:
|
||||
typedef std::shared_ptr<WebSocketBuffer> Ptr;
|
||||
|
||||
template<typename ...ARGS>
|
||||
WebSocketBuffer(WebSocketHeader::Type headType, bool fin, ARGS &&...args)
|
||||
: _head_type(headType), _fin(fin), BufferString(std::forward<ARGS>(args)...) {}
|
||||
|
||||
~WebSocketBuffer() override {}
|
||||
|
||||
WebSocketHeader::Type headType() const { return _head_type; }
|
||||
|
||||
bool isFinished() const { return _fin; };
|
||||
|
||||
private:
|
||||
WebSocketHeader::Type _head_type;
|
||||
bool _fin;
|
||||
};
|
||||
|
||||
class WebSocketSplitter : public WebSocketHeader{
|
||||
public:
|
||||
WebSocketSplitter(){}
|
||||
@ -80,6 +104,7 @@ public:
|
||||
* @param buffer 负载数据
|
||||
*/
|
||||
void encode(const WebSocketHeader &header,const Buffer::Ptr &buffer);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePayload回调
|
||||
@ -96,7 +121,6 @@ protected:
|
||||
*/
|
||||
virtual void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) {};
|
||||
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
@ -109,8 +133,10 @@ protected:
|
||||
* @param len 数据指针长度
|
||||
*/
|
||||
virtual void onWebSocketEncodeData(const Buffer::Ptr &buffer){};
|
||||
|
||||
private:
|
||||
void onPayloadData(uint8_t *data, uint64_t len);
|
||||
|
||||
private:
|
||||
string _remain_data;
|
||||
int _mask_offset = 0;
|
||||
|
@ -17,31 +17,43 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) {
|
||||
_poller = poller;
|
||||
if(!_poller){
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
|
||||
MediaPlayer::~MediaPlayer() {
|
||||
}
|
||||
void MediaPlayer::play(const string &strUrl) {
|
||||
_delegate = PlayerBase::createPlayer(_poller,strUrl);
|
||||
|
||||
static void setOnCreateSocket_l(const std::shared_ptr<PlayerBase> &delegate, const Socket::onCreateSocket &cb){
|
||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||
if (helper) {
|
||||
helper->setOnCreateSocket(cb);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer::play(const string &url) {
|
||||
_delegate = PlayerBase::createPlayer(_poller, url);
|
||||
assert(_delegate);
|
||||
setOnCreateSocket_l(_delegate, _on_create_socket);
|
||||
_delegate->setOnShutdown(_shutdownCB);
|
||||
_delegate->setOnPlayResult(_playResultCB);
|
||||
_delegate->setOnResume(_resumeCB);
|
||||
_delegate->setMediaSouce(_pMediaSrc);
|
||||
_delegate->mINI::operator=(*this);
|
||||
_delegate->play(strUrl);
|
||||
_delegate->play(url);
|
||||
}
|
||||
|
||||
EventPoller::Ptr MediaPlayer::getPoller(){
|
||||
return _poller;
|
||||
}
|
||||
|
||||
void MediaPlayer::pause(bool bPause) {
|
||||
void MediaPlayer::setOnCreateSocket(Socket::onCreateSocket cb){
|
||||
setOnCreateSocket_l(_delegate, cb);
|
||||
_on_create_socket = std::move(cb);
|
||||
}
|
||||
|
||||
void MediaPlayer::pause(bool pause) {
|
||||
if (_delegate) {
|
||||
_delegate->pause(bPause);
|
||||
_delegate->pause(pause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,15 @@ public:
|
||||
|
||||
MediaPlayer(const EventPoller::Ptr &poller = nullptr);
|
||||
virtual ~MediaPlayer();
|
||||
void play(const string &strUrl) override;
|
||||
void pause(bool bPause) override;
|
||||
void play(const string &url) override;
|
||||
void pause(bool pause) override;
|
||||
void teardown() override;
|
||||
EventPoller::Ptr getPoller();
|
||||
void setOnCreateSocket(Socket::onCreateSocket cb);
|
||||
|
||||
private:
|
||||
EventPoller::Ptr _poller;
|
||||
Socket::onCreateSocket _on_create_socket;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -105,7 +105,7 @@ vector<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
|
||||
ret.emplace_back(_audioTrack);
|
||||
}
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
float Demuxer::getDuration() const {
|
||||
|
@ -46,31 +46,23 @@ static uint8_t s_mute_adts[] = {0xff, 0xf1, 0x6c, 0x40, 0x2d, 0x3f, 0xfc, 0x00,
|
||||
#define MUTE_ADTS_DATA_LEN sizeof(s_mute_adts)
|
||||
#define MUTE_ADTS_DATA_MS 130
|
||||
|
||||
PlayerProxy::PlayerProxy(const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strSrc,
|
||||
bool bEnableRtsp,
|
||||
bool bEnableRtmp,
|
||||
bool bEnableHls,
|
||||
bool bEnableMp4,
|
||||
int iRetryCount,
|
||||
const EventPoller::Ptr &poller) : MediaPlayer(poller){
|
||||
_strVhost = strVhost;
|
||||
_strApp = strApp;
|
||||
_strSrc = strSrc;
|
||||
_bEnableRtsp = bEnableRtsp;
|
||||
_bEnableRtmp = bEnableRtmp;
|
||||
_bEnableHls = bEnableHls;
|
||||
_bEnableMp4 = bEnableMp4;
|
||||
_iRetryCount = iRetryCount;
|
||||
PlayerProxy::PlayerProxy(const string &vhost, const string &app, const string &stream_id,
|
||||
bool enable_hls, bool enable_mp4, int retry_count, const EventPoller::Ptr &poller)
|
||||
: MediaPlayer(poller) {
|
||||
_vhost = vhost;
|
||||
_app = app;
|
||||
_stream_id = stream_id;
|
||||
_enable_hls = enable_hls;
|
||||
_enable_mp4 = enable_mp4;
|
||||
_retry_count = retry_count;
|
||||
}
|
||||
|
||||
void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb){
|
||||
_playCB = cb;
|
||||
_on_play = cb;
|
||||
}
|
||||
|
||||
void PlayerProxy::setOnClose(const function<void()> &cb){
|
||||
_onClose = cb;
|
||||
_on_close = cb;
|
||||
}
|
||||
|
||||
void PlayerProxy::play(const string &strUrlTmp) {
|
||||
@ -82,16 +74,16 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(strongSelf->_playCB) {
|
||||
strongSelf->_playCB(err);
|
||||
strongSelf->_playCB = nullptr;
|
||||
if(strongSelf->_on_play) {
|
||||
strongSelf->_on_play(err);
|
||||
strongSelf->_on_play = nullptr;
|
||||
}
|
||||
|
||||
if(!err) {
|
||||
// 播放成功
|
||||
*piFailedCnt = 0;//连续播放失败次数清0
|
||||
strongSelf->onPlaySuccess();
|
||||
}else if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
||||
}else if(*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||
// 播放失败,延时重试播放
|
||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||
}
|
||||
@ -101,21 +93,21 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
if(strongSelf->_mediaMuxer) {
|
||||
auto tracks = strongSelf->getTracks(false);
|
||||
if(strongSelf->_muxer) {
|
||||
auto tracks = strongSelf->MediaPlayer::getTracks(false);
|
||||
for (auto & track : tracks){
|
||||
track->delDelegate(strongSelf->_mediaMuxer.get());
|
||||
track->delDelegate(strongSelf->_muxer.get());
|
||||
}
|
||||
|
||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||
if (resetWhenRePlay) {
|
||||
strongSelf->_mediaMuxer.reset();
|
||||
strongSelf->_muxer.reset();
|
||||
} else {
|
||||
strongSelf->_mediaMuxer->resetTracks();
|
||||
strongSelf->_muxer->resetTracks();
|
||||
}
|
||||
}
|
||||
//播放异常中断,延时重试播放
|
||||
if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
||||
if(*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||
}
|
||||
});
|
||||
@ -125,14 +117,12 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
||||
//rtsp拉流
|
||||
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
||||
if(directProxy && _bEnableRtsp){
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
|
||||
if(directProxy){
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_vhost, _app, _stream_id);
|
||||
}
|
||||
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
||||
//rtmp拉流
|
||||
if(_bEnableRtmp){
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
|
||||
}
|
||||
//rtmp拉流,rtmp强制直接代理
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_vhost, _app, _stream_id);
|
||||
}
|
||||
if(mediaSource){
|
||||
setMediaSouce(mediaSource);
|
||||
@ -143,6 +133,7 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
PlayerProxy::~PlayerProxy() {
|
||||
_timer.reset();
|
||||
}
|
||||
|
||||
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
|
||||
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
|
||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
||||
@ -164,16 +155,17 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
|
||||
}
|
||||
|
||||
//通知其停止推流
|
||||
weak_ptr<PlayerProxy> weakSlef = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||
getPoller()->async_first([weakSlef]() {
|
||||
auto stronSelf = weakSlef.lock();
|
||||
if (stronSelf) {
|
||||
stronSelf->_mediaMuxer.reset();
|
||||
stronSelf->setMediaSouce(nullptr);
|
||||
stronSelf->teardown();
|
||||
if(stronSelf->_onClose){
|
||||
stronSelf->_onClose();
|
||||
}
|
||||
weak_ptr<PlayerProxy> weakSelf = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||
getPoller()->async_first([weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->_muxer.reset();
|
||||
strongSelf->setMediaSouce(nullptr);
|
||||
strongSelf->teardown();
|
||||
if (strongSelf->_on_close) {
|
||||
strongSelf->_on_close();
|
||||
}
|
||||
});
|
||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||
@ -181,7 +173,7 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
|
||||
}
|
||||
|
||||
int PlayerProxy::totalReaderCount(){
|
||||
return (_mediaMuxer ? _mediaMuxer->totalReaderCount() : 0) + (_pMediaSrc ? _pMediaSrc->readerCount() : 0);
|
||||
return (_muxer ? _muxer->totalReaderCount() : 0) + (_pMediaSrc ? _pMediaSrc->readerCount() : 0);
|
||||
}
|
||||
|
||||
int PlayerProxy::totalReaderCount(MediaSource &sender) {
|
||||
@ -193,88 +185,90 @@ public:
|
||||
typedef std::shared_ptr<MuteAudioMaker> Ptr;
|
||||
|
||||
MuteAudioMaker(){};
|
||||
virtual ~MuteAudioMaker(){}
|
||||
~MuteAudioMaker() override {}
|
||||
|
||||
void inputFrame(const Frame::Ptr &frame) override {
|
||||
if(frame->getTrackType() == TrackVideo){
|
||||
auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS;
|
||||
if(_iAudioIndex != iAudioIndex){
|
||||
_iAudioIndex = iAudioIndex;
|
||||
auto aacFrame = std::make_shared<AACFrameCacheAble>((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS, 0);
|
||||
auto audio_idx = frame->dts() / MUTE_ADTS_DATA_MS;
|
||||
if(_audio_idx != audio_idx){
|
||||
_audio_idx = audio_idx;
|
||||
auto aacFrame = std::make_shared<FrameFromStaticPtr>(CodecAAC, (char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _audio_idx * MUTE_ADTS_DATA_MS, 0 ,ADTS_HEADER_LEN);
|
||||
FrameDispatcher::inputFrame(aacFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
||||
class FrameFromStaticPtr : public FrameFromPtr{
|
||||
public:
|
||||
template <typename ... ARGS>
|
||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
||||
virtual ~AACFrameCacheAble() = default;
|
||||
FrameFromStaticPtr(ARGS && ...args) : FrameFromPtr(std::forward<ARGS>(args)...) {};
|
||||
~FrameFromStaticPtr() override = default;
|
||||
|
||||
bool cacheAble() const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
int _iAudioIndex = 0;
|
||||
int _audio_idx = 0;
|
||||
};
|
||||
|
||||
void PlayerProxy::onPlaySuccess() {
|
||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
|
||||
//rtsp拉流代理
|
||||
if (resetWhenRePlay || !_mediaMuxer) {
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
||||
if (resetWhenRePlay || !_muxer) {
|
||||
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), false, true, _enable_hls, _enable_mp4));
|
||||
}
|
||||
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
|
||||
//rtmp拉流代理
|
||||
if (resetWhenRePlay || !_mediaMuxer) {
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
|
||||
if (resetWhenRePlay || !_muxer) {
|
||||
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), true, false, _enable_hls, _enable_mp4));
|
||||
}
|
||||
} else {
|
||||
//其他拉流代理
|
||||
if (resetWhenRePlay || !_mediaMuxer) {
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
||||
if (resetWhenRePlay || !_muxer) {
|
||||
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), true, true, _enable_hls, _enable_mp4));
|
||||
}
|
||||
}
|
||||
_mediaMuxer->setMediaListener(shared_from_this());
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
|
||||
auto videoTrack = getTrack(TrackVideo,false);
|
||||
if(videoTrack){
|
||||
auto videoTrack = getTrack(TrackVideo, false);
|
||||
if (videoTrack) {
|
||||
//添加视频
|
||||
_mediaMuxer->addTrack(videoTrack);
|
||||
_muxer->addTrack(videoTrack);
|
||||
//视频数据写入_mediaMuxer
|
||||
videoTrack->addDelegate(_mediaMuxer);
|
||||
videoTrack->addDelegate(_muxer);
|
||||
}
|
||||
|
||||
//是否添加静音音频
|
||||
GET_CONFIG(bool,addMuteAudio,General::kAddMuteAudio);
|
||||
GET_CONFIG(bool, addMuteAudio, General::kAddMuteAudio);
|
||||
|
||||
auto audioTrack = getTrack(TrackAudio, false);
|
||||
if(audioTrack){
|
||||
if (audioTrack) {
|
||||
//添加音频
|
||||
_mediaMuxer->addTrack(audioTrack);
|
||||
_muxer->addTrack(audioTrack);
|
||||
//音频数据写入_mediaMuxer
|
||||
audioTrack->addDelegate(_mediaMuxer);
|
||||
}else if(addMuteAudio && videoTrack){
|
||||
audioTrack->addDelegate(_muxer);
|
||||
} else if (addMuteAudio && videoTrack) {
|
||||
//没有音频信息,产生一个静音音频
|
||||
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
|
||||
//videoTrack把数据写入MuteAudioMaker
|
||||
videoTrack->addDelegate(audioMaker);
|
||||
//添加一个静音Track至_mediaMuxer
|
||||
_mediaMuxer->addTrack(std::make_shared<AACTrack>());
|
||||
_muxer->addTrack(std::make_shared<AACTrack>());
|
||||
//MuteAudioMaker生成静音音频然后写入_mediaMuxer;
|
||||
audioMaker->addDelegate(_mediaMuxer);
|
||||
audioMaker->addDelegate(_muxer);
|
||||
}
|
||||
|
||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||
_mediaMuxer->addTrackCompleted();
|
||||
_muxer->addTrackCompleted();
|
||||
|
||||
if(_pMediaSrc){
|
||||
_pMediaSrc->setTrackSource(_mediaMuxer);
|
||||
if (_pMediaSrc) {
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
_pMediaSrc->setListener(_muxer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -15,32 +15,22 @@
|
||||
#include "Common/Device.h"
|
||||
#include "Player/MediaPlayer.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PlayerProxy :public MediaPlayer,
|
||||
public std::enable_shared_from_this<PlayerProxy> ,
|
||||
public MediaSourceEvent{
|
||||
class PlayerProxy : public MediaPlayer, public MediaSourceEvent, public std::enable_shared_from_this<PlayerProxy> {
|
||||
public:
|
||||
typedef std::shared_ptr<PlayerProxy> Ptr;
|
||||
|
||||
//如果iRetryCount<0,则一直重试播放;否则重试iRetryCount次数
|
||||
//如果retry_count<0,则一直重试播放;否则重试retry_count次数
|
||||
//默认一直重试
|
||||
PlayerProxy(const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strSrc,
|
||||
bool bEnableRtsp = true,
|
||||
bool bEnableRtmp = true,
|
||||
bool bEnableHls = true,
|
||||
bool bEnableMp4 = false,
|
||||
int iRetryCount = -1,
|
||||
const EventPoller::Ptr &poller = nullptr);
|
||||
PlayerProxy(const string &vhost, const string &app, const string &stream_id,
|
||||
bool enable_hls = true, bool enable_mp4 = false,
|
||||
int retry_count = -1, const EventPoller::Ptr &poller = nullptr);
|
||||
|
||||
virtual ~PlayerProxy();
|
||||
~PlayerProxy() override;
|
||||
|
||||
/**
|
||||
* 设置play结果回调,只触发一次;在play执行之前有效
|
||||
@ -64,27 +54,26 @@ public:
|
||||
* 获取观看总人数
|
||||
*/
|
||||
int totalReaderCount() ;
|
||||
|
||||
private:
|
||||
//MediaSourceEvent override
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
void rePlay(const string &strUrl,int iFailedCnt);
|
||||
void onPlaySuccess();
|
||||
|
||||
private:
|
||||
bool _bEnableRtsp;
|
||||
bool _bEnableRtmp;
|
||||
bool _bEnableHls;
|
||||
bool _bEnableMp4;
|
||||
int _iRetryCount;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
string _strVhost;
|
||||
string _strApp;
|
||||
string _strSrc;
|
||||
bool _enable_hls;
|
||||
bool _enable_mp4;
|
||||
int _retry_count;
|
||||
string _vhost;
|
||||
string _app;
|
||||
string _stream_id;
|
||||
Timer::Ptr _timer;
|
||||
function<void(const SockException &ex)> _playCB;
|
||||
function<void()> _onClose;
|
||||
function<void()> _on_close;
|
||||
function<void(const SockException &ex)> _on_play;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_DEVICE_PLAYERPROXY_H_ */
|
||||
|
@ -19,34 +19,44 @@ namespace mediakit {
|
||||
MediaPusher::MediaPusher(const MediaSource::Ptr &src,
|
||||
const EventPoller::Ptr &poller) {
|
||||
_src = src;
|
||||
_poller = poller;
|
||||
if(!_poller){
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
|
||||
MediaPusher::MediaPusher(const string &schema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strStream,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const EventPoller::Ptr &poller) :
|
||||
MediaPusher(MediaSource::find(schema,strVhost,strApp,strStream),poller){
|
||||
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
|
||||
}
|
||||
|
||||
MediaPusher::~MediaPusher() {
|
||||
}
|
||||
void MediaPusher::publish(const string &strUrl) {
|
||||
_delegate = PusherBase::createPusher(_poller,_src.lock(),strUrl);
|
||||
|
||||
static void setOnCreateSocket_l(const std::shared_ptr<PusherBase> &delegate, const Socket::onCreateSocket &cb){
|
||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||
if (helper) {
|
||||
helper->setOnCreateSocket(cb);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPusher::publish(const string &url) {
|
||||
_delegate = PusherBase::createPusher(_poller, _src.lock(), url);
|
||||
assert(_delegate);
|
||||
setOnCreateSocket_l(_delegate, _on_create_socket);
|
||||
_delegate->setOnShutdown(_shutdownCB);
|
||||
_delegate->setOnPublished(_publishCB);
|
||||
_delegate->mINI::operator=(*this);
|
||||
_delegate->publish(strUrl);
|
||||
_delegate->publish(url);
|
||||
}
|
||||
|
||||
EventPoller::Ptr MediaPusher::getPoller(){
|
||||
return _poller;
|
||||
}
|
||||
|
||||
|
||||
void MediaPusher::setOnCreateSocket(Socket::onCreateSocket cb){
|
||||
setOnCreateSocket_l(_delegate, cb);
|
||||
_on_create_socket = std::move(cb);
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -24,20 +24,24 @@ public:
|
||||
typedef std::shared_ptr<MediaPusher> Ptr;
|
||||
|
||||
MediaPusher(const string &schema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strStream,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const EventPoller::Ptr &poller = nullptr);
|
||||
|
||||
MediaPusher(const MediaSource::Ptr &src,
|
||||
const EventPoller::Ptr &poller = nullptr);
|
||||
|
||||
virtual ~MediaPusher();
|
||||
void publish(const string &strUrl) override;
|
||||
|
||||
void publish(const string &url) override;
|
||||
EventPoller::Ptr getPoller();
|
||||
void setOnCreateSocket(Socket::onCreateSocket cb);
|
||||
|
||||
private:
|
||||
std::weak_ptr<MediaSource> _src;
|
||||
EventPoller::Ptr _poller;
|
||||
Socket::onCreateSocket _on_create_socket;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -32,27 +32,27 @@ void HlsMaker::makeIndexFile(bool eof) {
|
||||
}
|
||||
}
|
||||
|
||||
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
|
||||
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
|
||||
|
||||
string m3u8;
|
||||
snprintf(file_content,sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
sequence);
|
||||
snprintf(file_content, sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
sequence);
|
||||
|
||||
m3u8.assign(file_content);
|
||||
|
||||
for (auto &tp : _seg_dur_list) {
|
||||
snprintf(file_content,sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data());
|
||||
snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data());
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
|
||||
if (eof) {
|
||||
snprintf(file_content,sizeof(file_content),"#EXT-X-ENDLIST\n");
|
||||
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
onWriteHls(m3u8.data(), m3u8.size());
|
||||
@ -61,12 +61,15 @@ void HlsMaker::makeIndexFile(bool eof) {
|
||||
|
||||
void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet) {
|
||||
if (data && len) {
|
||||
if(is_idr_fast_packet){
|
||||
if (is_idr_fast_packet) {
|
||||
//尝试切片ts
|
||||
addNewSegment(timestamp);
|
||||
}
|
||||
onWriteSegment((char *) data, len);
|
||||
//记录上次写入数据时间
|
||||
_ticker_last_data.resetTime();
|
||||
if (!_last_file_name.empty()) {
|
||||
//存在切片才写入ts数据
|
||||
onWriteSegment((char *) data, len);
|
||||
_last_timestamp = timestamp;
|
||||
}
|
||||
} else {
|
||||
//resetTracks时触发此逻辑
|
||||
flushLastSegment(true);
|
||||
@ -74,7 +77,7 @@ void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_i
|
||||
}
|
||||
|
||||
void HlsMaker::delOldSegment() {
|
||||
if(_seg_number == 0){
|
||||
if (_seg_number == 0) {
|
||||
//如果设置为保留0个切片,则认为是保存为点播
|
||||
return;
|
||||
}
|
||||
@ -83,15 +86,15 @@ void HlsMaker::delOldSegment() {
|
||||
_seg_dur_list.pop_front();
|
||||
}
|
||||
|
||||
GET_CONFIG(uint32_t,segRetain,Hls::kSegmentRetain);
|
||||
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
|
||||
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
|
||||
if (_file_index > _seg_number + segRetain) {
|
||||
onDelSegment(_file_index - _seg_number - segRetain - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMaker::addNewSegment(uint32_t) {
|
||||
if(!_last_file_name.empty() && _ticker.elapsedTime() < _seg_duration * 1000){
|
||||
void HlsMaker::addNewSegment(uint32_t stamp) {
|
||||
if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) {
|
||||
//存在上个切片,并且未到分片时间
|
||||
return;
|
||||
}
|
||||
@ -100,28 +103,36 @@ void HlsMaker::addNewSegment(uint32_t) {
|
||||
flushLastSegment(_seg_number == 0);
|
||||
//新增切片
|
||||
_last_file_name = onOpenSegment(_file_index++);
|
||||
//重置切片计时器
|
||||
_ticker.resetTime();
|
||||
//记录本次切片的起始时间戳
|
||||
_last_seg_timestamp = stamp;
|
||||
}
|
||||
|
||||
void HlsMaker::flushLastSegment(bool eof){
|
||||
if(_last_file_name.empty()){
|
||||
if (_last_file_name.empty()) {
|
||||
//不存在上个切片
|
||||
return;
|
||||
}
|
||||
//文件创建到最后一次数据写入的时间即为切片长度
|
||||
auto seg_dur = _ticker.elapsedTime() - _ticker_last_data.elapsedTime();
|
||||
if(seg_dur <= 0){
|
||||
auto seg_dur = _last_timestamp - _last_seg_timestamp;
|
||||
if (seg_dur <= 0) {
|
||||
seg_dur = 100;
|
||||
}
|
||||
_seg_dur_list.push_back(std::make_tuple(seg_dur, _last_file_name));
|
||||
_seg_dur_list.push_back(std::make_tuple(seg_dur, std::move(_last_file_name)));
|
||||
_last_file_name.clear();
|
||||
delOldSegment();
|
||||
makeIndexFile(eof);
|
||||
_last_file_name.clear();
|
||||
onFlushLastSegment(seg_dur);
|
||||
}
|
||||
|
||||
bool HlsMaker::isLive() {
|
||||
return _seg_number != 0;
|
||||
}
|
||||
|
||||
void HlsMaker::clear() {
|
||||
_file_index = 0;
|
||||
_last_seg_timestamp = 0;
|
||||
_seg_dur_list.clear();
|
||||
_last_file_name.clear();
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -39,6 +39,17 @@ public:
|
||||
* @param is_idr_fast_packet 是否为关键帧第一个包
|
||||
*/
|
||||
void inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet);
|
||||
|
||||
/**
|
||||
* 是否为直播
|
||||
*/
|
||||
bool isLive();
|
||||
|
||||
/**
|
||||
* 清空记录
|
||||
*/
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 创建ts切片文件回调
|
||||
@ -68,15 +79,17 @@ protected:
|
||||
virtual void onWriteHls(const char *data, int len) = 0;
|
||||
|
||||
/**
|
||||
* 关闭上个ts切片并且写入m3u8索引
|
||||
* @param eof
|
||||
* 上一个 ts 切片写入完成, 可在这里进行通知处理
|
||||
* @param duration_ms 上一个 ts 切片的时长, 单位为毫秒
|
||||
*/
|
||||
void flushLastSegment(bool eof = false);
|
||||
virtual void onFlushLastSegment(uint32_t duration_ms) {};
|
||||
|
||||
/**
|
||||
* 是否为直播
|
||||
* 关闭上个ts切片并且写入m3u8索引
|
||||
* @param eof HLS直播是否已结束
|
||||
*/
|
||||
bool isLive();
|
||||
void flushLastSegment(bool eof);
|
||||
|
||||
private:
|
||||
/**
|
||||
* 生成m3u8文件
|
||||
@ -94,12 +107,13 @@ private:
|
||||
* @param timestamp
|
||||
*/
|
||||
void addNewSegment(uint32_t timestamp);
|
||||
|
||||
private:
|
||||
uint32_t _seg_number = 0;
|
||||
float _seg_duration = 0;
|
||||
uint32_t _seg_number = 0;
|
||||
uint32_t _last_timestamp = 0;
|
||||
uint32_t _last_seg_timestamp = 0;
|
||||
uint64_t _file_index = 0;
|
||||
Ticker _ticker;
|
||||
Ticker _ticker_last_data;
|
||||
string _last_file_name;
|
||||
std::deque<tuple<int,string> > _seg_dur_list;
|
||||
};
|
||||
|
@ -8,9 +8,12 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <ctime>
|
||||
#include <sys/stat.h>
|
||||
#include "HlsMakerImp.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/uv_errno.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
@ -24,45 +27,61 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
_path_hls = m3u8_file;
|
||||
_params = params;
|
||||
_buf_size = bufSize;
|
||||
_file_buf.reset(new char[bufSize],[](char *ptr){
|
||||
_file_buf.reset(new char[bufSize], [](char *ptr) {
|
||||
delete[] ptr;
|
||||
});
|
||||
|
||||
_info.folder = _path_prefix;
|
||||
}
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
clearCache();
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache() {
|
||||
//录制完了
|
||||
flushLastSegment(true);
|
||||
if(isLive()){
|
||||
if (isLive()) {
|
||||
//hls直播才删除文件
|
||||
clear();
|
||||
_file = nullptr;
|
||||
_segment_file_paths.clear();
|
||||
File::delete_file(_path_prefix.data());
|
||||
}
|
||||
}
|
||||
|
||||
string HlsMakerImp::onOpenSegment(int index) {
|
||||
string segment_name , segment_path;
|
||||
string segment_name, segment_path;
|
||||
{
|
||||
auto strDate = getTimeStr("%Y-%m-%d");
|
||||
auto strHour = getTimeStr("%H");
|
||||
auto strTime = getTimeStr("%M-%S");
|
||||
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
|
||||
segment_path = _path_prefix + "/" + segment_name;
|
||||
if(isLive()){
|
||||
_segment_file_paths.emplace(index,segment_path);
|
||||
segment_path = _path_prefix + "/" + segment_name;
|
||||
if (isLive()) {
|
||||
_segment_file_paths.emplace(index, segment_path);
|
||||
}
|
||||
}
|
||||
_file = makeFile(segment_path, true);
|
||||
if(!_file){
|
||||
WarnL << "create file falied," << segment_path << " " << get_uv_errmsg();
|
||||
|
||||
//保存本切片的元数据
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = segment_name;
|
||||
_info.file_path = segment_path;
|
||||
_info.url = _info.app + "/" + _info.stream + "/" + segment_name;
|
||||
|
||||
if (!_file) {
|
||||
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
|
||||
}
|
||||
if(_params.empty()){
|
||||
return std::move(segment_name);
|
||||
if (_params.empty()) {
|
||||
return segment_name;
|
||||
}
|
||||
return std::move(segment_name + "?" + _params);
|
||||
return segment_name + "?" + _params;
|
||||
}
|
||||
|
||||
void HlsMakerImp::onDelSegment(int index) {
|
||||
auto it = _segment_file_paths.find(index);
|
||||
if(it == _segment_file_paths.end()){
|
||||
if (it == _segment_file_paths.end()) {
|
||||
return;
|
||||
}
|
||||
File::delete_file(it->second.data());
|
||||
@ -77,27 +96,39 @@ void HlsMakerImp::onWriteSegment(const char *data, int len) {
|
||||
|
||||
void HlsMakerImp::onWriteHls(const char *data, int len) {
|
||||
auto hls = makeFile(_path_hls);
|
||||
if(hls){
|
||||
fwrite(data,len,1,hls.get());
|
||||
if (hls) {
|
||||
fwrite(data, len, 1, hls.get());
|
||||
hls.reset();
|
||||
if(_media_src){
|
||||
_media_src->registHls();
|
||||
if (_media_src) {
|
||||
_media_src->registHls(true);
|
||||
}
|
||||
} else{
|
||||
WarnL << "create hls file falied," << _path_hls << " " << get_uv_errmsg();
|
||||
} else {
|
||||
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
|
||||
}
|
||||
//DebugL << "\r\n" << string(data,len);
|
||||
}
|
||||
|
||||
void HlsMakerImp::onFlushLastSegment(uint32_t duration_ms) {
|
||||
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
|
||||
if (broadcastRecordTs) {
|
||||
//关闭ts文件以便获取正确的文件大小
|
||||
_file = nullptr;
|
||||
_info.time_len = duration_ms / 1000.0;
|
||||
struct stat fileData;
|
||||
stat(_info.file_path.data(), &fileData);
|
||||
_info.file_size = fileData.st_size;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file,bool setbuf) {
|
||||
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) {
|
||||
auto file_buf = _file_buf;
|
||||
auto ret= shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
|
||||
auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if(ret && setbuf){
|
||||
if (ret && setbuf) {
|
||||
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
||||
}
|
||||
return ret;
|
||||
@ -105,9 +136,12 @@ 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;
|
||||
}
|
||||
|
||||
MediaSource::Ptr HlsMakerImp::getMediaSource() const{
|
||||
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||
return _media_src;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "HlsMaker.h"
|
||||
#include "HlsMediaSource.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
@ -27,7 +28,8 @@ public:
|
||||
uint32_t bufSize = 64 * 1024,
|
||||
float seg_duration = 5,
|
||||
uint32_t seg_number = 3);
|
||||
virtual ~HlsMakerImp();
|
||||
|
||||
~HlsMakerImp() override;
|
||||
|
||||
/**
|
||||
* 设置媒体信息
|
||||
@ -41,23 +43,33 @@ public:
|
||||
* 获取MediaSource
|
||||
* @return
|
||||
*/
|
||||
MediaSource::Ptr getMediaSource() const;
|
||||
HlsMediaSource::Ptr getMediaSource() const;
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
void clearCache();
|
||||
|
||||
protected:
|
||||
string onOpenSegment(int index) override ;
|
||||
void onDelSegment(int index) override;
|
||||
void onWriteSegment(const char *data, int len) override;
|
||||
void onWriteHls(const char *data, int len) override;
|
||||
void onFlushLastSegment(uint32_t duration_ms) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false);
|
||||
|
||||
private:
|
||||
HlsMediaSource::Ptr _media_src;
|
||||
map<int /*index*/,string/*file_path*/> _segment_file_paths;
|
||||
int _buf_size;
|
||||
string _params;
|
||||
string _path_hls;
|
||||
string _path_prefix;
|
||||
RecordInfo _info;
|
||||
std::shared_ptr<FILE> _file;
|
||||
std::shared_ptr<char> _file_buf;
|
||||
string _path_prefix;
|
||||
string _path_hls;
|
||||
string _params;
|
||||
int _buf_size;
|
||||
HlsMediaSource::Ptr _media_src;
|
||||
map<int /*index*/,string/*file_path*/> _segment_file_paths;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -23,9 +23,7 @@ void HlsCookieData::addReaderCount(){
|
||||
if(!*_added){
|
||||
auto src = dynamic_pointer_cast<HlsMediaSource>(MediaSource::find(HLS_SCHEMA,_info._vhost,_info._app,_info._streamid));
|
||||
if(src){
|
||||
src->modifyReaderCount(true);
|
||||
*_added = true;
|
||||
_src = src;
|
||||
_ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller());
|
||||
auto added = _added;
|
||||
_ring_reader->setDetachCB([added](){
|
||||
@ -38,18 +36,15 @@ void HlsCookieData::addReaderCount(){
|
||||
|
||||
HlsCookieData::~HlsCookieData() {
|
||||
if (*_added) {
|
||||
auto src = _src.lock();
|
||||
if (src) {
|
||||
src->modifyReaderCount(false);
|
||||
}
|
||||
uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000;
|
||||
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port() << ") "
|
||||
<< "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid
|
||||
<< ")断开,耗时(s):" << duration;
|
||||
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
if (_bytes > iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, _bytes, duration, true, static_cast<SockInfo&>(*_sock_info));
|
||||
uint64_t bytes = _bytes.load();
|
||||
if (bytes > iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast<SockInfo&>(*_sock_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,12 +21,8 @@ public:
|
||||
friend class HlsCookieData;
|
||||
typedef RingBuffer<string> RingType;
|
||||
typedef std::shared_ptr<HlsMediaSource> Ptr;
|
||||
HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){
|
||||
_readerCount = 0;
|
||||
_ring = std::make_shared<RingType>();
|
||||
}
|
||||
|
||||
virtual ~HlsMediaSource() = default;
|
||||
HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){}
|
||||
~HlsMediaSource() override = default;
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
@ -37,41 +33,57 @@ public:
|
||||
|
||||
/**
|
||||
* 获取播放器个数
|
||||
* @return
|
||||
*/
|
||||
int readerCount() override {
|
||||
return _readerCount.load();
|
||||
return _ring ? _ring->readerCount() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册hls
|
||||
* 生成m3u8文件时触发
|
||||
* @param file_created 是否产生了hls文件
|
||||
*/
|
||||
void registHls(){
|
||||
if(!_registed){
|
||||
void registHls(bool file_created){
|
||||
if (!_is_regist) {
|
||||
_is_regist = true;
|
||||
weak_ptr<HlsMediaSource> weakSelf = dynamic_pointer_cast<HlsMediaSource>(shared_from_this());
|
||||
auto lam = [weakSelf](int size) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->onReaderChanged(size);
|
||||
};
|
||||
_ring = std::make_shared<RingType>(0, std::move(lam));
|
||||
onReaderChanged(0);
|
||||
regist();
|
||||
_registed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* 修改观看者个数
|
||||
* @param add 添加海思删除
|
||||
*/
|
||||
void modifyReaderCount(bool add) {
|
||||
if (add) {
|
||||
++_readerCount;
|
||||
if (!file_created) {
|
||||
//没产生文件
|
||||
return;
|
||||
}
|
||||
|
||||
if (--_readerCount == 0) {
|
||||
onNoneReader();
|
||||
//m3u8文件生成,发送给播放器
|
||||
decltype(_list_cb) copy;
|
||||
{
|
||||
lock_guard<mutex> lck(_mtx_cb);
|
||||
copy.swap(_list_cb);
|
||||
}
|
||||
copy.for_each([](const function<void()> &cb) {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
void waitForFile(function<void()> cb) {
|
||||
//等待生成m3u8文件
|
||||
lock_guard<mutex> lck(_mtx_cb);
|
||||
_list_cb.emplace_back(std::move(cb));
|
||||
}
|
||||
|
||||
private:
|
||||
atomic_int _readerCount;
|
||||
bool _registed = false;
|
||||
bool _is_regist = false;
|
||||
RingType::Ptr _ring;
|
||||
mutex _mtx_cb;
|
||||
List<function<void()> > _list_cb;
|
||||
};
|
||||
|
||||
class HlsCookieData{
|
||||
@ -80,13 +92,14 @@ public:
|
||||
HlsCookieData(const MediaInfo &info, const std::shared_ptr<SockInfo> &sock_info);
|
||||
~HlsCookieData();
|
||||
void addByteUsage(uint64_t bytes);
|
||||
|
||||
private:
|
||||
void addReaderCount();
|
||||
|
||||
private:
|
||||
uint64_t _bytes = 0;
|
||||
atomic<uint64_t> _bytes {0};
|
||||
MediaInfo _info;
|
||||
std::shared_ptr<bool> _added;
|
||||
weak_ptr<HlsMediaSource> _src;
|
||||
Ticker _ticker;
|
||||
std::shared_ptr<SockInfo> _sock_info;
|
||||
HlsMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
|
@ -15,37 +15,70 @@
|
||||
#include "TsMuxer.h"
|
||||
namespace mediakit {
|
||||
|
||||
class HlsRecorder
|
||||
#if defined(ENABLE_HLS)
|
||||
: public TsMuxer
|
||||
#endif
|
||||
{
|
||||
class HlsRecorder : public MediaSourceEventInterceptor, public TsMuxer, public std::enable_shared_from_this<HlsRecorder> {
|
||||
public:
|
||||
typedef std::shared_ptr<HlsRecorder> Ptr;
|
||||
HlsRecorder(const string &m3u8_file, const string ¶ms){
|
||||
GET_CONFIG(uint32_t,hlsNum,Hls::kSegmentNum);
|
||||
GET_CONFIG(uint32_t,hlsBufSize,Hls::kFileBufSize);
|
||||
GET_CONFIG(uint32_t,hlsDuration,Hls::kSegmentDuration);
|
||||
_hls = new HlsMakerImp(m3u8_file,params,hlsBufSize,hlsDuration,hlsNum);
|
||||
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
|
||||
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
|
||||
GET_CONFIG(uint32_t, hlsDuration, Hls::kSegmentDuration);
|
||||
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum);
|
||||
//清空上次的残余文件
|
||||
_hls->clearCache();
|
||||
}
|
||||
~HlsRecorder(){
|
||||
delete _hls;
|
||||
}
|
||||
void setMediaSource(const string &vhost, const string &app, const string &stream_id){
|
||||
|
||||
~HlsRecorder(){}
|
||||
|
||||
void setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||
_hls->setMediaSource(vhost, app, stream_id);
|
||||
}
|
||||
|
||||
MediaSource::Ptr getMediaSource() const{
|
||||
return _hls->getMediaSource();
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
_listener = listener;
|
||||
_hls->getMediaSource()->setListener(shared_from_this());
|
||||
//先注册媒体流,后续可以按需生成
|
||||
_hls->getMediaSource()->registHls(false);
|
||||
}
|
||||
#if defined(ENABLE_HLS)
|
||||
protected:
|
||||
void onTs(const void *packet, int bytes,uint32_t timestamp,bool is_idr_fast_packet) override {
|
||||
_hls->inputData((char *)packet,bytes,timestamp, is_idr_fast_packet);
|
||||
};
|
||||
#endif
|
||||
|
||||
int readerCount() {
|
||||
return _hls->getMediaSource()->readerCount();
|
||||
}
|
||||
|
||||
void onReaderChanged(MediaSource &sender, int size) override {
|
||||
//hls保留切片个数为0时代表为hls录制(不删除切片),那么不管有无观看者都一直生成hls
|
||||
_enabled = _hls->isLive() ? size : true;
|
||||
if (!size && _hls->isLive()) {
|
||||
//hls直播时,如果无人观看就删除视频缓存,目的是为了防止视频跳跃
|
||||
_clear_cache = true;
|
||||
}
|
||||
MediaSourceEventInterceptor::onReaderChanged(sender, size);
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
//缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存
|
||||
return _clear_cache ? true : _enabled;
|
||||
}
|
||||
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
if (_clear_cache) {
|
||||
_clear_cache = false;
|
||||
_hls->clearCache();
|
||||
}
|
||||
if (_enabled) {
|
||||
TsMuxer::inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HlsMakerImp *_hls;
|
||||
void onTs(const void *packet, int bytes, uint32_t timestamp, bool is_idr_fast_packet) override {
|
||||
_hls->inputData((char *) packet, bytes, timestamp, is_idr_fast_packet);
|
||||
}
|
||||
|
||||
private:
|
||||
//默认不生成hls文件,有播放器时再生成
|
||||
bool _enabled = false;
|
||||
bool _clear_cache = false;
|
||||
std::shared_ptr<HlsMakerImp> _hls;
|
||||
};
|
||||
}//namespace mediakit
|
||||
#endif //HLSRECORDER_H
|
||||
|
@ -13,34 +13,125 @@
|
||||
#include "Util/File.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Common/config.h"
|
||||
#include "fmp4-writer.h"
|
||||
|
||||
using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
/////////////////////////////////////////////////mp4_writer_t/////////////////////////////////////////////////
|
||||
|
||||
struct mp4_writer_t {
|
||||
int is_fmp4;
|
||||
union {
|
||||
fmp4_writer_t *fmp4;
|
||||
mov_writer_t *mov;
|
||||
} u;
|
||||
};
|
||||
|
||||
mp4_writer_t* mp4_writer_create(int is_fmp4, const struct mov_buffer_t *buffer, void* param, int flags){
|
||||
mp4_writer_t *mp4 = (mp4_writer_t *) malloc(sizeof(mp4_writer_t));
|
||||
mp4->is_fmp4 = is_fmp4;
|
||||
if (is_fmp4) {
|
||||
mp4->u.fmp4 = fmp4_writer_create(buffer, param, flags);
|
||||
} else {
|
||||
mp4->u.mov = mov_writer_create(buffer, param, flags);
|
||||
}
|
||||
return mp4;
|
||||
}
|
||||
|
||||
void mp4_writer_destroy(mp4_writer_t* mp4){
|
||||
if (mp4->is_fmp4) {
|
||||
fmp4_writer_destroy(mp4->u.fmp4);
|
||||
} else {
|
||||
mov_writer_destroy(mp4->u.mov);
|
||||
}
|
||||
free(mp4);
|
||||
}
|
||||
|
||||
int mp4_writer_add_audio(mp4_writer_t* mp4, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_add_audio(mp4->u.fmp4, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size);
|
||||
} else {
|
||||
return mov_writer_add_audio(mp4->u.mov, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_add_video(mp4_writer_t* mp4, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_add_video(mp4->u.fmp4, object, width, height, extra_data, extra_data_size);
|
||||
} else {
|
||||
return mov_writer_add_video(mp4->u.mov, object, width, height, extra_data, extra_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_add_subtitle(mp4_writer_t* mp4, uint8_t object, const void* extra_data, size_t extra_data_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_add_subtitle(mp4->u.fmp4, object, extra_data, extra_data_size);
|
||||
} else {
|
||||
return mov_writer_add_subtitle(mp4->u.mov, object, extra_data, extra_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_write(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_write(mp4->u.fmp4, track, data, bytes, pts, dts, flags);
|
||||
} else {
|
||||
return mov_writer_write(mp4->u.mov, track, data, bytes, pts, dts, flags);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_write_l(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags, int add_nalu_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_write_l(mp4->u.fmp4, track, data, bytes, pts, dts, flags, add_nalu_size);
|
||||
} else {
|
||||
return mov_writer_write_l(mp4->u.mov, track, data, bytes, pts, dts, flags, add_nalu_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_save_segment(mp4_writer_t* mp4){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_save_segment(mp4->u.fmp4);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_init_segment(mp4_writer_t* mp4){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_init_segment(mp4->u.fmp4);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////MP4FileIO/////////////////////////////////////////////////
|
||||
|
||||
static struct mov_buffer_t s_io = {
|
||||
[](void* ctx, void* data, uint64_t bytes) {
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
return thiz->onRead(data,bytes);
|
||||
[](void *ctx, void *data, uint64_t bytes) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onRead(data, bytes);
|
||||
},
|
||||
[](void* ctx, const void* data, uint64_t bytes){
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
return thiz->onWrite(data,bytes);
|
||||
[](void *ctx, const void *data, uint64_t bytes) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onWrite(data, bytes);
|
||||
},
|
||||
[](void* ctx, uint64_t offset) {
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
[](void *ctx, uint64_t offset) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onSeek(offset);
|
||||
},
|
||||
[](void* ctx){
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
[](void *ctx) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onTell();
|
||||
}
|
||||
};
|
||||
|
||||
MP4File::Writer MP4File::createWriter(){
|
||||
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
|
||||
MP4FileIO::Writer MP4FileIO::createWriter(int flags, bool is_fmp4){
|
||||
Writer writer;
|
||||
writer.reset(mov_writer_create(&s_io,this,mp4FastStart ? MOV_FLAG_FASTSTART : 0),[](mov_writer_t *ptr){
|
||||
Ptr self = shared_from_this();
|
||||
//保存自己的强引用,防止提前释放
|
||||
writer.reset(mp4_writer_create(is_fmp4, &s_io,this, flags),[self](mp4_writer_t *ptr){
|
||||
if(ptr){
|
||||
mov_writer_destroy(ptr);
|
||||
mp4_writer_destroy(ptr);
|
||||
}
|
||||
});
|
||||
if(!writer){
|
||||
@ -49,9 +140,11 @@ MP4File::Writer MP4File::createWriter(){
|
||||
return writer;
|
||||
}
|
||||
|
||||
MP4File::Reader MP4File::createReader(){
|
||||
MP4FileIO::Reader MP4FileIO::createReader(){
|
||||
Reader reader;
|
||||
reader.reset(mov_reader_create(&s_io,this),[](mov_reader_t *ptr){
|
||||
Ptr self = shared_from_this();
|
||||
//保存自己的强引用,防止提前释放
|
||||
reader.reset(mov_reader_create(&s_io,this),[self](mov_reader_t *ptr){
|
||||
if(ptr){
|
||||
mov_reader_destroy(ptr);
|
||||
}
|
||||
@ -62,15 +155,17 @@ MP4File::Reader MP4File::createReader(){
|
||||
return reader;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////MP4FileDisk/////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define fseek64 _fseeki64
|
||||
#define ftell64 _ftelli64
|
||||
#define ftell64 _ftelli64
|
||||
#else
|
||||
#define fseek64 fseek
|
||||
#define ftell64 ftell
|
||||
#define fseek64 fseek
|
||||
#define ftell64 ftell
|
||||
#endif
|
||||
|
||||
void MP4File::openFile(const char *file,const char *mode) {
|
||||
void MP4FileDisk::openFile(const char *file, const char *mode) {
|
||||
//创建文件
|
||||
auto fp = File::create_file(file, mode);
|
||||
if(!fp){
|
||||
@ -98,28 +193,74 @@ void MP4File::openFile(const char *file,const char *mode) {
|
||||
});
|
||||
}
|
||||
|
||||
void MP4File::closeFile() {
|
||||
void MP4FileDisk::closeFile() {
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
int MP4File::onRead(void *data, uint64_t bytes) {
|
||||
int MP4FileDisk::onRead(void *data, uint64_t bytes) {
|
||||
if (bytes == fread(data, 1, bytes, _file.get())){
|
||||
return 0;
|
||||
}
|
||||
return 0 != ferror(_file.get()) ? ferror(_file.get()) : -1 /*EOF*/;
|
||||
}
|
||||
|
||||
int MP4File::onWrite(const void *data, uint64_t bytes) {
|
||||
int MP4FileDisk::onWrite(const void *data, uint64_t bytes) {
|
||||
return bytes == fwrite(data, 1, bytes, _file.get()) ? 0 : ferror(_file.get());
|
||||
}
|
||||
|
||||
int MP4File::onSeek(uint64_t offset) {
|
||||
int MP4FileDisk::onSeek(uint64_t offset) {
|
||||
return fseek64(_file.get(), offset, SEEK_SET);
|
||||
}
|
||||
|
||||
uint64_t MP4File::onTell() {
|
||||
uint64_t MP4FileDisk::onTell() {
|
||||
return ftell64(_file.get());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////MP4FileMemory/////////////////////////////////////////////////////////
|
||||
|
||||
string MP4FileMemory::getAndClearMemory(){
|
||||
string ret;
|
||||
ret.swap(_memory);
|
||||
_offset = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t MP4FileMemory::fileSize() const{
|
||||
return _memory.size();
|
||||
}
|
||||
|
||||
uint64_t MP4FileMemory::onTell(){
|
||||
return _offset;
|
||||
}
|
||||
|
||||
int MP4FileMemory::onSeek(uint64_t offset){
|
||||
if (offset > _memory.size()) {
|
||||
return -1;
|
||||
}
|
||||
_offset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MP4FileMemory::onRead(void *data, uint64_t bytes){
|
||||
if (_offset >= _memory.size()) {
|
||||
//EOF
|
||||
return -1;
|
||||
}
|
||||
bytes = MIN(bytes, _memory.size() - _offset);
|
||||
memcpy(data, _memory.data(), bytes);
|
||||
_offset += bytes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MP4FileMemory::onWrite(const void *data, uint64_t bytes){
|
||||
if (_offset + bytes > _memory.size()) {
|
||||
//需要扩容
|
||||
_memory.resize(_offset + bytes);
|
||||
}
|
||||
memcpy((uint8_t *) _memory.data() + _offset, data, bytes);
|
||||
_offset += bytes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //NABLE_MP4RECORD
|
||||
|
128
src/Record/MP4.h
128
src/Record/MP4.h
@ -23,27 +23,127 @@
|
||||
using namespace std;
|
||||
namespace mediakit {
|
||||
|
||||
class MP4File {
|
||||
public:
|
||||
friend struct mov_buffer_t;
|
||||
typedef std::shared_ptr<mov_writer_t> Writer;
|
||||
typedef std::shared_ptr<mov_reader_t> Reader;
|
||||
MP4File() = default;
|
||||
virtual ~MP4File() = default;
|
||||
//以下是fmp4/mov的通用接口,简单包装了ireader/media-server的接口
|
||||
typedef struct mp4_writer_t mp4_writer_t;
|
||||
mp4_writer_t* mp4_writer_create(int is_fmp4, const struct mov_buffer_t *buffer, void* param, int flags);
|
||||
void mp4_writer_destroy(mp4_writer_t* mp4);
|
||||
int mp4_writer_add_audio(mp4_writer_t* mp4, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size);
|
||||
int mp4_writer_add_video(mp4_writer_t* mp4, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size);
|
||||
int mp4_writer_add_subtitle(mp4_writer_t* mp4, uint8_t object, const void* extra_data, size_t extra_data_size);
|
||||
int mp4_writer_write(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags);
|
||||
int mp4_writer_write_l(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags, int add_nalu_size);
|
||||
int mp4_writer_save_segment(mp4_writer_t* mp4);
|
||||
int mp4_writer_init_segment(mp4_writer_t* mp4);
|
||||
|
||||
Writer createWriter();
|
||||
Reader createReader();
|
||||
void openFile(const char *file,const char *mode);
|
||||
//mp4文件IO的抽象接口类
|
||||
class MP4FileIO : public std::enable_shared_from_this<MP4FileIO> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileIO>;
|
||||
using Writer = std::shared_ptr<mp4_writer_t>;
|
||||
using Reader = std::shared_ptr<mov_reader_t>;
|
||||
|
||||
MP4FileIO() = default;
|
||||
virtual ~MP4FileIO() = default;
|
||||
|
||||
/**
|
||||
* 创建mp4复用器
|
||||
* @param flags 支持0、MOV_FLAG_FASTSTART、MOV_FLAG_SEGMENT
|
||||
* @param is_fmp4 是否为fmp4还是普通mp4
|
||||
* @return mp4复用器
|
||||
*/
|
||||
virtual Writer createWriter(int flags, bool is_fmp4 = false);
|
||||
|
||||
/**
|
||||
* 创建mp4解复用器
|
||||
* @return mp4解复用器
|
||||
*/
|
||||
virtual Reader createReader();
|
||||
|
||||
/**
|
||||
* 获取文件读写位置
|
||||
*/
|
||||
virtual uint64_t onTell() = 0;
|
||||
|
||||
/**
|
||||
* seek至文件某处
|
||||
* @param offset 文件偏移量
|
||||
* @return 是否成功(0成功)
|
||||
*/
|
||||
virtual int onSeek(uint64_t offset) = 0;
|
||||
|
||||
/**
|
||||
* 从文件读取一定数据
|
||||
* @param data 数据存放指针
|
||||
* @param bytes 指针长度
|
||||
* @return 是否成功(0成功)
|
||||
*/
|
||||
virtual int onRead(void *data, uint64_t bytes) = 0;
|
||||
|
||||
/**
|
||||
* 写入文件一定数据
|
||||
* @param data 数据指针
|
||||
* @param bytes 数据长度
|
||||
* @return 是否成功(0成功)
|
||||
*/
|
||||
virtual int onWrite(const void *data, uint64_t bytes) = 0;
|
||||
};
|
||||
|
||||
//磁盘MP4文件类
|
||||
class MP4FileDisk : public MP4FileIO {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileDisk>;
|
||||
MP4FileDisk() = default;
|
||||
~MP4FileDisk() override = default;
|
||||
|
||||
/**
|
||||
* 打开磁盘文件
|
||||
* @param file 文件路径
|
||||
* @param mode fopen的方式
|
||||
*/
|
||||
void openFile(const char *file, const char *mode);
|
||||
|
||||
/**
|
||||
* 关闭磁盘文件
|
||||
*/
|
||||
void closeFile();
|
||||
|
||||
int onRead(void* data, uint64_t bytes);
|
||||
int onWrite(const void* data, uint64_t bytes);
|
||||
int onSeek( uint64_t offset);
|
||||
uint64_t onTell();
|
||||
protected:
|
||||
uint64_t onTell() override;
|
||||
int onSeek(uint64_t offset) override;
|
||||
int onRead(void *data, uint64_t bytes) override;
|
||||
int onWrite(const void *data, uint64_t bytes) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<FILE> _file;
|
||||
};
|
||||
|
||||
class MP4FileMemory : public MP4FileIO{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileMemory>;
|
||||
MP4FileMemory() = default;
|
||||
~MP4FileMemory() override = default;
|
||||
|
||||
/**
|
||||
* 获取文件大小
|
||||
*/
|
||||
uint64_t fileSize() const;
|
||||
|
||||
/**
|
||||
* 获取并清空文件缓存
|
||||
*/
|
||||
string getAndClearMemory();
|
||||
|
||||
protected:
|
||||
uint64_t onTell() override;
|
||||
int onSeek(uint64_t offset) override;
|
||||
int onRead(void *data, uint64_t bytes) override;
|
||||
int onWrite(const void *data, uint64_t bytes) override;
|
||||
|
||||
private:
|
||||
uint64_t _offset = 0;
|
||||
string _memory;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //NABLE_MP4RECORD
|
||||
#endif //ZLMEDIAKIT_MP4_H
|
||||
|
@ -15,21 +15,24 @@
|
||||
#include "Extension/H264.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Extension/G711.h"
|
||||
#include "Extension/Opus.h"
|
||||
using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
MP4Demuxer::MP4Demuxer(const char *file) {
|
||||
openFile(file,"rb+");
|
||||
_mov_reader = createReader();
|
||||
getAllTracks();
|
||||
_duration_ms = mov_reader_getduration(_mov_reader.get());
|
||||
}
|
||||
MP4Demuxer::MP4Demuxer() {}
|
||||
|
||||
MP4Demuxer::~MP4Demuxer() {
|
||||
_mov_reader = nullptr;
|
||||
closeFile();
|
||||
}
|
||||
|
||||
void MP4Demuxer::openMP4(const string &file){
|
||||
openFile(file.data(),"rb+");
|
||||
_mov_reader = createReader();
|
||||
getAllTracks();
|
||||
_duration_ms = mov_reader_getduration(_mov_reader.get());
|
||||
}
|
||||
|
||||
int MP4Demuxer::getAllTracks() {
|
||||
static mov_reader_trackinfo_t s_on_track = {
|
||||
[](void *param, uint32_t track, uint8_t object, int width, int height, const void *extra, size_t bytes) {
|
||||
@ -119,14 +122,22 @@ void MP4Demuxer::onAudioTrack(uint32_t track_id, uint8_t object, int channel_cou
|
||||
case MOV_OBJECT_AAC:{
|
||||
auto audio = std::make_shared<AACTrack>(bytes > 0 ? string((char *)extra,bytes) : "");
|
||||
_track_to_codec.emplace(track_id, audio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MOV_OBJECT_G711a:
|
||||
case MOV_OBJECT_G711u:{
|
||||
auto audio = std::make_shared<G711Track>(object == MOV_OBJECT_G711a ? CodecG711A : CodecG711U, sample_rate, channel_count, bit_per_sample / channel_count );
|
||||
_track_to_codec.emplace(track_id, audio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MOV_OBJECT_OPUS: {
|
||||
auto audio = std::make_shared<OpusTrack>();
|
||||
_track_to_codec.emplace(track_id, audio);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WarnL << "不支持该编码类型的MP4,已忽略:" << getObjectName(object);
|
||||
break;
|
||||
@ -149,6 +160,8 @@ struct Context{
|
||||
BufferRaw::Ptr buffer;
|
||||
};
|
||||
|
||||
#define DATA_OFFSET ADTS_HEADER_LEN
|
||||
|
||||
Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||
keyFrame = false;
|
||||
eof = false;
|
||||
@ -163,9 +176,9 @@ Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||
static mov_onalloc mov_onalloc = [](void *param, int bytes) -> void * {
|
||||
Context *ctx = (Context *) param;
|
||||
ctx->buffer = ctx->thiz->_buffer_pool.obtain();
|
||||
ctx->buffer->setCapacity(bytes + 1);
|
||||
ctx->buffer->setSize(bytes);
|
||||
return ctx->buffer->data();
|
||||
ctx->buffer->setCapacity(bytes + DATA_OFFSET + 1);
|
||||
ctx->buffer->setSize(bytes + DATA_OFFSET);
|
||||
return ctx->buffer->data() + DATA_OFFSET;
|
||||
};
|
||||
|
||||
Context ctx = {this, 0};
|
||||
@ -189,59 +202,49 @@ Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Parent>
|
||||
class FrameWrapper : public Parent{
|
||||
public:
|
||||
~FrameWrapper() = default;
|
||||
FrameWrapper(const Buffer::Ptr &buf, int64_t pts, int64_t dts, int prefix) : Parent(buf->data(), buf->size(), dts, pts, prefix){
|
||||
_buf = buf;
|
||||
}
|
||||
bool cacheAble() const override {
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
Buffer::Ptr _buf;
|
||||
};
|
||||
|
||||
Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int64_t pts, int64_t dts) {
|
||||
auto it = _track_to_codec.find(track_id);
|
||||
if (it == _track_to_codec.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto numBytes = buf->size();
|
||||
auto pBytes = buf->data();
|
||||
auto bytes = buf->size() - DATA_OFFSET;
|
||||
auto data = buf->data() + DATA_OFFSET;
|
||||
auto codec = it->second->getCodecId();
|
||||
switch (codec) {
|
||||
case CodecH264 :
|
||||
case CodecH265 : {
|
||||
uint32_t iOffset = 0;
|
||||
while (iOffset < numBytes) {
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pBytes + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
if (iFrameLen + iOffset + 4 > numBytes) {
|
||||
uint32_t offset = 0;
|
||||
while (offset < bytes) {
|
||||
uint32_t frame_len;
|
||||
memcpy(&frame_len, data + offset, 4);
|
||||
frame_len = ntohl(frame_len);
|
||||
if (frame_len + offset + 4 > bytes) {
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4);
|
||||
iOffset += (iFrameLen + 4);
|
||||
memcpy(data + offset, "\x0\x0\x0\x1", 4);
|
||||
offset += (frame_len + 4);
|
||||
}
|
||||
if (codec == CodecH264) {
|
||||
return std::make_shared<FrameWrapper<H264FrameNoCacheAble> >(buf, pts, dts, 4);
|
||||
return std::make_shared<FrameWrapper<H264FrameNoCacheAble> >(buf, dts, pts, 4, DATA_OFFSET);
|
||||
}
|
||||
return std::make_shared<FrameWrapper<H265FrameNoCacheAble> >(buf, pts, dts, 4);
|
||||
return std::make_shared<FrameWrapper<H265FrameNoCacheAble> >(buf, dts, pts, 4, DATA_OFFSET);
|
||||
}
|
||||
|
||||
case CodecAAC :
|
||||
return std::make_shared<FrameWrapper<AACFrameNoCacheAble> >(buf, pts, dts, 0);
|
||||
case CodecAAC: {
|
||||
AACTrack::Ptr track = dynamic_pointer_cast<AACTrack>(it->second);
|
||||
assert(track);
|
||||
//加上adts头
|
||||
dumpAacConfig(track->getAacCfg(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN);
|
||||
return std::make_shared<FrameWrapper<FrameFromPtr> >(buf, dts, pts, ADTS_HEADER_LEN, DATA_OFFSET - ADTS_HEADER_LEN, codec);
|
||||
}
|
||||
|
||||
case CodecOpus:
|
||||
case CodecG711A:
|
||||
case CodecG711U: {
|
||||
auto frame = std::make_shared<FrameWrapper<G711FrameNoCacheAble> >(buf, pts, dts, 0);
|
||||
frame->setCodec(codec);
|
||||
return frame;
|
||||
return std::make_shared<FrameWrapper<FrameFromPtr> >(buf, dts, pts, 0, DATA_OFFSET, codec);
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +256,7 @@ vector<Track::Ptr> MP4Demuxer::getTracks(bool trackReady) const {
|
||||
}
|
||||
ret.push_back(pr.second);
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t MP4Demuxer::getDurationMS() const {
|
||||
|
@ -16,24 +16,60 @@
|
||||
#include "Util/ResourcePool.h"
|
||||
namespace mediakit {
|
||||
|
||||
class MP4Demuxer : public MP4File, public TrackSource{
|
||||
class MP4Demuxer : public MP4FileDisk, public TrackSource{
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Demuxer> Ptr;
|
||||
MP4Demuxer(const char *file);
|
||||
|
||||
/**
|
||||
* 创建mp4解复用器
|
||||
*/
|
||||
MP4Demuxer();
|
||||
~MP4Demuxer() override;
|
||||
|
||||
/**
|
||||
* 打开文件
|
||||
* @param file mp4文件路径
|
||||
*/
|
||||
void openMP4(const string &file);
|
||||
|
||||
/**
|
||||
* 移动时间轴至某处
|
||||
* @param stamp_ms 预期的时间轴位置,单位毫秒
|
||||
* @return 时间轴位置
|
||||
*/
|
||||
int64_t seekTo(int64_t stamp_ms);
|
||||
|
||||
/**
|
||||
* 读取一帧数据
|
||||
* @param keyFrame 是否为关键帧
|
||||
* @param eof 是否文件读取完毕
|
||||
* @return 帧数据,可能为空
|
||||
*/
|
||||
Frame::Ptr readFrame(bool &keyFrame, bool &eof);
|
||||
vector<Track::Ptr> getTracks(bool trackReady) const override ;
|
||||
|
||||
/**
|
||||
* 获取所有Track信息
|
||||
* @param trackReady 是否要求track为就绪状态
|
||||
* @return 所有Track
|
||||
*/
|
||||
vector<Track::Ptr> getTracks(bool trackReady) const override;
|
||||
|
||||
/**
|
||||
* 获取文件长度
|
||||
* @return 文件长度,单位毫秒
|
||||
*/
|
||||
uint64_t getDurationMS() const;
|
||||
|
||||
private:
|
||||
int getAllTracks();
|
||||
void onVideoTrack(uint32_t track_id, uint8_t object, int width, int height, const void* extra, size_t bytes);
|
||||
void onAudioTrack(uint32_t track_id, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void* extra, size_t bytes);
|
||||
Frame::Ptr makeFrame(uint32_t track_id, const Buffer::Ptr &buf,int64_t pts, int64_t dts);
|
||||
void onVideoTrack(uint32_t track_id, uint8_t object, int width, int height, const void *extra, size_t bytes);
|
||||
void onAudioTrack(uint32_t track_id, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void *extra, size_t bytes);
|
||||
Frame::Ptr makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int64_t pts, int64_t dts);
|
||||
|
||||
private:
|
||||
MP4File::Reader _mov_reader;
|
||||
Reader _mov_reader;
|
||||
uint64_t _duration_ms = 0;
|
||||
map<int, Track::Ptr > _track_to_codec;
|
||||
map<int, Track::Ptr> _track_to_codec;
|
||||
ResourcePool<BufferRaw> _buffer_pool;
|
||||
};
|
||||
|
||||
|
@ -14,33 +14,57 @@
|
||||
#include "Extension/H264.h"
|
||||
namespace mediakit{
|
||||
|
||||
MP4Muxer::MP4Muxer(const char *file) {
|
||||
_file_name = file;
|
||||
openMP4();
|
||||
}
|
||||
MP4Muxer::MP4Muxer() {}
|
||||
|
||||
MP4Muxer::~MP4Muxer() {
|
||||
closeMP4();
|
||||
}
|
||||
|
||||
void MP4Muxer::openMP4(){
|
||||
void MP4Muxer::openMP4(const string &file){
|
||||
closeMP4();
|
||||
openFile(_file_name.data(), "wb+");
|
||||
_mov_writter = createWriter();
|
||||
_file_name = file;
|
||||
_mp4_file = std::make_shared<MP4FileDisk>();
|
||||
_mp4_file->openFile(_file_name.data(), "wb+");
|
||||
}
|
||||
|
||||
MP4FileIO::Writer MP4Muxer::createWriter(){
|
||||
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
|
||||
return _mp4_file->createWriter(mp4FastStart ? MOV_FLAG_FASTSTART : 0, false);
|
||||
}
|
||||
|
||||
void MP4Muxer::closeMP4(){
|
||||
_mov_writter = nullptr;
|
||||
closeFile();
|
||||
MP4MuxerInterface::resetTracks();
|
||||
_mp4_file = nullptr;
|
||||
}
|
||||
|
||||
void MP4Muxer::resetTracks() {
|
||||
_codec_to_trackid.clear();
|
||||
_started = false;
|
||||
_have_video = false;
|
||||
openMP4();
|
||||
MP4MuxerInterface::resetTracks();
|
||||
openMP4(_file_name);
|
||||
}
|
||||
|
||||
void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
/////////////////////////////////////////// MP4MuxerInterface /////////////////////////////////////////////
|
||||
|
||||
void MP4MuxerInterface::saveSegment(){
|
||||
mp4_writer_save_segment(_mov_writter.get());
|
||||
}
|
||||
|
||||
void MP4MuxerInterface::initSegment(){
|
||||
mp4_writer_init_segment(_mov_writter.get());
|
||||
}
|
||||
|
||||
bool MP4MuxerInterface::haveVideo() const{
|
||||
return _have_video;
|
||||
}
|
||||
|
||||
void MP4MuxerInterface::resetTracks() {
|
||||
_started = false;
|
||||
_have_video = false;
|
||||
_mov_writter = nullptr;
|
||||
_frameCached.clear();
|
||||
_codec_to_trackid.clear();
|
||||
}
|
||||
|
||||
void MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) {
|
||||
auto it = _codec_to_trackid.find(frame->getCodecId());
|
||||
if(it == _codec_to_trackid.end()){
|
||||
//该Track不存在或初始化失败
|
||||
@ -48,17 +72,13 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
|
||||
if (!_started) {
|
||||
//还没开始
|
||||
if (!_have_video) {
|
||||
_started = true;
|
||||
} else {
|
||||
if (frame->getTrackType() != TrackVideo || !frame->keyFrame()) {
|
||||
//如果首帧是音频或者是视频但是不是i帧,那么不能开始写文件
|
||||
return;
|
||||
}
|
||||
//开始写文件
|
||||
_started = true;
|
||||
//该逻辑确保含有视频时,第一帧为关键帧
|
||||
if (_have_video && !frame->keyFrame()) {
|
||||
//含有视频,但是不是关键帧,那么前面的帧丢弃
|
||||
return;
|
||||
}
|
||||
//开始写文件
|
||||
_started = true;
|
||||
}
|
||||
|
||||
//mp4文件时间戳需要从0开始
|
||||
@ -88,24 +108,23 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
merged.append((char *) &nalu_size, 4);
|
||||
merged.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
});
|
||||
mov_writer_write_l(_mov_writter.get(),
|
||||
mp4_writer_write(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
merged.data(),
|
||||
merged.size(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||
1/*我们合并时已经生成了4个字节的MP4格式start code*/);
|
||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
|
||||
} else {
|
||||
//缓存中只有一帧视频
|
||||
mov_writer_write_l(_mov_writter.get(),
|
||||
mp4_writer_write_l(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
back->data() + back->prefixSize(),
|
||||
back->size() - back->prefixSize(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||
0/*需要生成头4个字节的MP4格式start code*/);
|
||||
1/*需要生成头4个字节的MP4格式start code*/);
|
||||
}
|
||||
_frameCached.clear();
|
||||
}
|
||||
@ -115,14 +134,13 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
break;
|
||||
default: {
|
||||
track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
|
||||
mov_writer_write_l(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
frame->data() + frame->prefixSize(),
|
||||
frame->size() - frame->prefixSize(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||
1/*aac或其他类型frame不用添加4个nalu_size的字节*/);
|
||||
mp4_writer_write(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
frame->data() + frame->prefixSize(),
|
||||
frame->size() - frame->prefixSize(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -140,7 +158,7 @@ static uint8_t getObject(CodecId codecId){
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Muxer::stampSync(){
|
||||
void MP4MuxerInterface::stampSync(){
|
||||
if(_codec_to_trackid.size() < 2){
|
||||
return;
|
||||
}
|
||||
@ -160,7 +178,10 @@ void MP4Muxer::stampSync(){
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
void MP4MuxerInterface::addTrack(const Track::Ptr &track) {
|
||||
if (!_mov_writter) {
|
||||
_mov_writter = createWriter();
|
||||
}
|
||||
auto mp4_object = getObject(track->getCodecId());
|
||||
if (!mp4_object) {
|
||||
WarnL << "MP4录制不支持该编码格式:" << track->getCodecName();
|
||||
@ -182,7 +203,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_audio(_mov_writter.get(),
|
||||
mp4_object,
|
||||
audio_track->getAudioChannel(),
|
||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||
@ -203,7 +224,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_audio(_mov_writter.get(),
|
||||
mp4_object,
|
||||
audio_track->getAudioChannel(),
|
||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||
@ -236,7 +257,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_video(_mov_writter.get(),
|
||||
mp4_object,
|
||||
h264_track->getVideoWidth(),
|
||||
h264_track->getVideoHeight(),
|
||||
@ -271,7 +292,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_video(_mov_writter.get(),
|
||||
mp4_object,
|
||||
h265_track->getVideoWidth(),
|
||||
h265_track->getVideoHeight(),
|
||||
@ -293,5 +314,54 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
stampSync();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////// MP4MuxerMemory /////////////////////////////////////////////
|
||||
|
||||
MP4MuxerMemory::MP4MuxerMemory() {
|
||||
_memory_file = std::make_shared<MP4FileMemory>();
|
||||
}
|
||||
|
||||
MP4FileIO::Writer MP4MuxerMemory::createWriter() {
|
||||
return _memory_file->createWriter(MOV_FLAG_SEGMENT, true);
|
||||
}
|
||||
|
||||
const string &MP4MuxerMemory::getInitSegment(){
|
||||
if (_init_segment.empty()) {
|
||||
initSegment();
|
||||
saveSegment();
|
||||
_init_segment = _memory_file->getAndClearMemory();
|
||||
}
|
||||
return _init_segment;
|
||||
}
|
||||
|
||||
void MP4MuxerMemory::resetTracks(){
|
||||
MP4MuxerInterface::resetTracks();
|
||||
_memory_file = std::make_shared<MP4FileMemory>();
|
||||
_init_segment.clear();
|
||||
}
|
||||
|
||||
void MP4MuxerMemory::inputFrame(const Frame::Ptr &frame){
|
||||
if (_init_segment.empty()) {
|
||||
//尚未生成init segment
|
||||
return;
|
||||
}
|
||||
|
||||
bool key_frame = frame->keyFrame();
|
||||
if (_ticker.elapsedTime() > 50 || key_frame) {
|
||||
//遇到关键帧或者超过50ms则切片
|
||||
_ticker.resetTime();
|
||||
//flush切片
|
||||
saveSegment();
|
||||
//输出切片数据
|
||||
onSegmentData(_memory_file->getAndClearMemory(), frame->dts(), _key_frame);
|
||||
_key_frame = false;
|
||||
}
|
||||
|
||||
if (key_frame) {
|
||||
_key_frame = true;
|
||||
}
|
||||
MP4MuxerInterface::inputFrame(frame);
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//#ifdef ENABLE_MP4
|
||||
|
@ -23,17 +23,16 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class MP4Muxer : public MediaSinkInterface, public MP4File{
|
||||
class MP4MuxerInterface : public MediaSinkInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Muxer> Ptr;
|
||||
|
||||
MP4Muxer(const char *file);
|
||||
~MP4Muxer() override;
|
||||
MP4MuxerInterface() = default;
|
||||
~MP4MuxerInterface() override = default;
|
||||
|
||||
/**
|
||||
* 添加已经ready状态的track
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
void addTrack(const Track::Ptr &track) override;
|
||||
|
||||
/**
|
||||
* 输入帧
|
||||
*/
|
||||
@ -42,30 +41,112 @@ public:
|
||||
/**
|
||||
* 重置所有track
|
||||
*/
|
||||
void resetTracks() override ;
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 是否包含视频
|
||||
*/
|
||||
bool haveVideo() const;
|
||||
|
||||
/**
|
||||
* 保存fmp4分片
|
||||
*/
|
||||
void saveSegment();
|
||||
|
||||
/**
|
||||
* 创建新切片
|
||||
*/
|
||||
void initSegment();
|
||||
|
||||
protected:
|
||||
virtual MP4FileIO::Writer createWriter() = 0;
|
||||
|
||||
private:
|
||||
void stampSync();
|
||||
|
||||
private:
|
||||
bool _started = false;
|
||||
bool _have_video = false;
|
||||
MP4FileIO::Writer _mov_writter;
|
||||
struct track_info {
|
||||
int track_id = -1;
|
||||
Stamp stamp;
|
||||
};
|
||||
List<Frame::Ptr> _frameCached;
|
||||
unordered_map<int, track_info> _codec_to_trackid;
|
||||
};
|
||||
|
||||
class MP4Muxer : public MP4MuxerInterface{
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Muxer> Ptr;
|
||||
|
||||
MP4Muxer();
|
||||
~MP4Muxer() override;
|
||||
|
||||
/**
|
||||
* 重置所有track
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 打开mp4
|
||||
* @param file 文件完整路径
|
||||
*/
|
||||
void openMP4(const string &file);
|
||||
|
||||
/**
|
||||
* 手动关闭文件(对象析构时会自动关闭)
|
||||
*/
|
||||
void closeMP4();
|
||||
|
||||
private:
|
||||
void openMP4();
|
||||
void stampSync();
|
||||
protected:
|
||||
MP4FileIO::Writer createWriter() override;
|
||||
|
||||
private:
|
||||
struct track_info {
|
||||
int track_id = -1;
|
||||
Stamp stamp;
|
||||
};
|
||||
unordered_map<int, track_info> _codec_to_trackid;
|
||||
List<Frame::Ptr> _frameCached;
|
||||
bool _started = false;
|
||||
bool _have_video = false;
|
||||
MP4File::Writer _mov_writter;
|
||||
string _file_name;
|
||||
MP4FileDisk::Ptr _mp4_file;
|
||||
};
|
||||
|
||||
class MP4MuxerMemory : public MP4MuxerInterface{
|
||||
public:
|
||||
MP4MuxerMemory();
|
||||
~MP4MuxerMemory() override = default;
|
||||
|
||||
/**
|
||||
* 重置所有track
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 输入帧
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 获取fmp4 init segment
|
||||
*/
|
||||
const string &getInitSegment();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 输出fmp4切片回调函数
|
||||
* @param string 切片内容
|
||||
* @param stamp 切片末尾时间戳
|
||||
* @param key_frame 是否有关键帧
|
||||
*/
|
||||
virtual void onSegmentData(const string &string, uint32_t stamp, bool key_frame) = 0;
|
||||
|
||||
protected:
|
||||
MP4FileIO::Writer createWriter() override;
|
||||
|
||||
private:
|
||||
bool _key_frame = false;
|
||||
Ticker _ticker;
|
||||
string _init_segment;
|
||||
MP4FileMemory::Ptr _memory_file;
|
||||
};
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//#ifdef ENABLE_MP4
|
||||
#endif //ZLMEDIAKIT_MP4MUXER_H
|
||||
|
@ -29,7 +29,8 @@ MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &
|
||||
strFileName = File::absolutePath(strFileName,recordPath);
|
||||
}
|
||||
|
||||
_demuxer = std::make_shared<MP4Demuxer>(strFileName.data());
|
||||
_demuxer = std::make_shared<MP4Demuxer>();
|
||||
_demuxer->openMP4(strFileName);
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(strVhost, strApp, strId, _demuxer->getDurationMS() / 1000.0, true, true, false, false));
|
||||
auto tracks = _demuxer->getTracks(false);
|
||||
if(tracks.empty()){
|
||||
|
@ -35,6 +35,7 @@ public:
|
||||
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
||||
*/
|
||||
void startReadMP4();
|
||||
|
||||
private:
|
||||
//MediaSourceEvent override
|
||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||
@ -45,15 +46,16 @@ private:
|
||||
uint32_t getCurrentStamp();
|
||||
void setCurrentStamp(uint32_t ui32Stamp);
|
||||
bool seekTo(uint32_t ui32Stamp);
|
||||
|
||||
private:
|
||||
recursive_mutex _mtx;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
bool _have_video = false;
|
||||
uint32_t _seek_to;
|
||||
recursive_mutex _mtx;
|
||||
Ticker _seek_ticker;
|
||||
Timer::Ptr _timer;
|
||||
EventPoller::Ptr _poller;
|
||||
MP4Demuxer::Ptr _demuxer;
|
||||
bool _have_video = false;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
};
|
||||
|
||||
} /* 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