mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-26 12:37:09 +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/JerryLinGd)
|
||||||
[茄子](https://github.com/taotaobujue2008)
|
[茄子](https://github.com/taotaobujue2008)
|
||||||
[好心情](<409257224@qq.com>)
|
[好心情](<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
|
#添加rtp库用于rtp转ps/ts
|
||||||
if(ENABLE_RTPPROXY AND ENABLE_HLS)
|
if(ENABLE_RTPPROXY AND ENABLE_HLS)
|
||||||
message(STATUS "ENABLE_RTPPROXY defined")
|
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)
|
add_definitions(-DENABLE_RTPPROXY)
|
||||||
list(APPEND LINK_LIB_LIST rtp)
|
|
||||||
list(APPEND CXX_API_TARGETS rtp)
|
|
||||||
endif()
|
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的高性能运营级流媒体服务框架
|
# 一个基于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开发,避免使用裸指针,代码稳定可靠,性能优越。
|
- 基于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模式开发,并发性能优越,支持海量客户端连接。
|
- 使用多路复用/多线程/异步网络IO模式开发,并发性能优越,支持海量客户端连接。
|
||||||
- 代码经过长期大量的稳定性、性能测试,已经在线上商用验证已久。
|
- 代码经过长期大量的稳定性、性能测试,已经在线上商用验证已久。
|
||||||
- 支持linux、macos、ios、android、windows全平台。
|
- 支持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))。
|
- 支持画面秒开、极低延时([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/xiongziliang/ZLMediaKit/tree/master/api/include),可以作SDK用,或供其他语言调用。
|
- 提供完善的标准[C API](https://github.com/xia-chu/ZLMediaKit/tree/master/api/include),可以作SDK用,或供其他语言调用。
|
||||||
- 提供完整的[MediaServer](https://github.com/xiongziliang/ZLMediaKit/tree/master/server)服务器,可以免开发直接部署为商用服务器。
|
- 提供完整的[MediaServer](https://github.com/xia-chu/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),支持丰富的业务逻辑。
|
- 提供完善的[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支持都很完善。
|
- 打通了视频监控协议栈与直播协议栈,对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]
|
||||||
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
||||||
@ -38,7 +43,7 @@
|
|||||||
- 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口
|
- 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口
|
||||||
- 支持H265编码
|
- 支持H265编码
|
||||||
- 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式)
|
- 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式)
|
||||||
- 支持任意编码格式的rtsp推流,只是除H264/H265/AAC/G711外无法转协议
|
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||||
|
|
||||||
- RTMP[S]
|
- RTMP[S]
|
||||||
- RTMP[S] 播放服务器,支持RTSP/MP4/HLS转RTMP
|
- RTMP[S] 播放服务器,支持RTSP/MP4/HLS转RTMP
|
||||||
@ -47,16 +52,25 @@
|
|||||||
- RTMP[S] 推流客户端
|
- RTMP[S] 推流客户端
|
||||||
- 支持http[s]-flv直播
|
- 支持http[s]-flv直播
|
||||||
- 支持websocket-flv直播
|
- 支持websocket-flv直播
|
||||||
- 支持任意编码格式的rtmp推流,只是除H264/H265/AAC/G711外无法转协议
|
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||||
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
- 支持[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
|
||||||
- 支持HLS文件生成,自带HTTP文件服务器
|
- 支持HLS文件生成,自带HTTP文件服务器
|
||||||
- 通过cookie追踪技术,可以模拟HLS播放为长连接,实现丰富的业务逻辑
|
- 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务
|
||||||
- 支持完备的HLS用户追踪、播放统计等业务功能,可以实现HLS按需拉流等业务
|
|
||||||
- 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4
|
- 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4
|
||||||
|
- 支持H264/H265/AAC/G711/OPUS编码
|
||||||
|
|
||||||
- HTTP[S]
|
- TS
|
||||||
|
- 支持http[s]-ts直播
|
||||||
|
- 支持ws[s]-ts直播
|
||||||
|
|
||||||
|
- fMP4
|
||||||
|
- 支持http[s]-fmp4直播
|
||||||
|
- 支持ws[s]-fmp4直播
|
||||||
|
|
||||||
|
- HTTP[S]与WebSocket
|
||||||
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
|
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
|
||||||
- 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`
|
- 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`
|
||||||
- 完整HTTP API服务器,可以作为web后台开发框架
|
- 完整HTTP API服务器,可以作为web后台开发框架
|
||||||
@ -65,12 +79,15 @@
|
|||||||
- 支持WebSocket服务器和客户端
|
- 支持WebSocket服务器和客户端
|
||||||
- 支持http文件访问鉴权
|
- 支持http文件访问鉴权
|
||||||
|
|
||||||
- GB28181
|
- GB28181与RTP推流
|
||||||
- 支持UDP/TCP国标RTP(PS或TS)推流,可以转换成RTSP/RTMP/HLS等协议
|
- 支持UDP/TCP国标RTP(PS或TS)推流服务器,可以转换成RTSP/RTMP/HLS等协议
|
||||||
|
- 支持RTSP/RTMP/HLS转国标推流客户端,支持TCP/UDP模式,提供相应restful api
|
||||||
|
- 支持H264/H265/AAC/G711/OPUS编码
|
||||||
|
|
||||||
- 点播
|
- MP4点播与录制
|
||||||
- 支持录制为FLV/HLS/MP4
|
- 支持录制为FLV/HLS/MP4
|
||||||
- RTSP/RTMP/HTTP-FLV/WS-FLV支持MP4文件点播,支持seek
|
- RTSP/RTMP/HTTP-FLV/WS-FLV支持MP4文件点播,支持seek
|
||||||
|
- 支持H264/H265/AAC/G711/OPUS编码
|
||||||
|
|
||||||
- 其他
|
- 其他
|
||||||
- 支持丰富的restful api以及web hook事件
|
- 支持丰富的restful api以及web hook事件
|
||||||
@ -83,28 +100,29 @@
|
|||||||
- 提供c api sdk
|
- 提供c api sdk
|
||||||
- 支持FFmpeg拉流代理任意格式的流
|
- 支持FFmpeg拉流代理任意格式的流
|
||||||
- 支持http api生成并返回实时截图
|
- 支持http api生成并返回实时截图
|
||||||
|
- 支持按需解复用、转协议,当有人观看时才开启转协议
|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
- 2020/5/17 新增支持hls播发器,支持hls拉流代理
|
- 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,分别是:
|
你有三种方法使用ZLMediaKit,分别是:
|
||||||
|
|
||||||
- 1、使用c api,作为sdk使用,请参考[这里](https://github.com/xiongziliang/ZLMediaKit/tree/master/api/include).
|
- 1、使用c api,作为sdk使用,请参考[这里](https://github.com/xia-chu/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).
|
- 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/xiongziliang/ZLMediaKit/tree/master/tests).
|
- 3、如果想做c/c++开发,添加业务逻辑增加功能,可以参考这里的[测试程序](https://github.com/xia-chu/ZLMediaKit/tree/master/tests).
|
||||||
|
|
||||||
## Docker 镜像
|
## Docker 镜像
|
||||||
|
|
||||||
你可以从Docker Hub下载已经编译好的镜像并启动它:
|
你可以从Docker Hub下载已经编译好的镜像并启动它:
|
||||||
|
|
||||||
```bash
|
```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编译镜像:
|
你也可以根据Dockerfile编译镜像:
|
||||||
@ -115,14 +133,17 @@ bash build_docker_images.sh
|
|||||||
|
|
||||||
## 参考案例
|
## 参考案例
|
||||||
|
|
||||||
- [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xiahcu/IOSMedia)
|
- [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xia-chu/IOSMedia)
|
||||||
- [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xiahcu/IOSPlayer)
|
- [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xia-chu/IOSPlayer)
|
||||||
- [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xiongziliang/ZLMediaPlayer)
|
- [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xia-chu/ZLMediaPlayer)
|
||||||
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
||||||
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
|
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
|
||||||
- [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk)
|
- [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)
|
- [GB28181-2016网络视频平台](https://github.com/swwheihei/wvp)
|
||||||
- [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
|
- [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.
|
- 1、仔细看下readme、wiki,如果有必要可以查看下issue.
|
||||||
- 2、如果您的问题还没解决,可以提issue.
|
- 2、如果您的问题还没解决,可以提issue.
|
||||||
- 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提.
|
- 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)
|
[茄子](https://github.com/taotaobujue2008)
|
||||||
[好心情](<409257224@qq.com>)
|
[好心情](<409257224@qq.com>)
|
||||||
[浮沉](https://github.com/MingZhuLiu)
|
[浮沉](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.
|
# 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?
|
## 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.
|
- 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.
|
- Multiplexing asynchronous network IO based on epoll and multi thread,extreme performance.
|
||||||
- Well performance and stable test,can be used commercially.
|
- Well performance and stable test,can be used commercially.
|
||||||
- Support linux, macos, ios, android, Windows Platforms.
|
- Support linux, macos, ios, android, Windows Platforms.
|
||||||
@ -20,15 +24,15 @@
|
|||||||
- RTSP[S] player and pusher.
|
- RTSP[S] player and pusher.
|
||||||
- RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` .
|
- RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` .
|
||||||
- Basic/Digest/Url Authentication.
|
- Basic/Digest/Url Authentication.
|
||||||
- H264/H265/AAC/G711 codec.
|
- H265/H264/AAC/G711/OPUS codec.
|
||||||
- Recorded as mp4.
|
- Recorded as mp4.
|
||||||
- Vod of mp4.
|
- Vod of mp4.
|
||||||
|
|
||||||
- RTMP[S]
|
- RTMP[S]
|
||||||
- RTMP[S] server,support player and pusher.
|
- RTMP[S] server,support player and pusher.
|
||||||
- RTMP[S] player and pusher.
|
- RTMP[S] player and pusher.
|
||||||
- Support HTTP-FLV player.
|
- Support HTTP-FLV/WebSocket-FLV sever.
|
||||||
- H264/H265/AAC/G711 codec.
|
- H265/H264/AAC/G711/OPUS codec.
|
||||||
- Recorded as flv or mp4.
|
- Recorded as flv or mp4.
|
||||||
- Vod of mp4.
|
- Vod of mp4.
|
||||||
- support [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
- support [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
||||||
@ -38,6 +42,12 @@
|
|||||||
- Play authentication based on cookie.
|
- Play authentication based on cookie.
|
||||||
- Support HLS player, support streaming HLS proxy to RTSP / RTMP / MP4.
|
- 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[S]
|
||||||
- HTTP server,suppor directory meun、RESTful http api.
|
- HTTP server,suppor directory meun、RESTful http api.
|
||||||
- HTTP client,downloader,uploader,and http api requester.
|
- HTTP client,downloader,uploader,and http api requester.
|
||||||
@ -56,62 +66,6 @@
|
|||||||
- Support TS / PS streaming push through RTP,and it can be converted to RTSP / RTMP / HLS / FLV.
|
- Support TS / PS streaming push through RTP,and it can be converted to RTSP / RTMP / HLS / FLV.
|
||||||
- Support real-time online screenshot http api.
|
- 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
|
## System Requirements
|
||||||
|
|
||||||
- Compiler support c++11,GCC4.8/Clang3.3/VC2015 or above.
|
- Compiler support c++11,GCC4.8/Clang3.3/VC2015 or above.
|
||||||
@ -191,8 +145,6 @@ git submodule update --init
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Build on Android
|
### Build on Android
|
||||||
|
|
||||||
Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project.
|
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
|
## Docker Image
|
||||||
You can pull a pre-built docker image from Docker Hub and run with
|
You can pull a pre-built docker image from Docker Hub and run with
|
||||||
```bash
|
```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
|
Dockerfile is also supplied to build images on Ubuntu 16.04
|
||||||
@ -307,44 +259,6 @@ cd docker
|
|||||||
docker build -t zlmediakit .
|
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
|
## Contact
|
||||||
- Email:<1213642868@qq.com>
|
- Email:<1213642868@qq.com>
|
||||||
- QQ chat group:542509000
|
- QQ chat group:542509000
|
||||||
|
@ -19,25 +19,25 @@ extern "C" {
|
|||||||
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
||||||
//MP4Info对象的C映射
|
//MP4Info对象的C映射
|
||||||
typedef void* mk_mp4_info;
|
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);
|
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);
|
API_EXPORT float API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx);
|
||||||
//MP4Info::ui64FileSize
|
// 文件大小,单位 BYTE
|
||||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx);
|
||||||
|
|
||||||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||||
@ -276,13 +276,11 @@ typedef void* mk_publish_auth_invoker;
|
|||||||
/**
|
/**
|
||||||
* 执行Broadcast::PublishAuthInvoker
|
* 执行Broadcast::PublishAuthInvoker
|
||||||
* @param err_msg 为空或null则代表鉴权成功
|
* @param err_msg 为空或null则代表鉴权成功
|
||||||
* @param enable_rtxp rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp
|
|
||||||
* @param enable_hls 是否允许转换hls
|
* @param enable_hls 是否允许转换hls
|
||||||
* @param enable_mp4 是否运行MP4录制
|
* @param enable_mp4 是否运行MP4录制
|
||||||
*/
|
*/
|
||||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
||||||
const char *err_msg,
|
const char *err_msg,
|
||||||
int enable_rtxp,
|
|
||||||
int enable_hls,
|
int enable_hls,
|
||||||
int enable_mp4);
|
int enable_mp4);
|
||||||
|
|
||||||
|
@ -26,14 +26,12 @@ typedef void *mk_media;
|
|||||||
* @param app 应用名,推荐为live
|
* @param app 应用名,推荐为live
|
||||||
* @param stream 流id,例如camera
|
* @param stream 流id,例如camera
|
||||||
* @param duration 时长(单位秒),直播则为0
|
* @param duration 时长(单位秒),直播则为0
|
||||||
* @param rtsp_enabled 是否启用rtsp协议
|
|
||||||
* @param rtmp_enabled 是否启用rtmp协议
|
|
||||||
* @param hls_enabled 是否生成hls
|
* @param hls_enabled 是否生成hls
|
||||||
* @param mp4_enabled 是否生成mp4
|
* @param mp4_enabled 是否生成mp4
|
||||||
* @return 对象指针
|
* @return 对象指针
|
||||||
*/
|
*/
|
||||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration,
|
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream,
|
||||||
int rtsp_enabled, int rtmp_enabled, int hls_enabled, int mp4_enabled);
|
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 ctx 对象指针
|
||||||
* @param track_id 2:CodecAAC/3:CodecG711A/4:CodecG711U
|
* @param track_id 2:CodecAAC/3:CodecG711A/4:CodecG711U/5:OPUS
|
||||||
* @param channel 通道数
|
* @param channel 通道数
|
||||||
* @param sample_bit 采样位数,只支持16
|
* @param sample_bit 采样位数,只支持16
|
||||||
* @param sample_rate 采样率
|
* @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 data 不包含adts头的单帧AAC数据
|
||||||
* @param len 单帧AAC数据字节数
|
* @param len 单帧AAC数据字节数
|
||||||
* @param dts 时间戳,毫秒
|
* @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);
|
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);
|
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 ctx 对象指针
|
||||||
* @param data 单帧G711数据
|
* @param data 单帧音频数据
|
||||||
* @param len 单帧G711数据字节数
|
* @param len 单帧音频数据字节数
|
||||||
* @param dts 时间戳,毫秒
|
* @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()回调事件
|
* MediaSource.close()回调事件
|
||||||
|
@ -22,5 +22,6 @@
|
|||||||
#include "mk_tcp.h"
|
#include "mk_tcp.h"
|
||||||
#include "mk_util.h"
|
#include "mk_util.h"
|
||||||
#include "mk_thread.h"
|
#include "mk_thread.h"
|
||||||
|
#include "mk_rtp_server.h"
|
||||||
|
|
||||||
#endif /* MK_API_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 user_data 用户数据指针
|
||||||
* @param track_type 0:视频,1:音频
|
* @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 data 数据指针
|
||||||
* @param len 数据长度
|
* @param len 数据长度
|
||||||
* @param dts 解码时间戳,单位毫秒
|
* @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,
|
s_events.on_mk_media_publish((mk_media_info) &args,
|
||||||
(mk_publish_auth_invoker) &invoker,
|
(mk_publish_auth_invoker) &invoker,
|
||||||
(mk_sock_info) &sender);
|
(mk_sock_info) &sender);
|
||||||
}else{
|
} else {
|
||||||
GET_CONFIG(bool,toRtxp,General::kPublishToRtxp);
|
GET_CONFIG(bool, toHls, General::kPublishToHls);
|
||||||
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
GET_CONFIG(bool, toMP4, General::kPublishToMP4);
|
||||||
GET_CONFIG(bool,toMP4,General::kPublishToMP4);
|
invoker("", toHls, toMP4);
|
||||||
invoker("",toRtxp,toHls,toMP4);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,65 +18,65 @@
|
|||||||
#include "Rtsp/RtspSession.h"
|
#include "Rtsp/RtspSession.h"
|
||||||
using namespace mediakit;
|
using namespace mediakit;
|
||||||
|
|
||||||
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
|
///////////////////////////////////////////RecordInfo/////////////////////////////////////////////
|
||||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx){
|
API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->ui64StartedTime;
|
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);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->ui64TimeLen;
|
return info->time_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx){
|
API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->ui64FileSize;
|
return info->file_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strFilePath.c_str();
|
return info->file_path.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strFileName.c_str();
|
return info->file_name.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strFolder.c_str();
|
return info->folder.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strUrl.c_str();
|
return info->url.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strVhost.c_str();
|
return info->vhost.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strAppName.c_str();
|
return info->app.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){
|
API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MP4Info *info = (MP4Info *)ctx;
|
RecordInfo *info = (RecordInfo *)ctx;
|
||||||
return info->strStreamId.c_str();
|
return info->stream.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
///////////////////////////////////////////Parser/////////////////////////////////////////////
|
||||||
@ -256,7 +256,7 @@ static C get_http_header( const char *response_header[]){
|
|||||||
}
|
}
|
||||||
break;
|
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){
|
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/////////////////////////////////////////////
|
///////////////////////////////////////////Broadcast::PublishAuthInvoker/////////////////////////////////////////////
|
||||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx,
|
||||||
const char *err_msg,
|
const char *err_msg,
|
||||||
int enable_rtxp,
|
|
||||||
int enable_hls,
|
int enable_hls,
|
||||||
int enable_mp4){
|
int enable_mp4){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)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){
|
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;
|
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){
|
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();
|
return (*obj)->getChannel()->totalReaderCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration,
|
API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream,
|
||||||
int rtsp_enabled, int rtmp_enabled, int hls_enabled, int mp4_enabled) {
|
float duration, int hls_enabled, int mp4_enabled) {
|
||||||
assert(vhost && app && stream);
|
assert(vhost && app && stream);
|
||||||
MediaHelper::Ptr *obj(new MediaHelper::Ptr(new MediaHelper(vhost, app, stream, duration,
|
MediaHelper::Ptr *obj(new MediaHelper::Ptr(new MediaHelper(vhost, app, stream, duration, hls_enabled, mp4_enabled)));
|
||||||
rtsp_enabled, rtmp_enabled, hls_enabled, mp4_enabled)));
|
|
||||||
(*obj)->attachEvent();
|
(*obj)->attachEvent();
|
||||||
return (mk_media) obj;
|
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
|
#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);
|
assert(ctx && data && len > 0);
|
||||||
MediaHelper::Ptr* obj = (MediaHelper::Ptr*) ctx;
|
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);
|
strong_self->onData(frame);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (auto &track : _player->getTracks()) {
|
for (auto &track : _player->getTracks(false)) {
|
||||||
track->addDelegate(delegate);
|
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) {
|
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);
|
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;
|
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_stream(url_info),
|
||||||
mk_media_info_get_params(url_info));
|
mk_media_info_get_params(url_info));
|
||||||
|
|
||||||
//允许推流,并且允许转rtxp/hls/mp4
|
//允许推流,并且允许转hls/mp4
|
||||||
mk_publish_auth_invoker_do(invoker, NULL, 1, 1, 1);
|
mk_publish_auth_invoker_do(invoker, NULL, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,8 +40,6 @@ addMuteAudio=1
|
|||||||
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||||
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||||
resetWhenRePlay=1
|
resetWhenRePlay=1
|
||||||
#是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
|
||||||
publishToRtxp=1
|
|
||||||
#是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
#是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||||
publishToHls=1
|
publishToHls=1
|
||||||
#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||||
@ -68,6 +66,8 @@ segDur=2
|
|||||||
segNum=3
|
segNum=3
|
||||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||||
segRetain=5
|
segRetain=5
|
||||||
|
# 是否广播 ts 切片完成通知
|
||||||
|
broadcastRecordTs=0
|
||||||
|
|
||||||
[hook]
|
[hook]
|
||||||
#在推流时,如果url参数匹对admin_params,那么可以不经过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
|
on_publish=https://127.0.0.1/index/hook/on_publish
|
||||||
#录制mp4切片完成事件
|
#录制mp4切片完成事件
|
||||||
on_record_mp4=https://127.0.0.1/index/hook/on_record_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的用户名密码
|
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
|
||||||
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
|
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
|
||||||
#rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
|
#rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
|
||||||
|
@ -1048,6 +1048,108 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": []
|
"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": [
|
"event": [
|
||||||
@ -1074,17 +1176,17 @@
|
|||||||
],
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"id": "0e272976-965b-4f25-8b9e-5916c59234d7",
|
"id": "ce426571-eb1e-4067-8901-01978c982fed",
|
||||||
"key": "ZLMediaKit_URL",
|
"key": "ZLMediaKit_URL",
|
||||||
"value": "zlmediakit.com:8880"
|
"value": "zlmediakit.com:8880"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "321374c3-3357-4405-915e-9cb524d95fbc",
|
"id": "2d3dfd4a-a39c-47d8-a3e9-37d80352ea5f",
|
||||||
"key": "ZLMediaKit_secret",
|
"key": "ZLMediaKit_secret",
|
||||||
"value": "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
"value": "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "468ce1f6-ec79-44d2-819e-5cb9f42cd396",
|
"id": "0aacc473-3a2e-4ef9-b415-e86ce71e0c42",
|
||||||
"key": "defaultVhost",
|
"key": "defaultVhost",
|
||||||
"value": "__defaultVhost__"
|
"value": "__defaultVhost__"
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "Util/File.h"
|
#include "Util/File.h"
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
#include "Thread/WorkThreadPool.h"
|
#include "Thread/WorkThreadPool.h"
|
||||||
|
#include "Network/sockutil.h"
|
||||||
|
|
||||||
namespace FFmpeg {
|
namespace FFmpeg {
|
||||||
#define FFmpeg_FIELD "ffmpeg."
|
#define FFmpeg_FIELD "ffmpeg."
|
||||||
@ -45,6 +46,18 @@ FFmpegSource::~FFmpegSource() {
|
|||||||
DebugL;
|
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) {
|
void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) {
|
||||||
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
|
GET_CONFIG(string,ffmpeg_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));
|
_process.run(cmd,ffmpeg_log.empty() ? "" : File::absolutePath("",ffmpeg_log));
|
||||||
InfoL << cmd;
|
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){
|
if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){
|
||||||
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
|
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
|
||||||
@ -179,7 +192,7 @@ void FFmpegSource::startTimer(int timeout_ms) {
|
|||||||
//自身已经销毁
|
//自身已经销毁
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (strongSelf->_media_info._host == "127.0.0.1") {
|
if (is_local_ip(strongSelf->_media_info._host)) {
|
||||||
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
||||||
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
||||||
//同步查找流
|
//同步查找流
|
||||||
@ -232,33 +245,19 @@ bool FFmpegSource::close(MediaSource &sender, bool force) {
|
|||||||
return true;
|
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) {
|
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
||||||
_listener = src->getListener();
|
auto listener = src->getListener();
|
||||||
src->setListener(shared_from_this());
|
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) {
|
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;
|
~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:
|
public:
|
||||||
typedef shared_ptr<FFmpegSource> Ptr;
|
typedef shared_ptr<FFmpegSource> Ptr;
|
||||||
typedef function<void(const SockException &ex)> onPlay;
|
typedef function<void(const SockException &ex)> onPlay;
|
||||||
@ -60,9 +60,6 @@ private:
|
|||||||
|
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool close(MediaSource &sender,bool force) 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:
|
private:
|
||||||
Process _process;
|
Process _process;
|
||||||
@ -72,7 +69,6 @@ private:
|
|||||||
string _src_url;
|
string _src_url;
|
||||||
string _dst_url;
|
string _dst_url;
|
||||||
function<void()> _onClose;
|
function<void()> _onClose;
|
||||||
std::weak_ptr<MediaSourceEvent> _listener;
|
|
||||||
Ticker _replay_ticker;
|
Ticker _replay_ticker;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ static ApiArgsType getAllArgs(const Parser &parser) {
|
|||||||
for (auto &pr : parser.getUrlArgs()) {
|
for (auto &pr : parser.getUrlArgs()) {
|
||||||
allArgs[pr.first] = pr.second;
|
allArgs[pr.first] = pr.second;
|
||||||
}
|
}
|
||||||
return std::move(allArgs);
|
return allArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void addHttpListener(){
|
static inline void addHttpListener(){
|
||||||
@ -596,8 +596,6 @@ void installWebApi() {
|
|||||||
const string &app,
|
const string &app,
|
||||||
const string &stream,
|
const string &stream,
|
||||||
const string &url,
|
const string &url,
|
||||||
bool enable_rtsp,
|
|
||||||
bool enable_rtmp,
|
|
||||||
bool enable_hls,
|
bool enable_hls,
|
||||||
bool enable_mp4,
|
bool enable_mp4,
|
||||||
int rtp_type,
|
int rtp_type,
|
||||||
@ -610,7 +608,7 @@ void installWebApi() {
|
|||||||
return;
|
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;
|
s_proxyMap[key] = player;
|
||||||
|
|
||||||
//指定RTP over TCP(播放rtsp时有效)
|
//指定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
|
//测试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){
|
api_regist2("/index/api/addStreamProxy",[](API_ARGS2){
|
||||||
CHECK_SECRET();
|
CHECK_SECRET();
|
||||||
CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
|
CHECK_ARGS("vhost","app","stream","url");
|
||||||
addStreamProxy(allArgs["vhost"],
|
addStreamProxy(allArgs["vhost"],
|
||||||
allArgs["app"],
|
allArgs["app"],
|
||||||
allArgs["stream"],
|
allArgs["stream"],
|
||||||
allArgs["url"],
|
allArgs["url"],
|
||||||
allArgs["enable_rtsp"],/* 是否rtsp转发 */
|
|
||||||
allArgs["enable_rtmp"],/* 是否rtmp转发 */
|
|
||||||
allArgs["enable_hls"],/* 是否hls转发 */
|
allArgs["enable_hls"],/* 是否hls转发 */
|
||||||
allArgs["enable_mp4"],/* 是否MP4录制 */
|
allArgs["enable_mp4"],/* 是否MP4录制 */
|
||||||
allArgs["rtp_type"],
|
allArgs["rtp_type"],
|
||||||
@ -788,7 +784,14 @@ void installWebApi() {
|
|||||||
CHECK_ARGS("stream_id");
|
CHECK_ARGS("stream_id");
|
||||||
|
|
||||||
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
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){
|
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
|
#endif//ENABLE_RTPPROXY
|
||||||
|
|
||||||
// 开始录制hls或MP4
|
// 开始录制hls或MP4
|
||||||
@ -1031,8 +1067,6 @@ void installWebApi() {
|
|||||||
allArgs["stream"],
|
allArgs["stream"],
|
||||||
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
||||||
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
||||||
true,/* 开启rtsp转发 */
|
|
||||||
true,/* 开启rtmp转发 */
|
|
||||||
true,/* 开启hls转发 */
|
true,/* 开启hls转发 */
|
||||||
false,/* 禁用MP4录制 */
|
false,/* 禁用MP4录制 */
|
||||||
0,//rtp over tcp方式拉流
|
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 kOnStreamChanged = HOOK_FIELD"on_stream_changed";
|
||||||
const string kOnStreamNotFound = HOOK_FIELD"on_stream_not_found";
|
const string kOnStreamNotFound = HOOK_FIELD"on_stream_not_found";
|
||||||
const string kOnRecordMp4 = HOOK_FIELD"on_record_mp4";
|
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 kOnShellLogin = HOOK_FIELD"on_shell_login";
|
||||||
const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader";
|
const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader";
|
||||||
const string kOnHttpAccess = HOOK_FIELD"on_http_access";
|
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()[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()[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()[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()[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()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader";
|
||||||
mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access";
|
mINI::Instance()[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["app"] = args._app;
|
||||||
body["stream"] = args._streamid;
|
body["stream"] = args._streamid;
|
||||||
body["params"] = args._param_strs;
|
body["params"] = args._param_strs;
|
||||||
return std::move(body);
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reportServerStarted(){
|
static void reportServerStarted(){
|
||||||
@ -190,16 +192,16 @@ void installWebHook(){
|
|||||||
GET_CONFIG(string,hook_stream_chaned,Hook::kOnStreamChanged);
|
GET_CONFIG(string,hook_stream_chaned,Hook::kOnStreamChanged);
|
||||||
GET_CONFIG(string,hook_stream_not_found,Hook::kOnStreamNotFound);
|
GET_CONFIG(string,hook_stream_not_found,Hook::kOnStreamNotFound);
|
||||||
GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4);
|
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_shell_login,Hook::kOnShellLogin);
|
||||||
GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader);
|
GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader);
|
||||||
GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
|
GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
|
||||||
GET_CONFIG(bool,toRtxp,General::kPublishToRtxp);
|
|
||||||
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
GET_CONFIG(bool,toHls,General::kPublishToHls);
|
||||||
GET_CONFIG(bool,toMP4,General::kPublishToMP4);
|
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"){
|
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;
|
return;
|
||||||
}
|
}
|
||||||
//异步执行该hook api,防止阻塞NoticeCenter
|
//异步执行该hook api,防止阻塞NoticeCenter
|
||||||
@ -211,27 +213,20 @@ void installWebHook(){
|
|||||||
do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
|
do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
|
||||||
if(err.empty()){
|
if(err.empty()){
|
||||||
//推流鉴权成功
|
//推流鉴权成功
|
||||||
bool enableRtxp = toRtxp;
|
|
||||||
bool enableHls = toHls;
|
bool enableHls = toHls;
|
||||||
bool enableMP4 = toMP4;
|
bool enableMP4 = toMP4;
|
||||||
|
|
||||||
//兼容用户不传递enableRtxp、enableHls、enableMP4参数
|
//兼容用户不传递enableHls、enableMP4参数
|
||||||
if(obj.isMember("enableRtxp")){
|
if (obj.isMember("enableHls")) {
|
||||||
enableRtxp = obj["enableRtxp"].asBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(obj.isMember("enableHls")){
|
|
||||||
enableHls = obj["enableHls"].asBool();
|
enableHls = obj["enableHls"].asBool();
|
||||||
}
|
}
|
||||||
|
if (obj.isMember("enableMP4")) {
|
||||||
if(obj.isMember("enableMP4")){
|
|
||||||
enableMP4 = obj["enableMP4"].asBool();
|
enableMP4 = obj["enableMP4"].asBool();
|
||||||
}
|
}
|
||||||
|
invoker(err, enableHls, enableMP4);
|
||||||
invoker(err,enableRtxp,enableHls,enableMP4);
|
} else {
|
||||||
}else{
|
|
||||||
//推流鉴权失败
|
//推流鉴权失败
|
||||||
invoker(err,false, false, false);
|
invoker(err, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -336,7 +331,7 @@ void installWebHook(){
|
|||||||
//监听播放失败(未找到特定的流)事件
|
//监听播放失败(未找到特定的流)事件
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){
|
||||||
if(!hook_enable || hook_stream_not_found.empty()){
|
if(!hook_enable || hook_stream_not_found.empty()){
|
||||||
closePlayer();
|
// closePlayer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto body = make_json(args);
|
auto body = make_json(args);
|
||||||
@ -347,28 +342,40 @@ void installWebHook(){
|
|||||||
do_http_hook(hook_stream_not_found,body, nullptr);
|
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
|
#ifdef ENABLE_MP4
|
||||||
//录制mp4文件成功后广播
|
//录制mp4文件成功后广播
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
|
||||||
if(!hook_enable || hook_record_mp4.empty()){
|
if (!hook_enable || hook_record_mp4.empty()) {
|
||||||
return;
|
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
|
//执行hook
|
||||||
do_http_hook(hook_record_mp4,body, nullptr);
|
do_http_hook(hook_record_mp4, getRecordInfo(info), nullptr);
|
||||||
});
|
});
|
||||||
#endif //ENABLE_MP4
|
#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){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){
|
||||||
if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
||||||
invoker("");
|
invoker("");
|
||||||
@ -407,7 +414,6 @@ void installWebHook(){
|
|||||||
}
|
}
|
||||||
strongSrc->close(false);
|
strongSrc->close(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,23 +12,17 @@
|
|||||||
#include "Util/logger.h"
|
#include "Util/logger.h"
|
||||||
#include "Util/base64.h"
|
#include "Util/base64.h"
|
||||||
#include "Extension/AAC.h"
|
#include "Extension/AAC.h"
|
||||||
|
#include "Extension/Opus.h"
|
||||||
#include "Extension/G711.h"
|
#include "Extension/G711.h"
|
||||||
#include "Extension/H264.h"
|
#include "Extension/H264.h"
|
||||||
#include "Extension/H265.h"
|
#include "Extension/H265.h"
|
||||||
|
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
DevChannel::DevChannel(const string &vhost,
|
DevChannel::DevChannel(const string &vhost, const string &app, const string &stream_id,
|
||||||
const string &app,
|
float duration, bool enable_hls, bool enable_mp4) :
|
||||||
const string &stream_id,
|
MultiMediaSourceMuxer(vhost, app, stream_id, duration, true, true, enable_hls, enable_mp4) {}
|
||||||
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() {}
|
DevChannel::~DevChannel() {}
|
||||||
|
|
||||||
@ -109,11 +103,12 @@ void DevChannel::inputH265(const char *data, int len, uint32_t dts, uint32_t pts
|
|||||||
inputFrame(frame);
|
inputFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
class FrameAutoDelete : public FrameFromPtr{
|
||||||
public:
|
public:
|
||||||
template <typename ... ARGS>
|
template <typename ... ARGS>
|
||||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
FrameAutoDelete(ARGS && ...args) : FrameFromPtr(std::forward<ARGS>(args)...){}
|
||||||
virtual ~AACFrameCacheAble() {
|
|
||||||
|
~FrameAutoDelete() override {
|
||||||
delete [] _ptr;
|
delete [] _ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,31 +118,32 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
void DevChannel::inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header){
|
void DevChannel::inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header){
|
||||||
if(dts == 0){
|
if (dts == 0) {
|
||||||
dts = (uint32_t)_aTicker[1].elapsedTime();
|
dts = (uint32_t) _aTicker[1].elapsedTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(adts_header){
|
if (adts_header) {
|
||||||
if(adts_header + 7 == data_without_adts){
|
if (adts_header + ADTS_HEADER_LEN == data_without_adts) {
|
||||||
//adts头和帧在一起
|
//adts头和帧在一起
|
||||||
inputFrame(std::make_shared<AACFrameNoCacheAble>((char *)data_without_adts - 7, len + 7, dts, 0, 7));
|
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data_without_adts - ADTS_HEADER_LEN, len + ADTS_HEADER_LEN, dts, 0, ADTS_HEADER_LEN));
|
||||||
}else{
|
} else {
|
||||||
//adts头和帧不在一起
|
//adts头和帧不在一起
|
||||||
char *dataWithAdts = new char[len + 7];
|
char *data_with_adts = new char[len + ADTS_HEADER_LEN];
|
||||||
memcpy(dataWithAdts, adts_header, 7);
|
memcpy(data_with_adts, adts_header, ADTS_HEADER_LEN);
|
||||||
memcpy(dataWithAdts + 7 , data_without_adts , len);
|
memcpy(data_with_adts + ADTS_HEADER_LEN, data_without_adts, len);
|
||||||
inputFrame(std::make_shared<AACFrameCacheAble>(dataWithAdts, len + 7, dts, 0, 7));
|
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) {
|
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);
|
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data, len, dts, 0));
|
||||||
frame->setCodec(_audio->codecId);
|
|
||||||
inputFrame(frame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevChannel::initVideo(const VideoInfo &info) {
|
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 CodecAAC : addTrack(std::make_shared<AACTrack>()); break;
|
||||||
case CodecG711A :
|
case CodecG711A :
|
||||||
case CodecG711U : addTrack(std::make_shared<G711Track>(info.codecId, info.iSampleRate, info.iChannel, info.iSampleBit)); break;
|
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;
|
default: WarnL << "不支持该类型的音频编码类型:" << info.codecId; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,9 @@
|
|||||||
#include "Util/util.h"
|
#include "Util/util.h"
|
||||||
#include "Util/TimeTicker.h"
|
#include "Util/TimeTicker.h"
|
||||||
#include "Common/MultiMediaSourceMuxer.h"
|
#include "Common/MultiMediaSourceMuxer.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_FAAC
|
#ifdef ENABLE_FAAC
|
||||||
#include "Codec/AACEncoder.h"
|
#include "Codec/AACEncoder.h"
|
||||||
#endif //ENABLE_FAAC
|
#endif //ENABLE_FAAC
|
||||||
@ -55,16 +53,10 @@ class DevChannel : public MultiMediaSourceMuxer{
|
|||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<DevChannel> Ptr;
|
typedef std::shared_ptr<DevChannel> Ptr;
|
||||||
//fDuration<=0为直播,否则为点播
|
//fDuration<=0为直播,否则为点播
|
||||||
DevChannel(const string &vhost,
|
DevChannel(const string &vhost, const string &app, const string &stream_id,
|
||||||
const string &app,
|
float duration = 0, bool enable_hls = true, bool enable_mp4 = false);
|
||||||
const string &stream_id,
|
|
||||||
float duration = 0,
|
|
||||||
bool enable_rtsp = true,
|
|
||||||
bool enable_rtmp = true,
|
|
||||||
bool enable_hls = true,
|
|
||||||
bool enable_mp4 = false);
|
|
||||||
|
|
||||||
virtual ~DevChannel();
|
~DevChannel() override ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化视频Track
|
* 初始化视频Track
|
||||||
@ -108,12 +100,12 @@ public:
|
|||||||
void inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header);
|
void inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* G711音频帧
|
* 输入OPUS/G711音频帧
|
||||||
* @param data 音频帧
|
* @param data 音频帧
|
||||||
* @param len 帧数据长度
|
* @param len 帧数据长度
|
||||||
* @param dts 时间戳,单位毫秒
|
* @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
|
#ifdef ENABLE_X264
|
||||||
/**
|
/**
|
||||||
|
@ -161,7 +161,7 @@ vector<Track::Ptr> MediaSink::getTracks(bool trackReady) const{
|
|||||||
}
|
}
|
||||||
ret.emplace_back(pr.second);
|
ret.emplace_back(pr.second);
|
||||||
}
|
}
|
||||||
return std::move(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,6 +36,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void addTrack(const Track::Ptr & track) = 0;
|
virtual void addTrack(const Track::Ptr & track) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加所有Track完毕
|
||||||
|
*/
|
||||||
|
virtual void addTrackCompleted() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置track
|
* 重置track
|
||||||
*/
|
*/
|
||||||
@ -70,7 +75,7 @@ public:
|
|||||||
* 这样会增加生成流的延时,如果添加了音视频双Track,那么可以不调用此方法
|
* 这样会增加生成流的延时,如果添加了音视频双Track,那么可以不调用此方法
|
||||||
* 否则为了降低流注册延时,请手动调用此方法
|
* 否则为了降低流注册延时,请手动调用此方法
|
||||||
*/
|
*/
|
||||||
void addTrackCompleted();
|
void addTrackCompleted() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置track
|
* 重置track
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
* may be found in the AUTHORS file in the root of the source tree.
|
* may be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include "MediaSource.h"
|
#include "MediaSource.h"
|
||||||
#include "Record/MP4Reader.h"
|
#include "Record/MP4Reader.h"
|
||||||
#include "Util/util.h"
|
#include "Util/util.h"
|
||||||
@ -17,16 +16,19 @@
|
|||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
recursive_mutex MediaSource::g_mtxMediaSrc;
|
recursive_mutex s_media_source_mtx;
|
||||||
MediaSource::SchemaVhostAppStreamMap MediaSource::g_mapMediaSrc;
|
MediaSource::SchemaVhostAppStreamMap s_media_source_map;
|
||||||
|
|
||||||
MediaSource::MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) :
|
MediaSource::MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id){
|
||||||
_strSchema(strSchema), _strApp(strApp), _strId(strId) {
|
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||||
if (strVhost.empty()) {
|
if (!enableVhost) {
|
||||||
_strVhost = DEFAULT_VHOST;
|
_vhost = DEFAULT_VHOST;
|
||||||
} else {
|
} else {
|
||||||
_strVhost = strVhost;
|
_vhost = vhost.empty() ? DEFAULT_VHOST : vhost;
|
||||||
}
|
}
|
||||||
|
_schema = schema;
|
||||||
|
_app = app;
|
||||||
|
_stream_id = stream_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSource::~MediaSource() {
|
MediaSource::~MediaSource() {
|
||||||
@ -34,32 +36,28 @@ MediaSource::~MediaSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const string& MediaSource::getSchema() const {
|
const string& MediaSource::getSchema() const {
|
||||||
return _strSchema;
|
return _schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string& MediaSource::getVhost() const {
|
const string& MediaSource::getVhost() const {
|
||||||
return _strVhost;
|
return _vhost;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string& MediaSource::getApp() const {
|
const string& MediaSource::getApp() const {
|
||||||
//获取该源的id
|
//获取该源的id
|
||||||
return _strApp;
|
return _app;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string& MediaSource::getId() const {
|
const string& MediaSource::getId() const {
|
||||||
return _strId;
|
return _stream_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<Track::Ptr> MediaSource::getTracks(bool trackReady) const {
|
vector<Track::Ptr> MediaSource::getTracks(bool ready) const {
|
||||||
auto strongPtr = _track_source.lock();
|
auto listener = _listener.lock();
|
||||||
if(strongPtr){
|
if(!listener){
|
||||||
return strongPtr->getTracks(trackReady);
|
return vector<Track::Ptr>();
|
||||||
}
|
}
|
||||||
return vector<Track::Ptr>();
|
return listener->getTracks(const_cast<MediaSource &>(*this), ready);
|
||||||
}
|
|
||||||
|
|
||||||
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
|
||||||
_track_source = track_src;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||||
@ -77,12 +75,13 @@ int MediaSource::totalReaderCount(){
|
|||||||
}
|
}
|
||||||
return listener->totalReaderCount(*this);
|
return listener->totalReaderCount(*this);
|
||||||
}
|
}
|
||||||
bool MediaSource::seekTo(uint32_t ui32Stamp) {
|
|
||||||
|
bool MediaSource::seekTo(uint32_t stamp) {
|
||||||
auto listener = _listener.lock();
|
auto listener = _listener.lock();
|
||||||
if(!listener){
|
if(!listener){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return listener->seekTo(*this,ui32Stamp);
|
return listener->seekTo(*this, stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaSource::close(bool force) {
|
bool MediaSource::close(bool force) {
|
||||||
@ -93,19 +92,17 @@ bool MediaSource::close(bool force) {
|
|||||||
return listener->close(*this,force);
|
return listener->close(*this,force);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaSource::onNoneReader(){
|
void MediaSource::onReaderChanged(int size) {
|
||||||
auto listener = _listener.lock();
|
auto listener = _listener.lock();
|
||||||
if(!listener){
|
if (listener) {
|
||||||
return;
|
listener->onReaderChanged(*this, size);
|
||||||
}
|
|
||||||
if (listener->totalReaderCount(*this) == 0) {
|
|
||||||
listener->onNoneReader(*this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path){
|
bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path){
|
||||||
auto listener = _listener.lock();
|
auto listener = _listener.lock();
|
||||||
if (!listener) {
|
if (!listener) {
|
||||||
|
WarnL << "未设置MediaSource的事件监听者,setupRecord失败:" << getSchema() << "/" << getVhost() << "/" << getApp() << "/" << getId();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return listener->setupRecord(*this, type, start, custom_path);
|
return listener->setupRecord(*this, type, start, custom_path);
|
||||||
@ -119,13 +116,30 @@ bool MediaSource::isRecording(Recorder::type type){
|
|||||||
return listener->isRecording(*this, 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) {
|
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);
|
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||||
copy = g_mapMediaSrc;
|
copy = s_media_source_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &pr0 : copy) {
|
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){
|
static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, const string &app, const string &id, bool create_new) {
|
||||||
auto src = MediaSource::find_l(info._schema, info._vhost, info._app, info._streamid, true);
|
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) {
|
if (src || !retry) {
|
||||||
cb(src);
|
cb(src);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *listener_tag = session.get();
|
void *listener_tag = session.get();
|
||||||
weak_ptr<TcpSession> weakSession = session;
|
weak_ptr<TcpSession> weak_session = session;
|
||||||
|
|
||||||
GET_CONFIG(int, maxWaitMS, General::kMaxStreamWaitTimeMS);
|
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);
|
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||||
cb(nullptr);
|
cb(nullptr);
|
||||||
return 0;
|
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);
|
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||||
};
|
};
|
||||||
|
|
||||||
function<void()> closePlayer = [cb, cancelAll]() {
|
function<void()> close_player = [cb, cancel_all]() {
|
||||||
cancelAll();
|
cancel_all();
|
||||||
//告诉播放器,流不存在,这样会立即断开播放器
|
//告诉播放器,流不存在,这样会立即断开播放器
|
||||||
cb(nullptr);
|
cb(nullptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto onRegist = [weakSession, info, cb, cancelAll](BroadcastMediaChangedArgs) {
|
auto on_regist = [weak_session, info, cb, cancel_all](BroadcastMediaChangedArgs) {
|
||||||
auto strongSession = weakSession.lock();
|
auto strong_session = weak_session.lock();
|
||||||
if (!strongSession) {
|
if (!strong_session) {
|
||||||
//自己已经销毁
|
//自己已经销毁
|
||||||
cancelAll();
|
cancel_all();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,11 +276,11 @@ void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelAll();
|
cancel_all();
|
||||||
|
|
||||||
//播发器请求的流终于注册上了,切换到自己的线程再回复
|
//播发器请求的流终于注册上了,切换到自己的线程再回复
|
||||||
strongSession->async([weakSession, info, cb]() {
|
strong_session->async([weak_session, info, cb]() {
|
||||||
auto strongSession = weakSession.lock();
|
auto strongSession = weak_session.lock();
|
||||||
if (!strongSession) {
|
if (!strongSession) {
|
||||||
return;
|
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){
|
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);
|
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) {
|
MediaSource::Ptr MediaSource::find(const string &vhost, const string &app, const string &stream_id){
|
||||||
string vhost = vhost_tmp;
|
auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id);
|
||||||
if(vhost.empty()){
|
if (src) {
|
||||||
vhost = DEFAULT_VHOST;
|
return src;
|
||||||
}
|
}
|
||||||
|
src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id);
|
||||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
if (src) {
|
||||||
if(!enableVhost){
|
return src;
|
||||||
vhost = DEFAULT_VHOST;
|
|
||||||
}
|
}
|
||||||
|
return MediaSource::find(HLS_SCHEMA, vhost, app, stream_id);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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();
|
auto listener = _listener.lock();
|
||||||
if (listener) {
|
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 MediaSource::unregist() {
|
||||||
bool ret;
|
bool ret;
|
||||||
{
|
{
|
||||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
//减小互斥锁临界区
|
||||||
ret = searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId,
|
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||||
[&](SchemaVhostAppStreamMap::iterator &it0,
|
ret = searchMedia(s_media_source_map, _schema, _vhost, _app, _stream_id,
|
||||||
VhostAppStreamMap::iterator &it1,
|
[&](SchemaVhostAppStreamMap::iterator &it0, VhostAppStreamMap::iterator &it1,
|
||||||
AppStreamMap::iterator &it2,
|
AppStreamMap::iterator &it2, StreamMap::iterator &it3) {
|
||||||
StreamMap::iterator &it3) {
|
auto strong_self = it3->second.lock();
|
||||||
auto strongMedia = it3->second.lock();
|
if (strong_self && this != strong_self.get()) {
|
||||||
if (strongMedia && this != strongMedia.get()) {
|
//不是自己,不允许反注册
|
||||||
//不是自己,不允许反注册
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
it2->second.erase(it3);
|
||||||
it2->second.erase(it3);
|
eraseIfEmpty(s_media_source_map, it0, it1, it2);
|
||||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
return true;
|
||||||
return true;
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ret){
|
if (ret) {
|
||||||
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
|
emitEvent(false);
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, false, *this);
|
|
||||||
|
|
||||||
auto listener = _listener.lock();
|
|
||||||
if (listener) {
|
|
||||||
listener->onRegist(*this, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
||||||
|
|
||||||
void MediaInfo::parse(const string &url){
|
void MediaInfo::parse(const string &url_in){
|
||||||
//string url = "rtsp://127.0.0.1:8554/live/id?key=val&a=1&&b=2&vhost=vhost.com";
|
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("://");
|
auto schema_pos = url.find("://");
|
||||||
if(schema_pos != string::npos){
|
if (schema_pos != string::npos) {
|
||||||
_schema = url.substr(0,schema_pos);
|
_schema = url.substr(0, schema_pos);
|
||||||
}else{
|
} else {
|
||||||
schema_pos = -3;
|
schema_pos = -3;
|
||||||
}
|
}
|
||||||
auto split_vec = split(url.substr(schema_pos + 3),"/");
|
auto split_vec = split(url.substr(schema_pos + 3), "/");
|
||||||
if(split_vec.size() > 0){
|
if (split_vec.size() > 0) {
|
||||||
auto vhost = split_vec[0];
|
auto vhost = split_vec[0];
|
||||||
auto pos = vhost.find(":");
|
auto pos = vhost.find(":");
|
||||||
if(pos != string::npos){
|
if (pos != string::npos) {
|
||||||
_host = _vhost = vhost.substr(0,pos);
|
_host = _vhost = vhost.substr(0, pos);
|
||||||
_port = vhost.substr(pos + 1);
|
_port = vhost.substr(pos + 1);
|
||||||
} else{
|
} else {
|
||||||
_host = _vhost = vhost;
|
_host = _vhost = vhost;
|
||||||
}
|
}
|
||||||
|
if (_vhost == "localhost" || INADDR_NONE != inet_addr(_vhost.data())) {
|
||||||
if(_vhost == "localhost" || INADDR_NONE != inet_addr(_vhost.data())){
|
|
||||||
//如果访问的是localhost或ip,那么则为默认虚拟主机
|
//如果访问的是localhost或ip,那么则为默认虚拟主机
|
||||||
_vhost = DEFAULT_VHOST;
|
_vhost = DEFAULT_VHOST;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if(split_vec.size() > 1){
|
if (split_vec.size() > 1) {
|
||||||
_app = split_vec[1];
|
_app = split_vec[1];
|
||||||
}
|
}
|
||||||
if(split_vec.size() > 2){
|
if (split_vec.size() > 2) {
|
||||||
string steamid;
|
string stream_id;
|
||||||
for(int i = 2 ; i < split_vec.size() ; ++i){
|
for (int i = 2; i < split_vec.size(); ++i) {
|
||||||
steamid.append(split_vec[i] + "/");
|
stream_id.append(split_vec[i] + "/");
|
||||||
}
|
}
|
||||||
if(steamid.back() == '/'){
|
if (stream_id.back() == '/') {
|
||||||
steamid.pop_back();
|
stream_id.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;
|
|
||||||
}
|
}
|
||||||
|
_streamid = stream_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
auto params = Parser::parseArgs(_param_strs);
|
||||||
if(!enableVhost || _vhost.empty()){
|
if (params.find(VHOST_KEY) != params.end()) {
|
||||||
|
_vhost = params[VHOST_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||||
|
if (!enableVhost || _vhost.empty()) {
|
||||||
//如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
|
//如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
|
||||||
_vhost = DEFAULT_VHOST;
|
_vhost = DEFAULT_VHOST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
|
MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &file_path , bool check_app){
|
||||||
|
|
||||||
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){
|
|
||||||
GET_CONFIG(string, appName, Record::kAppName);
|
GET_CONFIG(string, appName, Record::kAppName);
|
||||||
if (checkApp && app != appName) {
|
if (check_app && app != appName) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#ifdef ENABLE_MP4
|
#ifdef ENABLE_MP4
|
||||||
try {
|
try {
|
||||||
MP4Reader::Ptr pReader(new MP4Reader(vhost, app, stream, filePath));
|
MP4Reader::Ptr pReader(new MP4Reader(vhost, app, stream, file_path));
|
||||||
pReader->startReadMP4();
|
pReader->startReadMP4();
|
||||||
return MediaSource::find(schema, vhost, app, stream);
|
return MediaSource::find(schema, vhost, app, stream);
|
||||||
} catch (std::exception &ex) {
|
} catch (std::exception &ex) {
|
||||||
@ -494,6 +439,136 @@ MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &
|
|||||||
#endif //ENABLE_MP4
|
#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) {
|
static bool isFlushAble_default(bool is_video, uint32_t last_stamp, uint32_t new_stamp, int cache_size) {
|
||||||
if (new_stamp + 500 < last_stamp) {
|
if (new_stamp + 500 < last_stamp) {
|
||||||
//时间戳回退比较大(可能seek中),由于rtp中时间戳是pts,是可能存在一定程度的回退的
|
//时间戳回退比较大(可能seek中),由于rtp中时间戳是pts,是可能存在一定程度的回退的
|
||||||
|
@ -44,33 +44,63 @@ public:
|
|||||||
virtual ~MediaSourceEvent(){};
|
virtual ~MediaSourceEvent(){};
|
||||||
|
|
||||||
// 通知拖动进度条
|
// 通知拖动进度条
|
||||||
virtual bool seekTo(MediaSource &sender,uint32_t ui32Stamp){ return false; }
|
virtual bool seekTo(MediaSource &sender, uint32_t stamp) { return false; }
|
||||||
// 通知其停止推流
|
// 通知其停止产生流
|
||||||
virtual bool close(MediaSource &sender,bool force) { return false;}
|
virtual bool close(MediaSource &sender, bool force) { return false; }
|
||||||
// 观看总人数
|
// 获取观看总人数
|
||||||
virtual int totalReaderCount(MediaSource &sender) = 0;
|
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 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 bool isRecording(MediaSource &sender, Recorder::type type) { return false; };
|
||||||
// 通知无人观看
|
// 获取所有track相关信息
|
||||||
virtual void onNoneReader(MediaSource &sender);
|
virtual vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const { return vector<Track::Ptr>(); };
|
||||||
//流注册或注销事件
|
// 开始发送ps-rtp
|
||||||
virtual void onRegist(MediaSource &sender, bool regist) {};
|
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:
|
private:
|
||||||
Timer::Ptr _async_close_timer;
|
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获取媒体相关信息
|
* 解析url获取媒体相关信息
|
||||||
*/
|
*/
|
||||||
class MediaInfo{
|
class MediaInfo{
|
||||||
public:
|
public:
|
||||||
MediaInfo(){}
|
~MediaInfo() {}
|
||||||
~MediaInfo(){}
|
MediaInfo() {}
|
||||||
MediaInfo(const string &url){ parse(url); }
|
MediaInfo(const string &url) { parse(url); }
|
||||||
void parse(const string &url);
|
void parse(const string &url);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
string _schema;
|
string _schema;
|
||||||
string _host;
|
string _host;
|
||||||
@ -92,9 +122,11 @@ public:
|
|||||||
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap;
|
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap;
|
||||||
typedef unordered_map<string, VhostAppStreamMap > SchemaVhostAppStreamMap;
|
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() ;
|
virtual ~MediaSource() ;
|
||||||
|
|
||||||
|
////////////////获取MediaSource相关信息////////////////
|
||||||
|
|
||||||
// 获取协议类型
|
// 获取协议类型
|
||||||
const string& getSchema() const;
|
const string& getSchema() const;
|
||||||
// 虚拟主机
|
// 虚拟主机
|
||||||
@ -104,13 +136,18 @@ public:
|
|||||||
// 流id
|
// 流id
|
||||||
const string& getId() const;
|
const string& getId() const;
|
||||||
|
|
||||||
// 设置TrackSource
|
|
||||||
void setTrackSource(const std::weak_ptr<TrackSource> &track_src);
|
|
||||||
// 获取所有Track
|
// 获取所有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;
|
const std::weak_ptr<MediaSourceEvent>& getListener() const;
|
||||||
|
|
||||||
@ -119,48 +156,52 @@ public:
|
|||||||
// 观看者个数,包括(hls/rtsp/rtmp)
|
// 观看者个数,包括(hls/rtsp/rtmp)
|
||||||
virtual int totalReaderCount();
|
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);
|
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);
|
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 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);
|
static void for_each_media(const function<void(const Ptr &src)> &cb);
|
||||||
|
|
||||||
// 从mp4文件生成MediaSource
|
// 从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:
|
protected:
|
||||||
void regist() ;
|
//媒体注册
|
||||||
bool unregist();
|
void regist();
|
||||||
|
|
||||||
private:
|
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:
|
private:
|
||||||
string _strSchema;
|
string _schema;
|
||||||
string _strVhost;
|
string _vhost;
|
||||||
string _strApp;
|
string _app;
|
||||||
string _strId;
|
string _stream_id;
|
||||||
std::weak_ptr<MediaSourceEvent> _listener;
|
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) {
|
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);
|
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;
|
virtual void onFlush(std::shared_ptr<packet_list> &, bool key_pos) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -221,9 +266,9 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool _key_pos = false;
|
||||||
policy _policy;
|
policy _policy;
|
||||||
std::shared_ptr<packet_list> _cache;
|
std::shared_ptr<packet_list> _cache;
|
||||||
bool _key_pos = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -8,36 +8,33 @@
|
|||||||
* may be found in the AUTHORS file in the root of the source tree.
|
* may be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include "MultiMediaSourceMuxer.h"
|
#include "MultiMediaSourceMuxer.h"
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
|
///////////////////////////////MultiMuxerPrivate//////////////////////////////////
|
||||||
|
|
||||||
MultiMuxerPrivate::~MultiMuxerPrivate() {}
|
MultiMuxerPrivate::~MultiMuxerPrivate() {}
|
||||||
MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost,
|
MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost, const string &app, const string &stream, float dur_sec,
|
||||||
const string &app,
|
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4) {
|
||||||
const string &stream,
|
_stream_url = vhost + " " + app + " " + stream;
|
||||||
float dur_sec,
|
|
||||||
bool enable_rtsp,
|
|
||||||
bool enable_rtmp,
|
|
||||||
bool enable_hls,
|
|
||||||
bool enable_mp4) {
|
|
||||||
if (enable_rtmp) {
|
if (enable_rtmp) {
|
||||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
||||||
_enable_rtxp = true;
|
|
||||||
}
|
}
|
||||||
if (enable_rtsp) {
|
if (enable_rtsp) {
|
||||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
||||||
_enable_rtxp = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable_hls) {
|
if (enable_hls) {
|
||||||
_hls = Recorder::createRecorder(Recorder::type_hls, vhost, app, stream);
|
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream));
|
||||||
_enable_record = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable_mp4) {
|
if (enable_mp4) {
|
||||||
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream);
|
_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() {
|
void MultiMuxerPrivate::resetTracks() {
|
||||||
@ -47,6 +44,12 @@ void MultiMuxerPrivate::resetTracks() {
|
|||||||
if (_rtsp) {
|
if (_rtsp) {
|
||||||
_rtsp->resetTracks();
|
_rtsp->resetTracks();
|
||||||
}
|
}
|
||||||
|
if (_ts) {
|
||||||
|
_ts->resetTracks();
|
||||||
|
}
|
||||||
|
if (_fmp4) {
|
||||||
|
_fmp4->resetTracks();
|
||||||
|
}
|
||||||
|
|
||||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||||
auto hls = _hls;
|
auto hls = _hls;
|
||||||
@ -61,24 +64,32 @@ void MultiMuxerPrivate::resetTracks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MultiMuxerPrivate::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
void MultiMuxerPrivate::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||||
|
_listener = listener;
|
||||||
if (_rtmp) {
|
if (_rtmp) {
|
||||||
_rtmp->setListener(listener);
|
_rtmp->setListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_rtsp) {
|
if (_rtsp) {
|
||||||
_rtsp->setListener(listener);
|
_rtsp->setListener(listener);
|
||||||
}
|
}
|
||||||
|
if (_ts) {
|
||||||
auto hls_src = getHlsMediaSource();
|
_ts->setListener(listener);
|
||||||
if (hls_src) {
|
}
|
||||||
hls_src->setListener(listener);
|
if (_fmp4) {
|
||||||
|
_fmp4->setListener(listener);
|
||||||
|
}
|
||||||
|
auto hls = _hls;
|
||||||
|
if (hls) {
|
||||||
|
hls->setListener(listener);
|
||||||
}
|
}
|
||||||
_meida_listener = listener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int MultiMuxerPrivate::totalReaderCount() const {
|
int MultiMuxerPrivate::totalReaderCount() const {
|
||||||
auto hls_src = getHlsMediaSource();
|
auto hls = _hls;
|
||||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
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){
|
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 : {
|
case Recorder::type_hls : {
|
||||||
if (start && !_hls) {
|
if (start && !_hls) {
|
||||||
//开始录制
|
//开始录制
|
||||||
_hls = makeRecorder(getTracks(true), type, custom_path, sender);
|
auto hls = dynamic_pointer_cast<HlsRecorder>(makeRecorder(getTracks(true), type, custom_path, sender));
|
||||||
auto hls_src = getHlsMediaSource();
|
if (hls) {
|
||||||
if (hls_src) {
|
|
||||||
//设置HlsMediaSource的事件监听器
|
//设置HlsMediaSource的事件监听器
|
||||||
hls_src->setListener(_meida_listener);
|
hls->setListener(_listener);
|
||||||
hls_src->setTrackSource(shared_from_this());
|
|
||||||
}
|
}
|
||||||
|
_hls = hls;
|
||||||
} else if (!start && _hls) {
|
} else if (!start && _hls) {
|
||||||
//停止录制
|
//停止录制
|
||||||
_hls = nullptr;
|
_hls = nullptr;
|
||||||
}
|
}
|
||||||
_enable_record = _hls || _mp4;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case Recorder::type_mp4 : {
|
case Recorder::type_mp4 : {
|
||||||
@ -117,7 +126,6 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
|||||||
//停止录制
|
//停止录制
|
||||||
_mp4 = nullptr;
|
_mp4 = nullptr;
|
||||||
}
|
}
|
||||||
_enable_record = _hls || _mp4;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default : return false;
|
default : return false;
|
||||||
@ -146,7 +154,7 @@ void MultiMuxerPrivate::setTimeStamp(uint32_t stamp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MultiMuxerPrivate::setTrackListener(Listener *listener) {
|
void MultiMuxerPrivate::setTrackListener(Listener *listener) {
|
||||||
_listener = listener;
|
_track_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||||
@ -156,6 +164,12 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
|||||||
if (_rtsp) {
|
if (_rtsp) {
|
||||||
_rtsp->addTrack(track);
|
_rtsp->addTrack(track);
|
||||||
}
|
}
|
||||||
|
if (_ts) {
|
||||||
|
_ts->addTrack(track);
|
||||||
|
}
|
||||||
|
if (_fmp4) {
|
||||||
|
_fmp4->addTrack(track);
|
||||||
|
}
|
||||||
|
|
||||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||||
auto hls = _hls;
|
auto hls = _hls;
|
||||||
@ -169,7 +183,12 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MultiMuxerPrivate::isEnabled(){
|
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) {
|
void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||||
@ -179,6 +198,13 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
|||||||
if (_rtsp) {
|
if (_rtsp) {
|
||||||
_rtsp->inputFrame(frame);
|
_rtsp->inputFrame(frame);
|
||||||
}
|
}
|
||||||
|
if (_ts) {
|
||||||
|
_ts->inputFrame(frame);
|
||||||
|
}
|
||||||
|
if (_fmp4) {
|
||||||
|
_fmp4->inputFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||||
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
|
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
|
||||||
auto hls = _hls;
|
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() {
|
void MultiMuxerPrivate::onAllTrackReady() {
|
||||||
if (_rtmp) {
|
if (_rtmp) {
|
||||||
_rtmp->setTrackSource(shared_from_this());
|
|
||||||
_rtmp->onAllTrackReady();
|
_rtmp->onAllTrackReady();
|
||||||
}
|
}
|
||||||
if (_rtsp) {
|
if (_rtsp) {
|
||||||
_rtsp->setTrackSource(shared_from_this());
|
|
||||||
_rtsp->onAllTrackReady();
|
_rtsp->onAllTrackReady();
|
||||||
}
|
}
|
||||||
|
if (_fmp4) {
|
||||||
auto hls_src = getHlsMediaSource();
|
_fmp4->onAllTrackReady();
|
||||||
if (hls_src) {
|
|
||||||
hls_src->setTrackSource(shared_from_this());
|
|
||||||
}
|
}
|
||||||
|
if (_track_listener) {
|
||||||
if (_listener) {
|
_track_listener->onAllTrackReady();
|
||||||
_listener->onAllTrackReady();
|
|
||||||
}
|
}
|
||||||
|
InfoL << "stream: " << _stream_url << " , codec info: " << getTrackInfoStr(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSource::Ptr MultiMuxerPrivate::getHlsMediaSource() const {
|
///////////////////////////////MultiMediaSourceMuxer//////////////////////////////////
|
||||||
auto recorder = dynamic_pointer_cast<HlsRecorder>(_hls);
|
|
||||||
if (recorder) {
|
|
||||||
return recorder->getMediaSource();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
MultiMediaSourceMuxer::~MultiMediaSourceMuxer() {}
|
MultiMediaSourceMuxer::~MultiMediaSourceMuxer() {}
|
||||||
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost,
|
|
||||||
const string &app,
|
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec,
|
||||||
const string &stream,
|
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4) {
|
||||||
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.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) {
|
void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||||
_muxer->setMediaListener(shared_from_this());
|
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
|
//拦截事件
|
||||||
|
_muxer->setMediaListener(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<MultiMuxerPrivate::Listener> &listener) {
|
||||||
|
_track_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MultiMediaSourceMuxer::totalReaderCount() const {
|
int MultiMediaSourceMuxer::totalReaderCount() const {
|
||||||
@ -246,62 +291,57 @@ void MultiMediaSourceMuxer::setTimeStamp(uint32_t stamp) {
|
|||||||
_muxer->setTimeStamp(stamp);
|
_muxer->setTimeStamp(stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiMediaSourceMuxer::setTrackListener(Listener *listener) {
|
vector<Track::Ptr> MultiMediaSourceMuxer::getTracks(MediaSource &sender, bool trackReady) const {
|
||||||
_muxer->setTrackListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<Track::Ptr> MultiMediaSourceMuxer::getTracks(bool trackReady) const {
|
|
||||||
return _muxer->getTracks(trackReady);
|
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) {
|
int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
|
||||||
auto listener = _listener.lock();
|
auto listener = _listener.lock();
|
||||||
if (!listener) {
|
if (!listener) {
|
||||||
return _muxer->totalReaderCount();
|
return totalReaderCount();
|
||||||
}
|
}
|
||||||
return listener->totalReaderCount(sender);
|
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) {
|
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) {
|
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||||
return _muxer->isRecording(sender,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) {
|
void MultiMediaSourceMuxer::addTrack(const Track::Ptr &track) {
|
||||||
_muxer->addTrack(track);
|
_muxer->addTrack(track);
|
||||||
}
|
}
|
||||||
@ -310,6 +350,14 @@ void MultiMediaSourceMuxer::addTrackCompleted() {
|
|||||||
_muxer->addTrackCompleted();
|
_muxer->addTrackCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiMediaSourceMuxer::onAllTrackReady(){
|
||||||
|
_muxer->setMediaListener(shared_from_this());
|
||||||
|
auto listener = _track_listener.lock();
|
||||||
|
if(listener){
|
||||||
|
listener->onAllTrackReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MultiMediaSourceMuxer::resetTracks() {
|
void MultiMediaSourceMuxer::resetTracks() {
|
||||||
_muxer->resetTracks();
|
_muxer->resetTracks();
|
||||||
}
|
}
|
||||||
@ -361,26 +409,35 @@ public:
|
|||||||
return _frame->getCodecId();
|
return _frame->getCodecId();
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
Frame::Ptr _frame;
|
|
||||||
int64_t _dts;
|
int64_t _dts;
|
||||||
int64_t _pts;
|
int64_t _pts;
|
||||||
|
Frame::Ptr _frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame) {
|
void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame_in) {
|
||||||
GET_CONFIG(bool,modify_stamp,General::kModifyStamp);
|
GET_CONFIG(bool, modify_stamp, General::kModifyStamp);
|
||||||
if(!modify_stamp){
|
auto frame = frame_in;
|
||||||
//未开启时间戳覆盖
|
if (modify_stamp) {
|
||||||
_muxer->inputFrame(frame);
|
|
||||||
}else{
|
|
||||||
//开启了时间戳覆盖
|
//开启了时间戳覆盖
|
||||||
FrameModifyStamp::Ptr new_frame = std::make_shared<FrameModifyStamp>(frame,_stamp[frame->getTrackType()]);
|
frame = std::make_shared<FrameModifyStamp>(frame, _stamp[frame->getTrackType()]);
|
||||||
//输入时间戳覆盖后的帧
|
|
||||||
_muxer->inputFrame(new_frame);
|
|
||||||
}
|
}
|
||||||
|
_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(){
|
bool MultiMediaSourceMuxer::isEnabled(){
|
||||||
|
#if defined(ENABLE_RTPPROXY)
|
||||||
|
return (_muxer->isEnabled() || _ps_rtp_sender);
|
||||||
|
#else
|
||||||
return _muxer->isEnabled();
|
return _muxer->isEnabled();
|
||||||
|
#endif //ENABLE_RTPPROXY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,14 +10,20 @@
|
|||||||
|
|
||||||
#ifndef ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
|
#ifndef ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
|
||||||
#define 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 "Rtsp/RtspMediaSourceMuxer.h"
|
||||||
#include "Rtmp/RtmpMediaSourceMuxer.h"
|
#include "Rtmp/RtmpMediaSourceMuxer.h"
|
||||||
#include "Record/Recorder.h"
|
#include "TS/TSMediaSourceMuxer.h"
|
||||||
#include "Record/HlsMediaSource.h"
|
#include "FMP4/FMP4MediaSourceMuxer.h"
|
||||||
#include "Record/HlsRecorder.h"
|
|
||||||
namespace mediakit{
|
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:
|
public:
|
||||||
friend class MultiMediaSourceMuxer;
|
friend class MultiMediaSourceMuxer;
|
||||||
typedef std::shared_ptr<MultiMuxerPrivate> Ptr;
|
typedef std::shared_ptr<MultiMuxerPrivate> Ptr;
|
||||||
@ -27,17 +33,12 @@ public:
|
|||||||
virtual ~Listener() = default;
|
virtual ~Listener() = default;
|
||||||
virtual void onAllTrackReady() = 0;
|
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 resetTracks() override;
|
||||||
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||||
int totalReaderCount() const;
|
int totalReaderCount() const;
|
||||||
@ -46,83 +47,67 @@ private:
|
|||||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path);
|
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path);
|
||||||
bool isRecording(MediaSource &sender, Recorder::type type);
|
bool isRecording(MediaSource &sender, Recorder::type type);
|
||||||
bool isEnabled();
|
bool isEnabled();
|
||||||
private:
|
|
||||||
void onTrackReady(const Track::Ptr & track) override;
|
void onTrackReady(const Track::Ptr & track) override;
|
||||||
void onTrackFrame(const Frame::Ptr &frame) override;
|
void onTrackFrame(const Frame::Ptr &frame) override;
|
||||||
void onAllTrackReady() override;
|
void onAllTrackReady() override;
|
||||||
MediaSource::Ptr getHlsMediaSource() const;
|
|
||||||
private:
|
private:
|
||||||
|
string _stream_url;
|
||||||
|
Listener *_track_listener = nullptr;
|
||||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||||
MediaSinkInterface::Ptr _hls;
|
HlsRecorder::Ptr _hls;
|
||||||
MediaSinkInterface::Ptr _mp4;
|
MediaSinkInterface::Ptr _mp4;
|
||||||
Listener *_listener = nullptr;
|
TSMediaSourceMuxer::Ptr _ts;
|
||||||
std::weak_ptr<MediaSourceEvent> _meida_listener;
|
FMP4MediaSourceMuxer::Ptr _fmp4;
|
||||||
bool _enable_rtxp = false;
|
std::weak_ptr<MediaSourceEvent> _listener;
|
||||||
bool _enable_record = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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:
|
public:
|
||||||
typedef MultiMuxerPrivate::Listener Listener;
|
typedef MultiMuxerPrivate::Listener Listener;
|
||||||
typedef std::shared_ptr<MultiMediaSourceMuxer> Ptr;
|
typedef std::shared_ptr<MultiMediaSourceMuxer> Ptr;
|
||||||
|
|
||||||
~MultiMediaSourceMuxer() override;
|
~MultiMediaSourceMuxer() override;
|
||||||
MultiMediaSourceMuxer(const string &vhost,
|
MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec = 0.0,
|
||||||
const string &app,
|
bool enable_rtsp = true, bool enable_rtmp = true, bool enable_hls = true, bool enable_mp4 = false);
|
||||||
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);
|
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随着Track就绪事件监听器
|
||||||
|
* @param listener 事件监听器
|
||||||
|
*/
|
||||||
|
void setTrackListener(const std::weak_ptr<MultiMuxerPrivate::Listener> &listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回总的消费者个数
|
* 返回总的消费者个数
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
int totalReaderCount() const;
|
int totalReaderCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否生效(是否正在转其他协议)
|
||||||
|
*/
|
||||||
|
bool isEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置MediaSource时间戳
|
* 设置MediaSource时间戳
|
||||||
* @param stamp 时间戳
|
* @param stamp 时间戳
|
||||||
*/
|
*/
|
||||||
void setTimeStamp(uint32_t stamp);
|
void setTimeStamp(uint32_t stamp);
|
||||||
|
|
||||||
/**
|
/////////////////////////////////MediaSourceEvent override/////////////////////////////////
|
||||||
* 随着Track就绪事件监听器
|
|
||||||
* @param listener 事件监听器
|
|
||||||
*/
|
|
||||||
void setTrackListener(Listener *listener);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有Track
|
* 获取所有Track
|
||||||
* @param trackReady 是否筛选过滤未就绪的track
|
* @param trackReady 是否筛选过滤未就绪的track
|
||||||
* @return 所有Track
|
* @return 所有Track
|
||||||
*/
|
*/
|
||||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
vector<Track::Ptr> getTracks(MediaSource &sender, 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 观看总人数
|
* 观看总人数
|
||||||
@ -131,19 +116,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
int totalReaderCount(MediaSource &sender) override;
|
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 录制类型
|
* @param type 录制类型
|
||||||
@ -160,18 +132,35 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool isRecording(MediaSource &sender, Recorder::type type) override;
|
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方法
|
* 添加track,内部会调用Track的clone方法
|
||||||
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
|
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
|
||||||
* @param track
|
* @param track 添加音频或视频轨道
|
||||||
*/
|
*/
|
||||||
void addTrack(const Track::Ptr & track) override;
|
void addTrack(const Track::Ptr &track) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加track完毕
|
* 添加track完毕
|
||||||
* @param track
|
|
||||||
*/
|
*/
|
||||||
void addTrackCompleted();
|
void addTrackCompleted() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置track
|
* 重置track
|
||||||
@ -184,14 +173,20 @@ public:
|
|||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override;
|
void inputFrame(const Frame::Ptr &frame) override;
|
||||||
|
|
||||||
|
/////////////////////////////////MultiMuxerPrivate::Listener override/////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否生效(是否正在转其他协议)
|
* 所有track全部就绪
|
||||||
*/
|
*/
|
||||||
bool isEnabled();
|
void onAllTrackReady() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MultiMuxerPrivate::Ptr _muxer;
|
|
||||||
std::weak_ptr<MediaSourceEvent> _listener;
|
|
||||||
Stamp _stamp[2];
|
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
|
}//namespace mediakit
|
||||||
|
@ -44,77 +44,79 @@ void Stamp::setPlayBack(bool playback) {
|
|||||||
|
|
||||||
void Stamp::syncTo(Stamp &other){
|
void Stamp::syncTo(Stamp &other){
|
||||||
_sync_master = &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) {
|
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);
|
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
|
||||||
if(_sync_finished || modifyStamp || _playback){
|
if (_playback) {
|
||||||
//自动生成时间戳或回放或同步完毕
|
//回放允许时间戳回退
|
||||||
if(dts_out < 0) { dts_out = 0; }
|
|
||||||
if(pts_out < 0) { pts_out = 0; }
|
|
||||||
return;
|
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当前时间差
|
//音视频dts当前时间差
|
||||||
int64_t dts_diff = _last_dts - _sync_master->_last_dts;
|
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||||
if(ABS(dts_diff) < 5000){
|
if (ABS(dts_diff) < 5000) {
|
||||||
//如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
//如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||||
_last_relativeStamp = _relativeStamp;
|
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||||
_relativeStamp = _sync_master->_relativeStamp + dts_diff;
|
|
||||||
}
|
}
|
||||||
//下次不用再强制同步
|
//下次不用再强制同步
|
||||||
_sync_master = nullptr;
|
_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;
|
pts = dts;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_playback){
|
if (_playback) {
|
||||||
//这是点播
|
//这是点播
|
||||||
dts_out = dts;
|
dts_out = dts;
|
||||||
pts_out = pts;
|
pts_out = pts;
|
||||||
_relativeStamp = dts_out;
|
_relative_stamp = dts_out;
|
||||||
_last_dts = dts;
|
_last_dts_in = dts;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//pts和dts的差值
|
//pts和dts的差值
|
||||||
int pts_dts_diff = pts - dts;
|
int pts_dts_diff = pts - dts;
|
||||||
|
|
||||||
if(_last_dts != dts){
|
if (_last_dts_in != dts) {
|
||||||
//时间戳发生变更
|
//时间戳发生变更
|
||||||
if(modifyStamp){
|
if (modifyStamp) {
|
||||||
//内部自己生产时间戳
|
//内部自己生产时间戳
|
||||||
_relativeStamp = _ticker.elapsedTime();
|
_relative_stamp = _ticker.elapsedTime();
|
||||||
}else{
|
} else {
|
||||||
_relativeStamp += deltaStamp(dts);
|
_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;
|
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) {
|
void Stamp::setRelativeStamp(int64_t relativeStamp) {
|
||||||
_relativeStamp = relativeStamp;
|
_relative_stamp = relativeStamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t Stamp::getRelativeStamp() const {
|
int64_t Stamp::getRelativeStamp() const {
|
||||||
return _relativeStamp;
|
return _relative_stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
||||||
|
@ -29,6 +29,7 @@ public:
|
|||||||
* @return 时间戳增量
|
* @return 时间戳增量
|
||||||
*/
|
*/
|
||||||
int64_t deltaStamp(int64_t stamp);
|
int64_t deltaStamp(int64_t stamp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t _last_stamp = 0;
|
int64_t _last_stamp = 0;
|
||||||
};
|
};
|
||||||
@ -41,7 +42,7 @@ public:
|
|||||||
~Stamp() = default;
|
~Stamp() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修正时间戳
|
* 求取相对时间戳,同时实现了音视频同步、限制dts回退等功能
|
||||||
* @param dts 输入dts,如果为0则根据系统时间戳生成
|
* @param dts 输入dts,如果为0则根据系统时间戳生成
|
||||||
* @param pts 输入pts,如果为0则等于dts
|
* @param pts 输入pts,如果为0则等于dts
|
||||||
* @param dts_out 输出dts
|
* @param dts_out 输出dts
|
||||||
@ -75,15 +76,20 @@ public:
|
|||||||
void syncTo(Stamp &other);
|
void syncTo(Stamp &other);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
//主要实现音视频时间戳同步功能
|
||||||
void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
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:
|
private:
|
||||||
int64_t _relativeStamp = 0;
|
int64_t _relative_stamp = 0;
|
||||||
int64_t _last_relativeStamp = 0;
|
int64_t _last_dts_in = 0;
|
||||||
int64_t _last_dts = 0;
|
int64_t _last_dts_out = 0;
|
||||||
|
int64_t _last_pts_out = 0;
|
||||||
SmoothTicker _ticker;
|
SmoothTicker _ticker;
|
||||||
bool _playback = false;
|
bool _playback = false;
|
||||||
Stamp *_sync_master = nullptr;
|
Stamp *_sync_master = nullptr;
|
||||||
bool _sync_finished = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//dts生成器,
|
//dts生成器,
|
||||||
@ -93,8 +99,10 @@ public:
|
|||||||
DtsGenerator() = default;
|
DtsGenerator() = default;
|
||||||
~DtsGenerator() = default;
|
~DtsGenerator() = default;
|
||||||
bool getDts(uint32_t pts, uint32_t &dts);
|
bool getDts(uint32_t pts, uint32_t &dts);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool getDts_l(uint32_t pts, uint32_t &dts);
|
bool getDts_l(uint32_t pts, uint32_t &dts);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _dts_pts_offset = 0;
|
uint32_t _dts_pts_offset = 0;
|
||||||
uint32_t _last_dts = 0;
|
uint32_t _last_dts = 0;
|
||||||
|
@ -40,6 +40,7 @@ bool loadIniConfig(const char *ini_path){
|
|||||||
namespace Broadcast {
|
namespace Broadcast {
|
||||||
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
||||||
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
||||||
|
const string kBroadcastRecordTs = "kBroadcastRecoredTs";
|
||||||
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
||||||
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
||||||
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
|
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
|
||||||
@ -63,7 +64,6 @@ const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
|
|||||||
const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
||||||
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
||||||
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
||||||
const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
|
|
||||||
const string kPublishToHls = GENERAL_FIELD"publishToHls";
|
const string kPublishToHls = GENERAL_FIELD"publishToHls";
|
||||||
const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
|
const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
|
||||||
const string kMergeWriteMS = GENERAL_FIELD"mergeWriteMS";
|
const string kMergeWriteMS = GENERAL_FIELD"mergeWriteMS";
|
||||||
@ -76,7 +76,6 @@ onceToken token([](){
|
|||||||
mINI::Instance()[kEnableVhost] = 0;
|
mINI::Instance()[kEnableVhost] = 0;
|
||||||
mINI::Instance()[kAddMuteAudio] = 1;
|
mINI::Instance()[kAddMuteAudio] = 1;
|
||||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||||
mINI::Instance()[kPublishToRtxp] = 1;
|
|
||||||
mINI::Instance()[kPublishToHls] = 1;
|
mINI::Instance()[kPublishToHls] = 1;
|
||||||
mINI::Instance()[kPublishToMP4] = 0;
|
mINI::Instance()[kPublishToMP4] = 0;
|
||||||
mINI::Instance()[kMergeWriteMS] = 0;
|
mINI::Instance()[kMergeWriteMS] = 0;
|
||||||
@ -253,6 +252,8 @@ const string kSegmentRetain = HLS_FIELD"segRetain";
|
|||||||
const string kFileBufSize = HLS_FIELD"fileBufSize";
|
const string kFileBufSize = HLS_FIELD"fileBufSize";
|
||||||
//录制文件路径
|
//录制文件路径
|
||||||
const string kFilePath = HLS_FIELD"filePath";
|
const string kFilePath = HLS_FIELD"filePath";
|
||||||
|
// 是否广播 ts 切片完成通知
|
||||||
|
const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kSegmentDuration] = 2;
|
mINI::Instance()[kSegmentDuration] = 2;
|
||||||
@ -260,6 +261,7 @@ onceToken token([](){
|
|||||||
mINI::Instance()[kSegmentRetain] = 5;
|
mINI::Instance()[kSegmentRetain] = 5;
|
||||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||||
mINI::Instance()[kFilePath] = "./www";
|
mINI::Instance()[kFilePath] = "./www";
|
||||||
|
mINI::Instance()[kBroadcastRecordTs] = false;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Hls
|
} //namespace Hls
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ bool loadIniConfig(const char *ini_path = nullptr);
|
|||||||
#define RTSP_SCHEMA "rtsp"
|
#define RTSP_SCHEMA "rtsp"
|
||||||
#define RTMP_SCHEMA "rtmp"
|
#define RTMP_SCHEMA "rtmp"
|
||||||
#define HLS_SCHEMA "hls"
|
#define HLS_SCHEMA "hls"
|
||||||
|
#define TS_SCHEMA "ts"
|
||||||
|
#define FMP4_SCHEMA "fmp4"
|
||||||
#define DEFAULT_VHOST "__defaultVhost__"
|
#define DEFAULT_VHOST "__defaultVhost__"
|
||||||
|
|
||||||
////////////广播名称///////////
|
////////////广播名称///////////
|
||||||
@ -58,7 +60,11 @@ extern const string kBroadcastMediaChanged;
|
|||||||
|
|
||||||
//录制mp4文件成功后广播
|
//录制mp4文件成功后广播
|
||||||
extern const string kBroadcastRecordMP4;
|
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请求广播
|
//收到http api请求广播
|
||||||
extern const string kBroadcastHttpRequest;
|
extern const string kBroadcastHttpRequest;
|
||||||
@ -86,8 +92,7 @@ extern const string kBroadcastOnRtspAuth;
|
|||||||
//如果errMessage为空则代表鉴权成功
|
//如果errMessage为空则代表鉴权成功
|
||||||
//enableHls: 是否允许转换hls
|
//enableHls: 是否允许转换hls
|
||||||
//enableMP4: 是否运行MP4录制
|
//enableMP4: 是否运行MP4录制
|
||||||
//enableRtxp: rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp
|
typedef std::function<void(const string &errMessage, bool enableHls, bool enableMP4)> PublishAuthInvoker;
|
||||||
typedef std::function<void(const string &errMessage,bool enableRtxp,bool enableHls,bool enableMP4)> PublishAuthInvoker;
|
|
||||||
|
|
||||||
//收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
//收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
||||||
extern const string kBroadcastMediaPublish;
|
extern const string kBroadcastMediaPublish;
|
||||||
@ -165,8 +170,6 @@ extern const string kAddMuteAudio;
|
|||||||
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||||
//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||||
extern const string kResetWhenRePlay;
|
extern const string kResetWhenRePlay;
|
||||||
//是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
|
||||||
extern const string kPublishToRtxp ;
|
|
||||||
//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||||
extern const string kPublishToHls ;
|
extern const string kPublishToHls ;
|
||||||
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||||
@ -284,6 +287,8 @@ extern const string kSegmentRetain;
|
|||||||
extern const string kFileBufSize;
|
extern const string kFileBufSize;
|
||||||
//录制文件路径
|
//录制文件路径
|
||||||
extern const string kFilePath;
|
extern const string kFilePath;
|
||||||
|
// 是否广播 ts 切片完成通知
|
||||||
|
extern const string kBroadcastRecordTs;
|
||||||
} //namespace Hls
|
} //namespace Hls
|
||||||
|
|
||||||
////////////Rtp代理相关配置///////////
|
////////////Rtp代理相关配置///////////
|
||||||
|
@ -92,6 +92,16 @@ static void parseAacConfig(const string &config, AdtsHeader &adts) {
|
|||||||
adts.no_raw_data_blocks_in_frame = 0;
|
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){
|
string makeAacConfig(const uint8_t *hex, int length){
|
||||||
#ifndef ENABLE_MP4
|
#ifndef ENABLE_MP4
|
||||||
if (!(hex[0] == 0xFF && (hex[1] & 0xF0) == 0xF0)) {
|
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
|
#ifndef ENABLE_MP4
|
||||||
AdtsHeader header;
|
AdtsHeader header;
|
||||||
parseAacConfig(config, header);
|
parseAacConfig(config, header);
|
||||||
header.aac_frame_length = length;
|
header.aac_frame_length = ADTS_HEADER_LEN + length;
|
||||||
dumpAdtsHeader(header, out);
|
dumpAdtsHeader(header, out);
|
||||||
return ADTS_HEADER_LEN;
|
return ADTS_HEADER_LEN;
|
||||||
#else
|
#else
|
||||||
|
@ -18,44 +18,10 @@
|
|||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
string makeAacConfig(const uint8_t *hex, int length);
|
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);
|
int dumpAacConfig(const string &config, int length, uint8_t *out, int out_size);
|
||||||
bool parseAacConfig(const string &config, int &samplerate, int &channels);
|
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音频通道
|
* aac音频通道
|
||||||
*/
|
*/
|
||||||
@ -135,6 +101,28 @@ public:
|
|||||||
* @param frame 数据帧
|
* @param frame 数据帧
|
||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override{
|
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()) {
|
if (_cfg.empty()) {
|
||||||
//未获取到aac_cfg信息
|
//未获取到aac_cfg信息
|
||||||
if (frame->prefixSize()) {
|
if (frame->prefixSize()) {
|
||||||
@ -151,7 +139,6 @@ public:
|
|||||||
AudioTrack::inputFrame(frame);
|
AudioTrack::inputFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
/**
|
/**
|
||||||
* 解析2个字节的aac配置
|
* 解析2个字节的aac配置
|
||||||
*/
|
*/
|
||||||
|
@ -21,29 +21,30 @@ static string getAacCfg(const RtmpPacket &thiz) {
|
|||||||
if (!thiz.isCfgFrame()) {
|
if (!thiz.isCfgFrame()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (thiz.strBuf.size() < 4) {
|
if (thiz.buffer.size() < 4) {
|
||||||
WarnL << "bad aac cfg!";
|
WarnL << "bad aac cfg!";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
ret = thiz.strBuf.substr(2);
|
ret = thiz.buffer.substr(2);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool) {
|
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
if (pkt->isCfgFrame()) {
|
if (pkt->isCfgFrame()) {
|
||||||
_aac_cfg = getAacCfg(*pkt);
|
_aac_cfg = getAacCfg(*pkt);
|
||||||
onGetAAC(nullptr, 0, 0);
|
onGetAAC(nullptr, 0, 0);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_aac_cfg.empty()) {
|
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) {
|
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头
|
//生成adts头
|
||||||
char adts_header[32] = {0};
|
char adts_header[32] = {0};
|
||||||
auto size = dumpAacConfig(_aac_cfg, len, (uint8_t *) adts_header, sizeof(adts_header));
|
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()){
|
if(!_aac_cfg.empty()){
|
||||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||||
rtmpPkt->strBuf.clear();
|
rtmpPkt->buffer.clear();
|
||||||
|
|
||||||
//header
|
//header
|
||||||
uint8_t is_config = false;
|
uint8_t is_config = false;
|
||||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||||
rtmpPkt->strBuf.push_back(!is_config);
|
rtmpPkt->buffer.push_back(!is_config);
|
||||||
|
|
||||||
//aac data
|
//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->body_size = rtmpPkt->buffer.size();
|
||||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||||
rtmpPkt->streamId = STREAM_MEDIA;
|
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->timeStamp = frame->dts();
|
rtmpPkt->time_stamp = frame->dts();
|
||||||
rtmpPkt->typeId = MSG_AUDIO;
|
rtmpPkt->type_id = MSG_AUDIO;
|
||||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
RtmpCodec::inputRtmp(rtmpPkt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AACRtmpEncoder::makeAudioConfigPkt() {
|
void AACRtmpEncoder::makeAudioConfigPkt() {
|
||||||
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
||||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||||
rtmpPkt->strBuf.clear();
|
rtmpPkt->buffer.clear();
|
||||||
|
|
||||||
//header
|
//header
|
||||||
uint8_t is_config = true;
|
uint8_t is_config = true;
|
||||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||||
rtmpPkt->strBuf.push_back(!is_config);
|
rtmpPkt->buffer.push_back(!is_config);
|
||||||
//aac config
|
//aac config
|
||||||
rtmpPkt->strBuf.append(_aac_cfg);
|
rtmpPkt->buffer.append(_aac_cfg);
|
||||||
|
|
||||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||||
rtmpPkt->streamId = STREAM_MEDIA;
|
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->timeStamp = 0;
|
rtmpPkt->time_stamp = 0;
|
||||||
rtmpPkt->typeId = MSG_AUDIO;
|
rtmpPkt->type_id = MSG_AUDIO;
|
||||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
RtmpCodec::inputRtmp(rtmpPkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
@ -19,7 +19,7 @@ namespace mediakit{
|
|||||||
/**
|
/**
|
||||||
* aac Rtmp转adts类
|
* aac Rtmp转adts类
|
||||||
*/
|
*/
|
||||||
class AACRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<AACFrame> {
|
class AACRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<FrameImp> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<AACRtmpDecoder> Ptr;
|
typedef std::shared_ptr<AACRtmpDecoder> Ptr;
|
||||||
|
|
||||||
@ -28,10 +28,9 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入Rtmp并解码
|
* 输入Rtmp并解码
|
||||||
* @param Rtmp Rtmp数据包
|
* @param rtmp Rtmp数据包
|
||||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
|
||||||
*/
|
*/
|
||||||
bool inputRtmp(const RtmpPacket::Ptr &Rtmp, bool key_pos = false) override;
|
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||||
|
|
||||||
CodecId getCodecId() const override{
|
CodecId getCodecId() const override{
|
||||||
return CodecAAC;
|
return CodecAAC;
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AACRtp.h"
|
#include "AACRtp.h"
|
||||||
#define AAC_MAX_FRAME_SIZE (2 * 1024)
|
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
@ -68,60 +67,73 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) {
|
|||||||
} else {
|
} else {
|
||||||
_aac_cfg = aacTrack->getAacCfg();
|
_aac_cfg = aacTrack->getAacCfg();
|
||||||
}
|
}
|
||||||
_frame = obtainFrame();
|
obtainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
AACRtpDecoder::AACRtpDecoder() {
|
AACRtpDecoder::AACRtpDecoder() {
|
||||||
_frame = obtainFrame();
|
obtainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
AACFrame::Ptr AACRtpDecoder::obtainFrame() {
|
void AACRtpDecoder::obtainFrame() {
|
||||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||||
auto frame = ResourcePoolHelper<AACFrame>::obtainObj();
|
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||||
frame->_prefix_size = 0;
|
_frame->_prefix_size = 0;
|
||||||
frame->_buffer.clear();
|
_frame->_buffer.clear();
|
||||||
return frame;
|
_frame->_codec_id = CodecAAC;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) {
|
bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) {
|
||||||
//rtp数据开始部分
|
//rtp数据开始部分
|
||||||
uint8_t *ptr = (uint8_t *) rtppack->data() + rtppack->offset;
|
uint8_t *ptr = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||||
//rtp数据末尾
|
//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个数
|
//首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数
|
||||||
const uint16_t au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
uint16_t au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
||||||
//忽略Au-Header区
|
//记录au_header起始指针
|
||||||
ptr += 2 + au_header_count * 2;
|
uint8_t *au_header_ptr = ptr + 2;
|
||||||
|
ptr = au_header_ptr + au_header_count * 2;
|
||||||
|
|
||||||
while (ptr < end) {
|
if (end < ptr) {
|
||||||
auto size = (uint32_t) (end - ptr);
|
//数据不够
|
||||||
if (size > AAC_MAX_FRAME_SIZE) {
|
return false;
|
||||||
size = AAC_MAX_FRAME_SIZE;
|
}
|
||||||
|
|
||||||
|
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();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AACRtpDecoder::flushData() {
|
void AACRtpDecoder::flushData() {
|
||||||
if (_frame->_buffer.empty()) {
|
|
||||||
//没有有效数据
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//插入adts头
|
//插入adts头
|
||||||
char adts_header[32] = {0};
|
char adts_header[32] = {0};
|
||||||
auto size = dumpAacConfig(_aac_cfg, _frame->_buffer.size(), (uint8_t *) adts_header, sizeof(adts_header));
|
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;
|
_frame->_prefix_size = size;
|
||||||
}
|
}
|
||||||
RtpCodec::inputFrame(_frame);
|
RtpCodec::inputFrame(_frame);
|
||||||
_frame = obtainFrame();
|
obtainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace mediakit{
|
|||||||
/**
|
/**
|
||||||
* aac rtp转adts类
|
* aac rtp转adts类
|
||||||
*/
|
*/
|
||||||
class AACRtpDecoder : public RtpCodec , public ResourcePoolHelper<AACFrame> {
|
class AACRtpDecoder : public RtpCodec , public ResourcePoolHelper<FrameImp> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<AACRtpDecoder> Ptr;
|
typedef std::shared_ptr<AACRtpDecoder> Ptr;
|
||||||
|
|
||||||
@ -39,12 +39,13 @@ protected:
|
|||||||
AACRtpDecoder();
|
AACRtpDecoder();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AACFrame::Ptr obtainFrame();
|
void obtainFrame();
|
||||||
void flushData();
|
void flushData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AACFrame::Ptr _frame;
|
FrameImp::Ptr _frame;
|
||||||
string _aac_cfg;
|
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 "H264Rtmp.h"
|
||||||
#include "H265Rtmp.h"
|
#include "H265Rtmp.h"
|
||||||
#include "AACRtmp.h"
|
#include "AACRtmp.h"
|
||||||
#include "G711Rtmp.h"
|
#include "CommonRtmp.h"
|
||||||
#include "H264Rtp.h"
|
#include "H264Rtp.h"
|
||||||
#include "AACRtp.h"
|
#include "AACRtp.h"
|
||||||
#include "G711Rtp.h"
|
|
||||||
#include "H265Rtp.h"
|
#include "H265Rtp.h"
|
||||||
|
#include "CommonRtp.h"
|
||||||
|
#include "Opus.h"
|
||||||
|
#include "G711.h"
|
||||||
#include "Common/Parser.h"
|
#include "Common/Parser.h"
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
@ -42,6 +44,10 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
|||||||
return std::make_shared<AACTrack>(aac_cfg);
|
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) {
|
if (strcasecmp(track->_codec.data(), "PCMA") == 0) {
|
||||||
return std::make_shared<G711Track>(CodecG711A, track->_samplerate, track->_channel, 16);
|
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 interleaved = sdp->getTrackType() * 2;
|
||||||
auto codec_id = sdp->getCodecId();
|
auto codec_id = sdp->getCodecId();
|
||||||
switch (codec_id){
|
switch (codec_id){
|
||||||
case CodecH264 : return std::make_shared<H264RtpEncoder>(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 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 CodecAAC : return std::make_shared<AACRtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||||
|
case CodecOpus :
|
||||||
case CodecG711A :
|
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;
|
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 CodecH264 : return std::make_shared<H264RtpDecoder>();
|
||||||
case CodecH265 : return std::make_shared<H265RtpDecoder>();
|
case CodecH265 : return std::make_shared<H265RtpDecoder>();
|
||||||
case CodecAAC : return std::make_shared<AACRtpDecoder>(track->clone());
|
case CodecAAC : return std::make_shared<AACRtpDecoder>(track->clone());
|
||||||
|
case CodecOpus :
|
||||||
case CodecG711A :
|
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;
|
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,40 +145,35 @@ RtpCodec::Ptr Factory::getRtpDecoderByTrack(const Track::Ptr &track) {
|
|||||||
/////////////////////////////rtmp相关///////////////////////////////////////////
|
/////////////////////////////rtmp相关///////////////////////////////////////////
|
||||||
|
|
||||||
static CodecId getVideoCodecIdByAmf(const AMFValue &val){
|
static CodecId getVideoCodecIdByAmf(const AMFValue &val){
|
||||||
if (val.type() == AMF_STRING){
|
if (val.type() == AMF_STRING) {
|
||||||
auto str = val.as_string();
|
auto str = val.as_string();
|
||||||
if(str == "avc1"){
|
if (str == "avc1") {
|
||||||
return CodecH264;
|
return CodecH264;
|
||||||
}
|
}
|
||||||
if(str == "mp4a"){
|
if (str == "hev1" || str == "hvc1") {
|
||||||
return CodecAAC;
|
|
||||||
}
|
|
||||||
if(str == "hev1" || str == "hvc1"){
|
|
||||||
return CodecH265;
|
return CodecH265;
|
||||||
}
|
}
|
||||||
WarnL << "暂不支持该Amf:" << str;
|
WarnL << "暂不支持该视频Amf:" << str;
|
||||||
return CodecInvalid;
|
return CodecInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.type() != AMF_NULL){
|
if (val.type() != AMF_NULL) {
|
||||||
auto type_id = val.as_integer();
|
auto type_id = val.as_integer();
|
||||||
switch (type_id){
|
switch (type_id) {
|
||||||
case FLV_CODEC_H264: return CodecH264;
|
case FLV_CODEC_H264 : return CodecH264;
|
||||||
case FLV_CODEC_AAC: return CodecAAC;
|
case FLV_CODEC_H265 : return CodecH265;
|
||||||
case FLV_CODEC_H265: return CodecH265;
|
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
|
||||||
default : WarnL << "暂不支持该Amf:" << type_id; return CodecInvalid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CodecInvalid;
|
return CodecInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
|
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
|
||||||
switch (codecId){
|
switch (codecId){
|
||||||
case CodecH264 : return std::make_shared<H264Track>();
|
case CodecH264 : return std::make_shared<H264Track>();
|
||||||
case CodecH265 : return std::make_shared<H265Track>();
|
case CodecH265 : return std::make_shared<H265Track>();
|
||||||
case CodecAAC : return std::make_shared<AACTrack>();
|
case CodecAAC : return std::make_shared<AACTrack>();
|
||||||
|
case CodecOpus: return std::make_shared<OpusTrack>();
|
||||||
case CodecG711A :
|
case CodecG711A :
|
||||||
case CodecG711U : return (sample_rate && channels && sample_bit) ? std::make_shared<G711Track>(codecId, sample_rate, channels, sample_bit) : nullptr;
|
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;
|
default : WarnL << "暂不支持该CodecId:" << codecId; return nullptr;
|
||||||
@ -191,7 +194,7 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
|||||||
if (str == "mp4a") {
|
if (str == "mp4a") {
|
||||||
return CodecAAC;
|
return CodecAAC;
|
||||||
}
|
}
|
||||||
WarnL << "暂不支持该Amf:" << str;
|
WarnL << "暂不支持该音频Amf:" << str;
|
||||||
return CodecInvalid;
|
return CodecInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +204,8 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
|||||||
case FLV_CODEC_AAC : return CodecAAC;
|
case FLV_CODEC_AAC : return CodecAAC;
|
||||||
case FLV_CODEC_G711A : return CodecG711A;
|
case FLV_CODEC_G711A : return CodecG711A;
|
||||||
case FLV_CODEC_G711U : return CodecG711U;
|
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 CodecH264 : return std::make_shared<H264RtmpEncoder>(track);
|
||||||
case CodecAAC : return std::make_shared<AACRtmpEncoder>(track);
|
case CodecAAC : return std::make_shared<AACRtmpEncoder>(track);
|
||||||
case CodecH265 : return std::make_shared<H265RtmpEncoder>(track);
|
case CodecH265 : return std::make_shared<H265RtmpEncoder>(track);
|
||||||
|
case CodecOpus : return std::make_shared<CommonRtmpEncoder>(track);
|
||||||
case CodecG711A :
|
case CodecG711A :
|
||||||
case CodecG711U : {
|
case CodecG711U : {
|
||||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
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 nullptr;
|
||||||
}
|
}
|
||||||
return std::make_shared<G711RtmpEncoder>(track);
|
return std::make_shared<CommonRtmpEncoder>(track);
|
||||||
}
|
}
|
||||||
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
||||||
}
|
}
|
||||||
@ -248,6 +253,7 @@ AMFValue Factory::getAmfByCodecId(CodecId codecId) {
|
|||||||
case CodecH265: return AMFValue(FLV_CODEC_H265);
|
case CodecH265: return AMFValue(FLV_CODEC_H265);
|
||||||
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
|
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
|
||||||
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
|
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
|
||||||
|
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
|
||||||
default: return AMFValue(AMF_NULL);
|
default: return AMFValue(AMF_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,12 @@ public:
|
|||||||
_dts = frame->dts();
|
_dts = frame->dts();
|
||||||
_pts = frame->pts();
|
_pts = frame->pts();
|
||||||
_prefix_size = frame->prefixSize();
|
_prefix_size = frame->prefixSize();
|
||||||
_codecid = frame->getCodecId();
|
_codec_id = frame->getCodecId();
|
||||||
_key = frame->keyFrame();
|
_key = frame->keyFrame();
|
||||||
_config = frame->configFrame();
|
_config = frame->configFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~FrameCacheAble() = default;
|
~FrameCacheAble() override = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可以被缓存
|
* 可以被缓存
|
||||||
@ -49,10 +49,6 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodecId getCodecId() const override{
|
|
||||||
return _codecid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool keyFrame() const override{
|
bool keyFrame() const override{
|
||||||
return _key;
|
return _key;
|
||||||
}
|
}
|
||||||
@ -60,10 +56,10 @@ public:
|
|||||||
bool configFrame() const override{
|
bool configFrame() const override{
|
||||||
return _config;
|
return _config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Frame::Ptr _frame;
|
Frame::Ptr _frame;
|
||||||
BufferRaw::Ptr _buffer;
|
BufferRaw::Ptr _buffer;
|
||||||
CodecId _codecid;
|
|
||||||
bool _key;
|
bool _key;
|
||||||
bool _config;
|
bool _config;
|
||||||
};
|
};
|
||||||
|
@ -148,7 +148,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
CodecId getCodecId() const override{
|
CodecId getCodecId() const override{
|
||||||
return _codecid;
|
return _codec_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
@ -160,7 +160,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CodecId _codecid = CodecInvalid;
|
CodecId _codec_id = CodecInvalid;
|
||||||
string _buffer;
|
string _buffer;
|
||||||
uint32_t _dts = 0;
|
uint32_t _dts = 0;
|
||||||
uint32_t _pts = 0;
|
uint32_t _pts = 0;
|
||||||
@ -314,9 +314,24 @@ private:
|
|||||||
class FrameFromPtr : public Frame{
|
class FrameFromPtr : public Frame{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<FrameFromPtr> Ptr;
|
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{
|
char *data() const override{
|
||||||
return _ptr;
|
return _ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t size() const override {
|
uint32_t size() const override {
|
||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
@ -336,12 +351,80 @@ public:
|
|||||||
bool cacheAble() const override {
|
bool cacheAble() const override {
|
||||||
return false;
|
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:
|
protected:
|
||||||
char *_ptr;
|
char *_ptr;
|
||||||
uint32_t _size;
|
uint32_t _size;
|
||||||
uint32_t _dts;
|
uint32_t _dts;
|
||||||
uint32_t _pts = 0;
|
uint32_t _pts = 0;
|
||||||
uint32_t _prefix_size;
|
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
|
}//namespace mediakit
|
||||||
|
@ -16,47 +16,6 @@
|
|||||||
|
|
||||||
namespace mediakit{
|
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音频通道
|
* 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 std::shared_ptr<H264Frame> Ptr;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NAL_SPS = 7,
|
|
||||||
NAL_PPS = 8,
|
|
||||||
NAL_IDR = 5,
|
NAL_IDR = 5,
|
||||||
NAL_SEI = 6,
|
NAL_SEI = 6,
|
||||||
|
NAL_SPS = 7,
|
||||||
|
NAL_PPS = 8,
|
||||||
|
NAL_AUD = 9,
|
||||||
|
NAL_B_P = 1,
|
||||||
} NalType;
|
} NalType;
|
||||||
|
|
||||||
H264Frame(){
|
H264Frame(){
|
||||||
_codecid = CodecH264;
|
_codec_id = CodecH264;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
@ -68,10 +70,7 @@ public:
|
|||||||
_dts = dts;
|
_dts = dts;
|
||||||
_pts = pts;
|
_pts = pts;
|
||||||
_prefix_size = prefix_size;
|
_prefix_size = prefix_size;
|
||||||
}
|
_codec_id = CodecH264;
|
||||||
|
|
||||||
CodecId getCodecId() const override{
|
|
||||||
return CodecH264;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
@ -182,8 +181,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override{
|
void inputFrame(const Frame::Ptr &frame) override{
|
||||||
int type = H264_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
int type = H264_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
||||||
if(type == H264Frame::NAL_SPS || type == H264Frame::NAL_SEI){
|
if(type != H264Frame::NAL_B_P && type != H264Frame::NAL_IDR){
|
||||||
//有些设备会把SPS PPS IDR帧当做一个帧打包,所以我们要split一下
|
//非I/B/P帧情况下,split一下,防止多个帧粘合在一起
|
||||||
splitH264(frame->data(), frame->size(), frame->prefixSize(), [&](const char *ptr, int len, int prefix) {
|
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);
|
H264FrameInternal::Ptr sub_frame = std::make_shared<H264FrameInternal>(frame, (char *)ptr, len, prefix);
|
||||||
inputFrame_l(sub_frame);
|
inputFrame_l(sub_frame);
|
||||||
@ -227,6 +226,10 @@ private:
|
|||||||
VideoTrack::inputFrame(frame);
|
VideoTrack::inputFrame(frame);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case H264Frame::NAL_AUD:{
|
||||||
|
//忽略AUD帧;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
VideoTrack::inputFrame(frame);
|
VideoTrack::inputFrame(frame);
|
||||||
|
@ -23,10 +23,6 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
|
||||||
return decodeRtmp(rtmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回不带0x00 00 00 01头的sps
|
* 返回不带0x00 00 00 01头的sps
|
||||||
* @return
|
* @return
|
||||||
@ -39,18 +35,18 @@ static string getH264SPS(const RtmpPacket &thiz) {
|
|||||||
if (!thiz.isCfgFrame()) {
|
if (!thiz.isCfgFrame()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (thiz.strBuf.size() < 13) {
|
if (thiz.buffer.size() < 13) {
|
||||||
WarnL << "bad H264 cfg!";
|
WarnL << "bad H264 cfg!";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
uint16_t sps_size ;
|
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);
|
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!";
|
WarnL << "bad H264 cfg!";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
ret.assign(thiz.strBuf.data() + 13, sps_size);
|
ret.assign(thiz.buffer.data() + 13, sps_size);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,60 +62,59 @@ static string getH264PPS(const RtmpPacket &thiz) {
|
|||||||
if (!thiz.isCfgFrame()) {
|
if (!thiz.isCfgFrame()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (thiz.strBuf.size() < 13) {
|
if (thiz.buffer.size() < 13) {
|
||||||
WarnL << "bad H264 cfg!";
|
WarnL << "bad H264 cfg!";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
uint16_t sps_size ;
|
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);
|
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!";
|
WarnL << "bad H264 cfg!";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
uint16_t pps_size ;
|
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);
|
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!";
|
WarnL << "bad H264 cfg!";
|
||||||
return ret;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
if (pkt->isCfgFrame()) {
|
if (pkt->isCfgFrame()) {
|
||||||
//缓存sps pps,后续插入到I帧之前
|
//缓存sps pps,后续插入到I帧之前
|
||||||
_sps = getH264SPS(*pkt);
|
_sps = getH264SPS(*pkt);
|
||||||
_pps = getH264PPS(*pkt);
|
_pps = getH264PPS(*pkt);
|
||||||
onGetH264(_sps.data(), _sps.size(), pkt->timeStamp , pkt->timeStamp);
|
onGetH264(_sps.data(), _sps.size(), pkt->time_stamp , pkt->time_stamp);
|
||||||
onGetH264(_pps.data(), _pps.size(), pkt->timeStamp , pkt->timeStamp);
|
onGetH264(_pps.data(), _pps.size(), pkt->time_stamp , pkt->time_stamp);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pkt->strBuf.size() > 9) {
|
if (pkt->buffer.size() > 9) {
|
||||||
uint32_t iTotalLen = pkt->strBuf.size();
|
uint32_t iTotalLen = pkt->buffer.size();
|
||||||
uint32_t iOffset = 5;
|
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;
|
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){
|
while(iOffset + 4 < iTotalLen){
|
||||||
uint32_t iFrameLen;
|
uint32_t iFrameLen;
|
||||||
memcpy(&iFrameLen, pkt->strBuf.data() + iOffset, 4);
|
memcpy(&iFrameLen, pkt->buffer.data() + iOffset, 4);
|
||||||
iFrameLen = ntohl(iFrameLen);
|
iFrameLen = ntohl(iFrameLen);
|
||||||
iOffset += 4;
|
iOffset += 4;
|
||||||
if(iFrameLen + iOffset > iTotalLen){
|
if(iFrameLen + iOffset > iTotalLen){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
onGetH264(pkt->strBuf.data() + iOffset, iFrameLen, pkt->timeStamp , pts);
|
onGetH264(pkt->buffer.data() + iOffset, iFrameLen, pkt->time_stamp , pts);
|
||||||
iOffset += iFrameLen;
|
iOffset += iFrameLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pkt->isVideoKeyFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
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()) {
|
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) {
|
||||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
RtmpCodec::inputRtmp(_lastPacket);
|
||||||
_lastPacket = nullptr;
|
_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);
|
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||||
|
|
||||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||||
_lastPacket->strBuf.clear();
|
_lastPacket->buffer.clear();
|
||||||
_lastPacket->strBuf.push_back(flags);
|
_lastPacket->buffer.push_back(flags);
|
||||||
_lastPacket->strBuf.push_back(!is_config);
|
_lastPacket->buffer.push_back(!is_config);
|
||||||
auto cts = frame->pts() - frame->dts();
|
auto cts = frame->pts() - frame->dts();
|
||||||
cts = htonl(cts);
|
cts = htonl(cts);
|
||||||
_lastPacket->strBuf.append((char *)&cts + 1, 3);
|
_lastPacket->buffer.append((char *)&cts + 1, 3);
|
||||||
|
|
||||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
_lastPacket->chunk_id = CHUNK_VIDEO;
|
||||||
_lastPacket->streamId = STREAM_MEDIA;
|
_lastPacket->stream_index = STREAM_MEDIA;
|
||||||
_lastPacket->timeStamp = frame->dts();
|
_lastPacket->time_stamp = frame->dts();
|
||||||
_lastPacket->typeId = MSG_VIDEO;
|
_lastPacket->type_id = MSG_VIDEO;
|
||||||
|
|
||||||
}
|
}
|
||||||
auto size = htonl(iLen);
|
auto size = htonl(iLen);
|
||||||
_lastPacket->strBuf.append((char *) &size, 4);
|
_lastPacket->buffer.append((char *) &size, 4);
|
||||||
_lastPacket->strBuf.append(pcData, iLen);
|
_lastPacket->buffer.append(pcData, iLen);
|
||||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||||
@ -227,39 +222,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() {
|
|||||||
bool is_config = true;
|
bool is_config = true;
|
||||||
|
|
||||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||||
rtmpPkt->strBuf.clear();
|
rtmpPkt->buffer.clear();
|
||||||
|
|
||||||
//header
|
//header
|
||||||
rtmpPkt->strBuf.push_back(flags);
|
rtmpPkt->buffer.push_back(flags);
|
||||||
rtmpPkt->strBuf.push_back(!is_config);
|
rtmpPkt->buffer.push_back(!is_config);
|
||||||
//cts
|
//cts
|
||||||
rtmpPkt->strBuf.append("\x0\x0\x0", 3);
|
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||||
|
|
||||||
//AVCDecoderConfigurationRecord start
|
//AVCDecoderConfigurationRecord start
|
||||||
rtmpPkt->strBuf.push_back(1); // version
|
rtmpPkt->buffer.push_back(1); // version
|
||||||
rtmpPkt->strBuf.push_back(_sps[1]); // profile
|
rtmpPkt->buffer.push_back(_sps[1]); // profile
|
||||||
rtmpPkt->strBuf.push_back(_sps[2]); // compat
|
rtmpPkt->buffer.push_back(_sps[2]); // compat
|
||||||
rtmpPkt->strBuf.push_back(_sps[3]); // level
|
rtmpPkt->buffer.push_back(_sps[3]); // level
|
||||||
rtmpPkt->strBuf.push_back(0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
rtmpPkt->buffer.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(0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||||
//sps
|
//sps
|
||||||
uint16_t size = _sps.size();
|
uint16_t size = _sps.size();
|
||||||
size = htons(size);
|
size = htons(size);
|
||||||
rtmpPkt->strBuf.append((char *) &size, 2);
|
rtmpPkt->buffer.append((char *) &size, 2);
|
||||||
rtmpPkt->strBuf.append(_sps);
|
rtmpPkt->buffer.append(_sps);
|
||||||
//pps
|
//pps
|
||||||
rtmpPkt->strBuf.push_back(1); // version
|
rtmpPkt->buffer.push_back(1); // version
|
||||||
size = _pps.size();
|
size = _pps.size();
|
||||||
size = htons(size);
|
size = htons(size);
|
||||||
rtmpPkt->strBuf.append((char *) &size, 2);
|
rtmpPkt->buffer.append((char *) &size, 2);
|
||||||
rtmpPkt->strBuf.append(_pps);
|
rtmpPkt->buffer.append(_pps);
|
||||||
|
|
||||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||||
rtmpPkt->chunkId = CHUNK_VIDEO;
|
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||||
rtmpPkt->streamId = STREAM_MEDIA;
|
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->timeStamp = 0;
|
rtmpPkt->time_stamp = 0;
|
||||||
rtmpPkt->typeId = MSG_VIDEO;
|
rtmpPkt->type_id = MSG_VIDEO;
|
||||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
RtmpCodec::inputRtmp(rtmpPkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
@ -32,17 +32,17 @@ public:
|
|||||||
/**
|
/**
|
||||||
* 输入264 Rtmp包
|
* 输入264 Rtmp包
|
||||||
* @param rtmp 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{
|
CodecId getCodecId() const override{
|
||||||
return CodecH264;
|
return CodecH264;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool decodeRtmp(const RtmpPacket::Ptr &Rtmp);
|
|
||||||
void onGetH264(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
void onGetH264(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
||||||
H264Frame::Ptr obtainFrame();
|
H264Frame::Ptr obtainFrame();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
H264Frame::Ptr _h264frame;
|
H264Frame::Ptr _h264frame;
|
||||||
string _sps;
|
string _sps;
|
||||||
|
@ -12,13 +12,6 @@
|
|||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unsigned forbidden_zero_bit :1;
|
|
||||||
unsigned nal_ref_idc :2;
|
|
||||||
unsigned type :5;
|
|
||||||
} NALU;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned S :1;
|
unsigned S :1;
|
||||||
unsigned E :1;
|
unsigned E :1;
|
||||||
@ -26,15 +19,6 @@ typedef struct {
|
|||||||
unsigned type :5;
|
unsigned type :5;
|
||||||
} FU;
|
} 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) {
|
static bool MakeFU(uint8_t in, FU &fu) {
|
||||||
fu.S = in >> 7;
|
fu.S = in >> 7;
|
||||||
fu.E = (in >> 6) & 0x01;
|
fu.E = (in >> 6) & 0x01;
|
||||||
@ -86,30 +70,28 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
28 FU-A Fragmentation unit 5.8
|
28 FU-A Fragmentation unit 5.8
|
||||||
29 FU-B Fragmentation unit 5.8
|
29 FU-B Fragmentation unit 5.8
|
||||||
30-31 undefined -
|
30-31 undefined -
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||||
int length = rtppack->size() - rtppack->offset;
|
int length = rtppack->size() - rtppack->offset;
|
||||||
NALU nal;
|
int nal_type = *frame & 0x1F;
|
||||||
MakeNalu(*frame, nal);
|
int nal_suffix = *frame & (~0x1F);
|
||||||
|
|
||||||
if (nal.type >= 0 && nal.type < 24) {
|
if (nal_type >= 0 && nal_type < 24) {
|
||||||
//a full frame
|
//a full frame
|
||||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
_h264frame->_buffer.append((char *)frame, length);
|
_h264frame->_buffer.append((char *) frame, length);
|
||||||
_h264frame->_pts = rtppack->timeStamp;
|
_h264frame->_pts = rtppack->timeStamp;
|
||||||
auto key = _h264frame->keyFrame();
|
auto key = _h264frame->keyFrame();
|
||||||
onGetH264(_h264frame);
|
onGetH264(_h264frame);
|
||||||
return (key); //i frame
|
return (key); //i frame
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (nal.type){
|
switch (nal_type){
|
||||||
case 24:{
|
case 24:{
|
||||||
// 24 STAP-A 单一时间的组合包
|
// 24 STAP-A 单一时间的组合包
|
||||||
bool haveIDR = false;
|
bool haveIDR = false;
|
||||||
auto ptr = frame + 1;
|
auto ptr = frame + 1;
|
||||||
while(true){
|
while (true) {
|
||||||
int off = ptr - frame;
|
int off = ptr - frame;
|
||||||
if (off >= length) {
|
if (off >= length) {
|
||||||
break;
|
break;
|
||||||
@ -121,14 +103,12 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
if (off + len > length) {
|
if (off + len > length) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(len >= 10){
|
if (len > 0) {
|
||||||
//过小的帧丢弃
|
//有有效数据
|
||||||
NALU nal;
|
|
||||||
MakeNalu(ptr[0], nal);
|
|
||||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
_h264frame->_buffer.append((char *)ptr, len);
|
_h264frame->_buffer.append((char *) ptr, len);
|
||||||
_h264frame->_pts = rtppack->timeStamp;
|
_h264frame->_pts = rtppack->timeStamp;
|
||||||
if(nal.type == H264Frame::NAL_IDR){
|
if ((ptr[0] & 0x1F) == H264Frame::NAL_IDR) {
|
||||||
haveIDR = true;
|
haveIDR = true;
|
||||||
}
|
}
|
||||||
onGetH264(_h264frame);
|
onGetH264(_h264frame);
|
||||||
@ -144,10 +124,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
MakeFU(frame[1], fu);
|
MakeFU(frame[1], fu);
|
||||||
if (fu.S) {
|
if (fu.S) {
|
||||||
//该帧的第一个rtp包 FU-A start
|
//该帧的第一个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.assign("\x0\x0\x0\x1", 4);
|
||||||
_h264frame->_buffer.push_back(tmp);
|
_h264frame->_buffer.push_back(nal_suffix | fu.type);
|
||||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||||
_h264frame->_pts = rtppack->timeStamp;
|
_h264frame->_pts = rtppack->timeStamp;
|
||||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||||
_lastSeq = rtppack->sequence;
|
_lastSeq = rtppack->sequence;
|
||||||
@ -163,20 +142,20 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
|
|
||||||
if (!fu.E) {
|
if (!fu.E) {
|
||||||
//该帧的中间rtp包 FU-A mid
|
//该帧的中间rtp包 FU-A mid
|
||||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||||
_lastSeq = rtppack->sequence;
|
_lastSeq = rtppack->sequence;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//该帧最后一个rtp包 FU-A end
|
//该帧最后一个rtp包 FU-A end
|
||||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||||
_h264frame->_pts = rtppack->timeStamp;
|
_h264frame->_pts = rtppack->timeStamp;
|
||||||
onGetH264(_h264frame);
|
onGetH264(_h264frame);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:{
|
default: {
|
||||||
// 29 FU-B 单NAL单元B模式
|
// 29 FU-B 单NAL单元B模式
|
||||||
// 25 STAP-B 单一时间的组合包
|
// 25 STAP-B 单一时间的组合包
|
||||||
// 26 MTAP16 多个时间的组合包
|
// 26 MTAP16 多个时间的组合包
|
||||||
@ -184,7 +163,7 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
// 0 udef
|
// 0 udef
|
||||||
// 30 udef
|
// 30 udef
|
||||||
// 31 udef
|
// 31 udef
|
||||||
WarnL << "不支持的rtp类型:" << (int)nal.type << " " << rtppack->sequence;
|
WarnL << "不支持的rtp类型:" << (int) nal_type << " " << rtppack->sequence;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,63 +194,62 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ui32Ssrc,
|
|||||||
|
|
||||||
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||||
GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS);
|
GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS);
|
||||||
auto pcData = frame->data() + frame->prefixSize();
|
auto ptr = frame->data() + frame->prefixSize();
|
||||||
auto uiStamp = frame->pts();
|
auto pts = frame->pts() % cycleMS;
|
||||||
auto iLen = frame->size() - frame->prefixSize();
|
auto len = frame->size() - frame->prefixSize();
|
||||||
//获取NALU的5bit 帧类型
|
auto nal_type = H264_TYPE(ptr[0]);
|
||||||
unsigned char naluType = H264_TYPE(pcData[0]);
|
auto max_rtp_size = _ui32MtuSize - 2;
|
||||||
|
|
||||||
uiStamp %= cycleMS;
|
|
||||||
int iSize = _ui32MtuSize - 2;
|
|
||||||
//超过MTU则按照FU-A模式打包
|
//超过MTU则按照FU-A模式打包
|
||||||
if (iLen > iSize) {
|
if (len > max_rtp_size) {
|
||||||
//最高位bit为forbidden_zero_bit,
|
//最高位bit为forbidden_zero_bit,
|
||||||
//后面2bit为nal_ref_idc(帧重要程度),00:可以丢,11:不能丢
|
//后面2bit为nal_ref_idc(帧重要程度),00:可以丢,11:不能丢
|
||||||
//末尾5bit为nalu type,固定为28(FU-A)
|
//末尾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;
|
unsigned char s_e_r_flags;
|
||||||
bool bFirst = true;
|
bool fu_a_start = true;
|
||||||
bool mark = false;
|
bool mark_bit = false;
|
||||||
int nOffset = 1;
|
int offset = 1;
|
||||||
while (!mark) {
|
while (!mark_bit) {
|
||||||
if (iLen <= nOffset + iSize) {
|
if (len <= offset + max_rtp_size) {
|
||||||
//已经拆分结束
|
//已经拆分结束
|
||||||
iSize = iLen - nOffset;
|
max_rtp_size = len - offset;
|
||||||
mark = true;
|
mark_bit = true;
|
||||||
//FU-A end
|
//FU-A end
|
||||||
s_e_r_flags = (1 << 6) | naluType;
|
s_e_r_flags = (1 << 6) | nal_type;
|
||||||
} else if (bFirst) {
|
} else if (fu_a_start) {
|
||||||
//FU-A start
|
//FU-A start
|
||||||
s_e_r_flags = (1 << 7) | naluType;
|
s_e_r_flags = (1 << 7) | nal_type;
|
||||||
} else {
|
} else {
|
||||||
//FU-A mid
|
//FU-A mid
|
||||||
s_e_r_flags = naluType;
|
s_e_r_flags = nal_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
//传入nullptr先不做payload的内存拷贝
|
//传入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 负载部分
|
//rtp payload 负载部分
|
||||||
uint8_t *payload = (uint8_t*)rtp->data() + rtp->offset;
|
uint8_t *payload = (uint8_t*)rtp->data() + rtp->offset;
|
||||||
//FU-A 第1个字节
|
//FU-A 第1个字节
|
||||||
payload[0] = f_nri_flags;
|
payload[0] = nal_fu_a;
|
||||||
//FU-A 第2个字节
|
//FU-A 第2个字节
|
||||||
payload[1] = s_e_r_flags;
|
payload[1] = s_e_r_flags;
|
||||||
//H264 数据
|
//H264 数据
|
||||||
memcpy(payload + 2, (unsigned char *) pcData + nOffset, iSize);
|
memcpy(payload + 2, (unsigned char *) ptr + offset, max_rtp_size);
|
||||||
//输入到rtp环形缓存
|
//输入到rtp环形缓存
|
||||||
RtpCodec::inputRtp(rtp,bFirst && naluType == H264Frame::NAL_IDR);
|
RtpCodec::inputRtp(rtp, fu_a_start && nal_type == H264Frame::NAL_IDR);
|
||||||
}
|
}
|
||||||
nOffset += iSize;
|
offset += max_rtp_size;
|
||||||
bFirst = false;
|
fu_a_start = false;
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
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),first_packet && nal_type == H264Frame::NAL_IDR);
|
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), gop_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
@ -78,7 +78,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override;
|
void inputFrame(const Frame::Ptr &frame) override;
|
||||||
private:
|
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{
|
}//namespace mediakit{
|
||||||
|
@ -61,7 +61,7 @@ public:
|
|||||||
} NaleType;
|
} NaleType;
|
||||||
|
|
||||||
H265Frame(){
|
H265Frame(){
|
||||||
_codecid = CodecH265;
|
_codec_id = CodecH265;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
@ -92,10 +92,7 @@ public:
|
|||||||
_dts = dts;
|
_dts = dts;
|
||||||
_pts = pts;
|
_pts = pts;
|
||||||
_prefix_size = prefix_size;
|
_prefix_size = prefix_size;
|
||||||
}
|
_codec_id = CodecH265;
|
||||||
|
|
||||||
CodecId getCodecId() const override {
|
|
||||||
return CodecH265;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
|
@ -27,10 +27,6 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
|
||||||
return decodeRtmp(rtmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ENABLE_MP4
|
#ifdef ENABLE_MP4
|
||||||
/**
|
/**
|
||||||
* 返回不带0x00 00 00 01头的sps
|
* 返回不带0x00 00 00 01头的sps
|
||||||
@ -43,61 +39,60 @@ static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
|
|||||||
if (!thiz.isCfgFrame()) {
|
if (!thiz.isCfgFrame()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (thiz.strBuf.size() < 6) {
|
if (thiz.buffer.size() < 6) {
|
||||||
WarnL << "bad H265 cfg!";
|
WarnL << "bad H265 cfg!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto extra = thiz.strBuf.data() + 5;
|
auto extra = thiz.buffer.data() + 5;
|
||||||
auto bytes = thiz.strBuf.size() - 5;
|
auto bytes = thiz.buffer.size() - 5;
|
||||||
|
|
||||||
struct mpeg4_hevc_t hevc = {0};
|
struct mpeg4_hevc_t hevc = {0};
|
||||||
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) {
|
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) {
|
||||||
uint8_t config[1024] = {0};
|
uint8_t *config = new uint8_t[bytes * 2];
|
||||||
int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config));
|
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
|
||||||
if (size > 4) {
|
if (size > 4) {
|
||||||
frame.assign((char *) config + 4, size - 4);
|
frame.assign((char *) config + 4, size - 4);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
delete [] config;
|
||||||
|
return size > 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool H265RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
if (pkt->isCfgFrame()) {
|
if (pkt->isCfgFrame()) {
|
||||||
#ifdef ENABLE_MP4
|
#ifdef ENABLE_MP4
|
||||||
string config;
|
string config;
|
||||||
if(getH265ConfigFrame(*pkt,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
|
#else
|
||||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pkt->strBuf.size() > 9) {
|
if (pkt->buffer.size() > 9) {
|
||||||
uint32_t iTotalLen = pkt->strBuf.size();
|
uint32_t iTotalLen = pkt->buffer.size();
|
||||||
uint32_t iOffset = 5;
|
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;
|
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){
|
while(iOffset + 4 < iTotalLen){
|
||||||
uint32_t iFrameLen;
|
uint32_t iFrameLen;
|
||||||
memcpy(&iFrameLen, pkt->strBuf.data() + iOffset, 4);
|
memcpy(&iFrameLen, pkt->buffer.data() + iOffset, 4);
|
||||||
iFrameLen = ntohl(iFrameLen);
|
iFrameLen = ntohl(iFrameLen);
|
||||||
iOffset += 4;
|
iOffset += 4;
|
||||||
if(iFrameLen + iOffset > iTotalLen){
|
if(iFrameLen + iOffset > iTotalLen){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
onGetH265(pkt->strBuf.data() + iOffset, iFrameLen, pkt->timeStamp , pts);
|
onGetH265(pkt->buffer.data() + iOffset, iFrameLen, pkt->time_stamp , pts);
|
||||||
iOffset += iFrameLen;
|
iOffset += iFrameLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pkt->isVideoKeyFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void H265RtmpDecoder::onGetH265(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
|
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) {
|
||||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
RtmpCodec::inputRtmp(_lastPacket);
|
||||||
_lastPacket = nullptr;
|
_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);
|
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||||
|
|
||||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||||
_lastPacket->strBuf.clear();
|
_lastPacket->buffer.clear();
|
||||||
_lastPacket->strBuf.push_back(flags);
|
_lastPacket->buffer.push_back(flags);
|
||||||
_lastPacket->strBuf.push_back(!is_config);
|
_lastPacket->buffer.push_back(!is_config);
|
||||||
auto cts = frame->pts() - frame->dts();
|
auto cts = frame->pts() - frame->dts();
|
||||||
cts = htonl(cts);
|
cts = htonl(cts);
|
||||||
_lastPacket->strBuf.append((char *)&cts + 1, 3);
|
_lastPacket->buffer.append((char *)&cts + 1, 3);
|
||||||
|
|
||||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
_lastPacket->chunk_id = CHUNK_VIDEO;
|
||||||
_lastPacket->streamId = STREAM_MEDIA;
|
_lastPacket->stream_index = STREAM_MEDIA;
|
||||||
_lastPacket->timeStamp = frame->dts();
|
_lastPacket->time_stamp = frame->dts();
|
||||||
_lastPacket->typeId = MSG_VIDEO;
|
_lastPacket->type_id = MSG_VIDEO;
|
||||||
|
|
||||||
}
|
}
|
||||||
auto size = htonl(iLen);
|
auto size = htonl(iLen);
|
||||||
_lastPacket->strBuf.append((char *) &size, 4);
|
_lastPacket->buffer.append((char *) &size, 4);
|
||||||
_lastPacket->strBuf.append(pcData, iLen);
|
_lastPacket->buffer.append(pcData, iLen);
|
||||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void H265RtmpEncoder::makeVideoConfigPkt() {
|
void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||||
@ -214,13 +209,13 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
|||||||
bool is_config = true;
|
bool is_config = true;
|
||||||
|
|
||||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||||
rtmpPkt->strBuf.clear();
|
rtmpPkt->buffer.clear();
|
||||||
|
|
||||||
//header
|
//header
|
||||||
rtmpPkt->strBuf.push_back(flags);
|
rtmpPkt->buffer.push_back(flags);
|
||||||
rtmpPkt->strBuf.push_back(!is_config);
|
rtmpPkt->buffer.push_back(!is_config);
|
||||||
//cts
|
//cts
|
||||||
rtmpPkt->strBuf.append("\x0\x0\x0", 3);
|
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||||
|
|
||||||
struct mpeg4_hevc_t hevc = {0};
|
struct mpeg4_hevc_t hevc = {0};
|
||||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
|
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
|
||||||
@ -235,14 +230,14 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//HEVCDecoderConfigurationRecord
|
//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->body_size = rtmpPkt->buffer.size();
|
||||||
rtmpPkt->chunkId = CHUNK_VIDEO;
|
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||||
rtmpPkt->streamId = STREAM_MEDIA;
|
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->timeStamp = 0;
|
rtmpPkt->time_stamp = 0;
|
||||||
rtmpPkt->typeId = MSG_VIDEO;
|
rtmpPkt->type_id = MSG_VIDEO;
|
||||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
RtmpCodec::inputRtmp(rtmpPkt);
|
||||||
#else
|
#else
|
||||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||||
#endif
|
#endif
|
||||||
|
@ -32,17 +32,17 @@ public:
|
|||||||
/**
|
/**
|
||||||
* 输入265 Rtmp包
|
* 输入265 Rtmp包
|
||||||
* @param rtmp 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{
|
CodecId getCodecId() const override{
|
||||||
return CodecH265;
|
return CodecH265;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool decodeRtmp(const RtmpPacket::Ptr &Rtmp);
|
|
||||||
void onGetH265(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
void onGetH265(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
||||||
H265Frame::Ptr obtainFrame();
|
H265Frame::Ptr obtainFrame();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
H265Frame::Ptr _h265frame;
|
H265Frame::Ptr _h265frame;
|
||||||
};
|
};
|
||||||
|
@ -200,7 +200,7 @@ void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
|||||||
bFirst = false;
|
bFirst = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
makeH265Rtp(naluType,pcData, iLen, true, true, uiStamp);
|
makeH265Rtp(naluType,pcData, iLen, false, true, uiStamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,52 +16,13 @@
|
|||||||
|
|
||||||
namespace mediakit{
|
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帧音频通道
|
* Opus帧音频通道
|
||||||
*/
|
*/
|
||||||
class OpusTrack : public AudioTrackImp{
|
class OpusTrack : public AudioTrackImp{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<OpusTrack> Ptr;
|
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:
|
private:
|
||||||
//克隆该Track
|
//克隆该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){
|
HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller){
|
||||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
_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() {}
|
HlsPlayer::~HlsPlayer() {}
|
||||||
@ -63,6 +63,15 @@ void HlsPlayer::playNextTs(bool force){
|
|||||||
std::shared_ptr<Ticker> ticker(new Ticker);
|
std::shared_ptr<Ticker> ticker(new Ticker);
|
||||||
|
|
||||||
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller(), false);
|
_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) {
|
_http_ts_player->setOnDisconnect([weakSelf, ticker, ts_duration](const SockException &err) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (!strongSelf) {
|
if (!strongSelf) {
|
||||||
@ -84,6 +93,7 @@ void HlsPlayer::playNextTs(bool force){
|
|||||||
}, strongSelf->getPoller()));
|
}, strongSelf->getPoller()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_http_ts_player->setOnPacket([weakSelf](const char *data, uint64_t len) {
|
_http_ts_player->setOnPacket([weakSelf](const char *data, uint64_t len) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (!strongSelf) {
|
if (!strongSelf) {
|
||||||
@ -94,9 +104,10 @@ void HlsPlayer::playNextTs(bool force){
|
|||||||
});
|
});
|
||||||
|
|
||||||
_http_ts_player->setMethod("GET");
|
_http_ts_player->setMethod("GET");
|
||||||
if(!(*this)[kNetAdapter].empty()) {
|
if (!(*this)[kNetAdapter].empty()) {
|
||||||
_http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]);
|
_http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_http_ts_player->sendRequest(_ts_list.front().url, 2 * _ts_list.front().duration);
|
_http_ts_player->sendRequest(_ts_list.front().url, 2 * _ts_list.front().duration);
|
||||||
_ts_list.pop_front();
|
_ts_list.pop_front();
|
||||||
}
|
}
|
||||||
@ -254,11 +265,15 @@ void HlsPlayerImp::onAllTrackReady() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
||||||
if(ex){
|
if (ex) {
|
||||||
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
|
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
|
||||||
}else{
|
} else {
|
||||||
|
_frame_cache.clear();
|
||||||
|
_stamp[TrackAudio].setRelativeStamp(0);
|
||||||
|
_stamp[TrackVideo].setRelativeStamp(0);
|
||||||
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
|
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
|
||||||
_ticker.resetTime();
|
setPlayPosition(0);
|
||||||
|
|
||||||
weak_ptr<HlsPlayerImp> weakSelf = dynamic_pointer_cast<HlsPlayerImp>(shared_from_this());
|
weak_ptr<HlsPlayerImp> weakSelf = dynamic_pointer_cast<HlsPlayerImp>(shared_from_this());
|
||||||
//每50毫秒执行一次
|
//每50毫秒执行一次
|
||||||
_timer = std::make_shared<Timer>(0.05, [weakSelf]() {
|
_timer = std::make_shared<Timer>(0.05, [weakSelf]() {
|
||||||
@ -288,25 +303,47 @@ void HlsPlayerImp::inputFrame(const Frame::Ptr &frame) {
|
|||||||
//根据时间戳缓存frame
|
//根据时间戳缓存frame
|
||||||
_frame_cache.emplace(dts, Frame::getCacheAbleFrame(frame));
|
_frame_cache.emplace(dts, Frame::getCacheAbleFrame(frame));
|
||||||
|
|
||||||
while (!_frame_cache.empty()) {
|
if (getBufferMS() > 30 * 1000) {
|
||||||
if (_frame_cache.rbegin()->first - _frame_cache.begin()->first > 30 * 1000) {
|
//缓存超过30秒,强制消费至15秒(减少延时或内存占用)
|
||||||
//缓存超过30秒,强制消费掉
|
while (getBufferMS() > 15 * 1000) {
|
||||||
MediaSink::inputFrame(_frame_cache.begin()->second);
|
MediaSink::inputFrame(_frame_cache.begin()->second);
|
||||||
_frame_cache.erase(_frame_cache.begin());
|
_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() {
|
void HlsPlayerImp::onTick() {
|
||||||
auto it = _frame_cache.begin();
|
auto it = _frame_cache.begin();
|
||||||
while (it != _frame_cache.end()) {
|
while (it != _frame_cache.end()) {
|
||||||
if (it->first > _ticker.elapsedTime()) {
|
if (it->first > getPlayPosition()) {
|
||||||
//这些帧还未到时间播放
|
//这些帧还未到时间播放
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getBufferMS() < 3 * 1000) {
|
||||||
|
//缓存小于3秒,那么降低定时器消费速度(让剩余的数据在3秒后消费完毕)
|
||||||
|
//目的是为了防止定时器长时间干等后,数据瞬间消费完毕
|
||||||
|
setPlayPosition(_frame_cache.begin()->first);
|
||||||
|
}
|
||||||
|
|
||||||
//消费掉已经到期的帧
|
//消费掉已经到期的帧
|
||||||
MediaSink::inputFrame(it->second);
|
MediaSink::inputFrame(it->second);
|
||||||
it = _frame_cache.erase(it);
|
it = _frame_cache.erase(it);
|
||||||
|
@ -138,13 +138,19 @@ private:
|
|||||||
void inputFrame(const Frame::Ptr &frame) override;
|
void inputFrame(const Frame::Ptr &frame) override;
|
||||||
void onShutdown(const SockException &ex) override;
|
void onShutdown(const SockException &ex) override;
|
||||||
void onTick();
|
void onTick();
|
||||||
|
|
||||||
|
int64_t getPlayPosition();
|
||||||
|
void setPlayPosition(int64_t pos);
|
||||||
|
int64_t getBufferMS();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TSSegment::onSegment _on_ts;
|
int64_t _ticker_offset = 0;
|
||||||
DecoderImp::Ptr _decoder;
|
|
||||||
multimap<int64_t, Frame::Ptr> _frame_cache;
|
|
||||||
Timer::Ptr _timer;
|
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
Stamp _stamp[2];
|
Stamp _stamp[2];
|
||||||
|
Timer::Ptr _timer;
|
||||||
|
DecoderImp::Ptr _decoder;
|
||||||
|
TSSegment::onSegment _on_ts;
|
||||||
|
multimap<int64_t, Frame::Ptr> _frame_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
@ -135,7 +135,7 @@ Buffer::Ptr HttpFileBody::readData(uint32_t size) {
|
|||||||
//读到数据了
|
//读到数据了
|
||||||
ret->setSize(iRead);
|
ret->setSize(iRead);
|
||||||
_offset += iRead;
|
_offset += iRead;
|
||||||
return std::move(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
//读取文件异常,文件真实长度小于声明长度
|
//读取文件异常,文件真实长度小于声明长度
|
||||||
_offset = _max_size;
|
_offset = _max_size;
|
||||||
@ -146,7 +146,7 @@ Buffer::Ptr HttpFileBody::readData(uint32_t size) {
|
|||||||
//mmap模式
|
//mmap模式
|
||||||
auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
|
auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
|
||||||
_offset += size;
|
_offset += size;
|
||||||
return std::move(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
@ -100,7 +100,7 @@ void HttpClient::onConnect(const SockException &ex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//先假设http客户端只会接收一点点数据(只接受http头,节省内存)
|
//先假设http客户端只会接收一点点数据(只接受http头,节省内存)
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
getSock()->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||||
|
|
||||||
_totalBodySize = 0;
|
_totalBodySize = 0;
|
||||||
_recvedBodySize = 0;
|
_recvedBodySize = 0;
|
||||||
@ -157,7 +157,7 @@ int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
|
|||||||
|
|
||||||
if(_parser["Transfer-Encoding"] == "chunked"){
|
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是不限制长度的
|
//如果Transfer-Encoding字段等于chunked,则认为后续的content是不限制长度的
|
||||||
_totalBodySize = -1;
|
_totalBodySize = -1;
|
||||||
@ -185,9 +185,9 @@ int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
|
|||||||
_recvedBodySize = 0;
|
_recvedBodySize = 0;
|
||||||
if(_totalBodySize > 0){
|
if(_totalBodySize > 0){
|
||||||
//根据_totalBodySize设置接收缓存大小
|
//根据_totalBodySize设置接收缓存大小
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(_totalBodySize + 1,256 * 1024)));
|
getSock()->setReadBuffer(std::make_shared<BufferRaw>(MIN(_totalBodySize + 1,256 * 1024)));
|
||||||
}else{
|
}else{
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -27,7 +27,7 @@ static int kHlsCookieSecond = 60;
|
|||||||
static const string kCookieName = "ZL_COOKIE";
|
static const string kCookieName = "ZL_COOKIE";
|
||||||
static const string kHlsSuffix = "/hls.m3u8";
|
static const string kHlsSuffix = "/hls.m3u8";
|
||||||
|
|
||||||
class HttpCookieAttachment{
|
class HttpCookieAttachment {
|
||||||
public:
|
public:
|
||||||
HttpCookieAttachment() {};
|
HttpCookieAttachment() {};
|
||||||
~HttpCookieAttachment() {};
|
~HttpCookieAttachment() {};
|
||||||
@ -160,7 +160,7 @@ const string &HttpFileManager::getContentType(const char *name) {
|
|||||||
dot = strrchr(name, '.');
|
dot = strrchr(name, '.');
|
||||||
static StrCaseMap mapType;
|
static StrCaseMap mapType;
|
||||||
static onceToken token([&]() {
|
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]);
|
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;
|
set<string> setFile;
|
||||||
while ((pDirent = readdir(pDir)) != NULL) {
|
while ((pDirent = readdir(pDir)) != NULL) {
|
||||||
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"};
|
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
|
||||||
if(indexSet.find(pDirent->d_name) != indexSet.end()){
|
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
|
||||||
string ret = pDirent->d_name;
|
string ret = pDirent->d_name;
|
||||||
closedir(pDir);
|
closedir(pDir);
|
||||||
return ret;
|
return ret;
|
||||||
@ -196,16 +196,16 @@ static string searchIndexFile(const string &dir){
|
|||||||
|
|
||||||
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
|
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
|
||||||
GET_CONFIG(bool, dirMenu, Http::kDirMenu);
|
GET_CONFIG(bool, dirMenu, Http::kDirMenu);
|
||||||
if(!dirMenu){
|
if (!dirMenu) {
|
||||||
//不允许浏览文件夹
|
//不允许浏览文件夹
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
string strPathPrefix(strFullPath);
|
string strPathPrefix(strFullPath);
|
||||||
string last_dir_name;
|
string last_dir_name;
|
||||||
if(strPathPrefix.back() == '/'){
|
if (strPathPrefix.back() == '/') {
|
||||||
strPathPrefix.pop_back();
|
strPathPrefix.pop_back();
|
||||||
}else{
|
} else {
|
||||||
last_dir_name = split(strPathPrefix,"/").back();
|
last_dir_name = split(strPathPrefix, "/").back();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File::is_dir(strPathPrefix.data())) {
|
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)) {
|
if (File::is_special_dir(pDirent->d_name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(pDirent->d_name[0] == '.'){
|
if (pDirent->d_name[0] == '.') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
setFile.emplace(pDirent->d_name);
|
setFile.emplace(pDirent->d_name);
|
||||||
}
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(auto &strFile :setFile ){
|
for (auto &strFile :setFile) {
|
||||||
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
||||||
bool isDir = File::is_dir(strAbsolutePath.data());
|
bool isDir = File::is_dir(strAbsolutePath.data());
|
||||||
ss << "<li><span>" << i++ << "</span>\t";
|
ss << "<li><span>" << i++ << "</span>\t";
|
||||||
ss << "<a href=\"";
|
ss << "<a href=\"";
|
||||||
if(!last_dir_name.empty()){
|
if (!last_dir_name.empty()) {
|
||||||
ss << last_dir_name << "/" << strFile;
|
ss << last_dir_name << "/" << strFile;
|
||||||
}else{
|
} else {
|
||||||
ss << strFile;
|
ss << strFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isDir){
|
if (isDir) {
|
||||||
ss << "/";
|
ss << "/";
|
||||||
}
|
}
|
||||||
ss << "\">";
|
ss << "\">";
|
||||||
@ -307,11 +307,16 @@ static bool end_of(const string &str, const string &substr){
|
|||||||
//拦截hls的播放请求
|
//拦截hls的播放请求
|
||||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
||||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||||
Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){
|
Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
|
||||||
//cookie有效期为kHlsCookieSecond
|
//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{
|
class SockInfoImp : public SockInfo{
|
||||||
@ -320,23 +325,23 @@ public:
|
|||||||
SockInfoImp() = default;
|
SockInfoImp() = default;
|
||||||
~SockInfoImp() override = default;
|
~SockInfoImp() override = default;
|
||||||
|
|
||||||
string get_local_ip() override{
|
string get_local_ip() override {
|
||||||
return _local_ip;
|
return _local_ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t get_local_port() override{
|
uint16_t get_local_port() override {
|
||||||
return _local_port;
|
return _local_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
string get_peer_ip() override{
|
string get_peer_ip() override {
|
||||||
return _peer_ip;
|
return _peer_ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t get_peer_port() override{
|
uint16_t get_peer_port() override {
|
||||||
return _peer_port;
|
return _peer_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
string getIdentifier() const override{
|
string getIdentifier() const override {
|
||||||
return _identifier;
|
return _identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +384,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
|||||||
//上次cookie是限定本目录
|
//上次cookie是限定本目录
|
||||||
if (attachment._err_msg.empty()) {
|
if (attachment._err_msg.empty()) {
|
||||||
//上次鉴权成功
|
//上次鉴权成功
|
||||||
if(attachment._is_hls){
|
if (attachment._is_hls) {
|
||||||
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
||||||
cookie->updateTime();
|
cookie->updateTime();
|
||||||
cookie_from_header = false;
|
cookie_from_header = false;
|
||||||
@ -429,7 +434,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
|||||||
attachment._err_msg = errMsg;
|
attachment._err_msg = errMsg;
|
||||||
//记录访问的是否为hls
|
//记录访问的是否为hls
|
||||||
attachment._is_hls = is_hls;
|
attachment._is_hls = is_hls;
|
||||||
if(is_hls){
|
if (is_hls) {
|
||||||
//hls相关信息
|
//hls相关信息
|
||||||
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
|
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
|
||||||
//hls未查找MediaSource
|
//hls未查找MediaSource
|
||||||
@ -437,13 +442,14 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
|||||||
}
|
}
|
||||||
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
|
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
|
||||||
callback(errMsg, cookie);
|
callback(errMsg, cookie);
|
||||||
}else{
|
} else {
|
||||||
callback(errMsg, nullptr);
|
callback(errMsg, nullptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (is_hls && emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender)) {
|
if (is_hls) {
|
||||||
//是hls的播放鉴权,拦截之
|
//是hls的播放鉴权,拦截之
|
||||||
|
emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,15 +465,15 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
|||||||
* 发送404 Not Found
|
* 发送404 Not Found
|
||||||
*/
|
*/
|
||||||
static void sendNotFound(const HttpFileManager::invoker &cb) {
|
static void sendNotFound(const HttpFileManager::invoker &cb) {
|
||||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||||
cb("404 Not Found","text/html",StrCaseMap(),std::make_shared<HttpStringBody>(notFound));
|
cb("404 Not Found", "text/html", StrCaseMap(), std::make_shared<HttpStringBody>(notFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼接文件路径
|
* 拼接文件路径
|
||||||
*/
|
*/
|
||||||
static string pathCat(const string &a, const string &b){
|
static string pathCat(const string &a, const string &b){
|
||||||
if(a.back() == '/'){
|
if (a.back() == '/') {
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
return a + '/' + b;
|
return a + '/' + b;
|
||||||
@ -490,7 +496,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_hls){
|
if (is_hls) {
|
||||||
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||||
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
||||||
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
|
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) {
|
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();
|
auto strongSession = weakSession.lock();
|
||||||
if(!strongSession){
|
if (!strongSession) {
|
||||||
//http客户端已经断开,不需要回复
|
//http客户端已经断开,不需要回复
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -508,6 +514,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
|||||||
//文件鉴权失败
|
//文件鉴权失败
|
||||||
StrCaseMap headerOut;
|
StrCaseMap headerOut;
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
|
auto lck = cookie->getLock();
|
||||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||||
}
|
}
|
||||||
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
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) {
|
auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
|
||||||
StrCaseMap httpHeader;
|
StrCaseMap httpHeader;
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
|
auto lck = cookie->getLock();
|
||||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||||
}
|
}
|
||||||
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||||
if (cookie && file_exist) {
|
if (cookie && file_exist) {
|
||||||
cookie->getLock();
|
auto lck = cookie->getLock();
|
||||||
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
|
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
|
||||||
if (is_hls) {
|
if (is_hls) {
|
||||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
|
(*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) {
|
if (!is_hls) {
|
||||||
//不是hls,直接回复文件或404
|
//不是hls,直接回复文件或404
|
||||||
response_file(cookie, cb, strFile, parser);
|
response_file(cookie, cb, strFile, parser);
|
||||||
} else {
|
return;
|
||||||
//是hls直播,判断是否存在
|
}
|
||||||
bool have_find_media_src = false;
|
|
||||||
if(cookie){
|
//是hls直播,判断HLS直播流是否已经注册
|
||||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
bool have_find_media_src = false;
|
||||||
if(!have_find_media_src){
|
if (cookie) {
|
||||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
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);
|
response_file(cookie, cb, strFile, parser);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//hls文件不存在,我们等待其生成并延后回复
|
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
|
||||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
if (!hls) {
|
||||||
//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);
|
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);
|
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||||
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath);
|
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath);
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
|
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//////////////////////////////////////
|
////////////////////////////////////HttpResponseInvokerImp//////////////////////////////////////
|
||||||
|
|
||||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
|
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
|
||||||
if(_lambad){
|
if (_lambad) {
|
||||||
_lambad(codeOut,headerOut,body);
|
_lambad(codeOut, headerOut, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{
|
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){
|
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
|
||||||
@ -629,23 +658,23 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt
|
|||||||
}
|
}
|
||||||
|
|
||||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
|
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
|
||||||
if(!lambda){
|
if (!lambda) {
|
||||||
_lambad = nullptr;
|
_lambad = nullptr;
|
||||||
return;
|
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;
|
string str;
|
||||||
if(body && body->remainSize()){
|
if (body && body->remainSize()) {
|
||||||
str = body->readData(body->remainSize())->toString();
|
str = body->readData(body->remainSize())->toString();
|
||||||
}
|
}
|
||||||
lambda(codeOut,headerOut,str);
|
lambda(codeOut, headerOut, str);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||||
const StrCaseMap &responseHeader,
|
const StrCaseMap &responseHeader,
|
||||||
const string &filePath) const {
|
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) {
|
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||||
if (fp) {
|
if (fp) {
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
@ -654,8 +683,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
|||||||
|
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
//打开文件失败
|
//打开文件失败
|
||||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||||
|
|
||||||
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
||||||
httpHeader["Content-Type"] = strContentType;
|
httpHeader["Content-Type"] = strContentType;
|
||||||
@ -665,14 +694,14 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
|||||||
|
|
||||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||||
int64_t iRangeStart = 0;
|
int64_t iRangeStart = 0;
|
||||||
int64_t iRangeEnd = 0 ;
|
int64_t iRangeEnd = 0;
|
||||||
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
|
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
|
||||||
|
|
||||||
const char *pcHttpResult = NULL;
|
const char *pcHttpResult = NULL;
|
||||||
if (strRange.size() == 0) {
|
if (strRange.size() == 0) {
|
||||||
//全部下载
|
//全部下载
|
||||||
pcHttpResult = "200 OK";
|
pcHttpResult = "200 OK";
|
||||||
iRangeEnd = fileSize - 1;
|
iRangeEnd = fileSize - 1;
|
||||||
} else {
|
} else {
|
||||||
//分节下载
|
//分节下载
|
||||||
pcHttpResult = "206 Partial Content";
|
pcHttpResult = "206 Partial Content";
|
||||||
|
@ -8,15 +8,9 @@
|
|||||||
* may be found in the AUTHORS file in the root of the source tree.
|
* 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 <stdio.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
#include "strCoding.h"
|
#include "strCoding.h"
|
||||||
#include "HttpSession.h"
|
#include "HttpSession.h"
|
||||||
@ -96,10 +90,10 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onError(const SockException& err) {
|
void HttpSession::onError(const SockException& err) {
|
||||||
if(_is_flv_stream){
|
if(_is_live_stream){
|
||||||
uint64_t duration = _ticker.createdTime()/1000;
|
uint64_t duration = _ticker.createdTime()/1000;
|
||||||
//flv播放器
|
//flv/ts播放器
|
||||||
WarnP(this) << "FLV播放器("
|
WarnP(this) << "FLV/TS/FMP4播放器("
|
||||||
<< _mediaInfo._vhost << "/"
|
<< _mediaInfo._vhost << "/"
|
||||||
<< _mediaInfo._app << "/"
|
<< _mediaInfo._app << "/"
|
||||||
<< _mediaInfo._streamid
|
<< _mediaInfo._streamid
|
||||||
@ -107,8 +101,8 @@ void HttpSession::onError(const SockException& err) {
|
|||||||
<< ",耗时(s):" << duration;
|
<< ",耗时(s):" << duration;
|
||||||
|
|
||||||
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
|
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
|
||||||
if(_ui64TotalBytes > iFlowThreshold * 1024){
|
if(_total_bytes_usage > iFlowThreshold * 1024){
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration , true, static_cast<SockInfo &>(*this));
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration , true, static_cast<SockInfo &>(*this));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -132,7 +126,7 @@ void HttpSession::onManager() {
|
|||||||
|
|
||||||
bool HttpSession::checkWebSocket(){
|
bool HttpSession::checkWebSocket(){
|
||||||
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
||||||
if(Sec_WebSocket_Key.empty()){
|
if (Sec_WebSocket_Key.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
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["Upgrade"] = "websocket";
|
||||||
headerOut["Connection"] = "Upgrade";
|
headerOut["Connection"] = "Upgrade";
|
||||||
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
|
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"];
|
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto res_cb = [this,headerOut](){
|
auto res_cb = [this, headerOut]() {
|
||||||
_flv_over_websocket = true;
|
_live_over_websocket = true;
|
||||||
sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr, true);
|
sendResponse("101 Switching Protocols", false, nullptr, headerOut, nullptr, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
//判断是否为websocket-flv
|
//判断是否为websocket-flv
|
||||||
if(checkLiveFlvStream(res_cb)){
|
if (checkLiveStreamFlv(res_cb)) {
|
||||||
//这里是websocket-flv直播请求
|
//这里是websocket-flv直播请求
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接
|
//判断是否为websocket-ts
|
||||||
if(!onWebSocketConnect(_parser)){
|
if (checkLiveStreamTS(res_cb)) {
|
||||||
sendResponse("501 Not Implemented",true, nullptr, headerOut);
|
//这里是websocket-ts直播请求
|
||||||
return true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb){
|
||||||
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
auto pos = strcasestr(_parser.Url().data(), url_suffix.data());
|
||||||
bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
if (!pos || pos + url_suffix.size() != 1 + &_parser.Url().back()) {
|
||||||
auto pos = strrchr(_parser.Url().data(),'.');
|
//未找到后缀
|
||||||
if(!pos){
|
|
||||||
//未找到".flv"后缀
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(strcasecmp(pos,".flv") != 0){
|
|
||||||
//未找到".flv"后缀
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//这是个.flv的流
|
//这是个符合后缀的直播的流
|
||||||
_mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
|
_mediaInfo.parse(schema + "://" + _parser["Host"] + _parser.FullUrl());
|
||||||
if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
|
if (_mediaInfo._app.empty() || _mediaInfo._streamid.size() < url_suffix.size() + 1) {
|
||||||
//url不合法
|
//url不合法
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
//去除后缀
|
||||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
||||||
|
//流id去除后缀
|
||||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
_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 onRes = [cb, weak_self, close_flag](const string &err) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strongSelf) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
//本对象已经销毁
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//异步查找rtmp流
|
//异步查找直播流
|
||||||
MediaSource::findAsync(strongSelf->_mediaInfo, strongSelf, [weakSelf, bClose, cb](const MediaSource::Ptr &src) {
|
MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strongSelf) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
//本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
if (!src) {
|
||||||
if (!rtmp_src) {
|
|
||||||
//未找到该流
|
//未找到该流
|
||||||
strongSelf->sendNotFound(bClose);
|
strong_self->sendNotFound(close_flag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
strong_self->_is_live_stream = true;
|
||||||
if (!cb) {
|
//触发回调
|
||||||
//找到rtmp源,发送http头,负载后续发送
|
cb(src);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Broadcast::AuthInvoker invoker = [weakSelf, onRes](const string &err) {
|
Broadcast::AuthInvoker invoker = [weak_self, onRes](const string &err) {
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weak_self.lock();
|
||||||
if (!strongSelf) {
|
if (!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -250,34 +240,142 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
|||||||
return true;
|
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) {
|
void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||||
Handle_Req_GET_l(content_len, true);
|
Handle_Req_GET_l(content_len, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
|
void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
|
||||||
//先看看是否为WebSocket请求
|
//先看看是否为WebSocket请求
|
||||||
if(checkWebSocket()){
|
if (checkWebSocket()) {
|
||||||
content_len = -1;
|
content_len = -1;
|
||||||
_contentCallBack = [this](const char *data,uint64_t len){
|
_contentCallBack = [this](const char *data, uint64_t len) {
|
||||||
WebSocketSplitter::decode((uint8_t *)data,len);
|
WebSocketSplitter::decode((uint8_t *) data, len);
|
||||||
//_contentCallBack是可持续的,后面还要处理后续数据
|
//_contentCallBack是可持续的,后面还要处理后续数据
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(emitHttpEvent(false)){
|
if (emitHttpEvent(false)) {
|
||||||
//拦截http api事件
|
//拦截http api事件
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(checkLiveFlvStream()){
|
if (checkLiveStreamFlv()) {
|
||||||
//拦截http-flv播放器
|
//拦截http-flv播放器
|
||||||
return;
|
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());
|
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,
|
HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type,
|
||||||
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
||||||
@ -389,7 +487,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
|||||||
const char *pcContentType,
|
const char *pcContentType,
|
||||||
const HttpSession::KeyValue &header,
|
const HttpSession::KeyValue &header,
|
||||||
const HttpBody::Ptr &body,
|
const HttpBody::Ptr &body,
|
||||||
bool is_http_flv ){
|
bool no_content_length ){
|
||||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||||
|
|
||||||
@ -400,7 +498,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
|||||||
size = body->remainSize();
|
size = body->remainSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_http_flv){
|
if(no_content_length){
|
||||||
//http-flv直播是Keep-Alive类型
|
//http-flv直播是Keep-Alive类型
|
||||||
bClose = false;
|
bClose = false;
|
||||||
}else if(size >= INT64_MAX){
|
}else if(size >= INT64_MAX){
|
||||||
@ -425,7 +523,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
|||||||
headerOut.emplace(kAccessControlAllowCredentials, "true");
|
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
|
//文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||||
headerOut[kContentLength] = to_string(size);
|
headerOut[kContentLength] = to_string(size);
|
||||||
}
|
}
|
||||||
@ -475,7 +573,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
|||||||
|
|
||||||
//发送http body
|
//发送http body
|
||||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(),body,bClose);
|
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(),body,bClose);
|
||||||
_sock->setOnFlush([data](){
|
getSock()->setOnFlush([data](){
|
||||||
return AsyncSender::onSocketFlushed(data);
|
return AsyncSender::onSocketFlushed(data);
|
||||||
});
|
});
|
||||||
AsyncSender::onSocketFlushed(data);
|
AsyncSender::onSocketFlushed(data);
|
||||||
@ -542,10 +640,10 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
|||||||
|
|
||||||
//根据Content-Length设置接收缓存大小
|
//根据Content-Length设置接收缓存大小
|
||||||
if(totalContentLen > 0){
|
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{
|
}else{
|
||||||
//不定长度的Content-Length
|
//不定长度的Content-Length
|
||||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
||||||
@ -609,7 +707,7 @@ void HttpSession::setSocketFlags(){
|
|||||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(mergeWriteMS > 0) {
|
if(mergeWriteMS > 0) {
|
||||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||||
}
|
}
|
||||||
@ -622,29 +720,44 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ticker.resetTime();
|
_ticker.resetTime();
|
||||||
if(!_flv_over_websocket){
|
if (!_live_over_websocket) {
|
||||||
_ui64TotalBytes += buffer->size();
|
_total_bytes_usage += buffer->size();
|
||||||
send(buffer);
|
send(buffer);
|
||||||
}else{
|
} else {
|
||||||
WebSocketHeader header;
|
WebSocketHeader header;
|
||||||
header._fin = true;
|
header._fin = true;
|
||||||
header._reserved = 0;
|
header._reserved = 0;
|
||||||
header._opcode = WebSocketHeader::BINARY;
|
header._opcode = WebSocketHeader::BINARY;
|
||||||
header._mask_flag = false;
|
header._mask_flag = false;
|
||||||
WebSocketSplitter::encode(header,buffer);
|
WebSocketSplitter::encode(header, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(flush){
|
if (flush) {
|
||||||
//本次刷新缓存后,下次不用刷新缓存
|
//本次刷新缓存后,下次不用刷新缓存
|
||||||
HttpSession::setSendFlushFlag(false);
|
HttpSession::setSendFlushFlag(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
||||||
_ui64TotalBytes += buffer->size();
|
_total_bytes_usage += buffer->size();
|
||||||
send(buffer);
|
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() {
|
void HttpSession::onDetach() {
|
||||||
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#include "WebSocketSplitter.h"
|
#include "WebSocketSplitter.h"
|
||||||
#include "HttpCookieManager.h"
|
#include "HttpCookieManager.h"
|
||||||
#include "HttpFileManager.h"
|
#include "HttpFileManager.h"
|
||||||
|
#include "TS/TSMediaSource.h"
|
||||||
|
#include "FMP4/FMP4MediaSource.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
@ -47,6 +49,7 @@ public:
|
|||||||
void onError(const SockException &err) override;
|
void onError(const SockException &err) override;
|
||||||
void onManager() override;
|
void onManager() override;
|
||||||
static string urlDecode(const string &str);
|
static string urlDecode(const string &str);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//FlvMuxer override
|
//FlvMuxer override
|
||||||
void onWrite(const Buffer::Ptr &data, bool flush) override ;
|
void onWrite(const Buffer::Ptr &data, bool flush) override ;
|
||||||
@ -90,35 +93,49 @@ protected:
|
|||||||
* @param buffer websocket协议数据
|
* @param buffer websocket协议数据
|
||||||
*/
|
*/
|
||||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收到完整的一个webSocket数据包后回调
|
||||||
|
* @param header 数据包包头
|
||||||
|
*/
|
||||||
|
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Handle_Req_GET(int64_t &content_len);
|
void Handle_Req_GET(int64_t &content_len);
|
||||||
void Handle_Req_GET_l(int64_t &content_len, bool sendBody);
|
void Handle_Req_GET_l(int64_t &content_len, bool sendBody);
|
||||||
void Handle_Req_POST(int64_t &content_len);
|
void Handle_Req_POST(int64_t &content_len);
|
||||||
void Handle_Req_HEAD(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 checkWebSocket();
|
||||||
bool emitHttpEvent(bool doInvoke);
|
bool emitHttpEvent(bool doInvoke);
|
||||||
void urlDecode(Parser &parser);
|
void urlDecode(Parser &parser);
|
||||||
void sendNotFound(bool bClose);
|
void sendNotFound(bool bClose);
|
||||||
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
||||||
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
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标志
|
//设置socket标志
|
||||||
void setSocketFlags();
|
void setSocketFlags();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool _is_live_stream = false;
|
||||||
|
bool _live_over_websocket = false;
|
||||||
|
//消耗的总流量
|
||||||
|
uint64_t _total_bytes_usage = 0;
|
||||||
string _origin;
|
string _origin;
|
||||||
Parser _parser;
|
Parser _parser;
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
//消耗的总流量
|
|
||||||
uint64_t _ui64TotalBytes = 0;
|
|
||||||
//flv over http
|
|
||||||
MediaInfo _mediaInfo;
|
MediaInfo _mediaInfo;
|
||||||
|
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
||||||
|
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
||||||
//处理content数据的callback
|
//处理content数据的callback
|
||||||
function<bool (const char *data,uint64_t len) > _contentCallBack;
|
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 {
|
namespace mediakit {
|
||||||
|
|
||||||
HttpTSPlayer::HttpTSPlayer(const EventPoller::Ptr &poller, bool split_ts){
|
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;
|
_split_ts = split_ts;
|
||||||
|
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||||
|
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpTSPlayer::~HttpTSPlayer() {}
|
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));
|
shutdown(SockException(Err_other, StrPrinter << "bad http status code:" + status));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto contet_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"];
|
auto content_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"];
|
||||||
if (contet_type.find("video/mp2t") == 0 || contet_type.find("video/mpeg") == 0) {
|
if (content_type.find("video/mp2t") == 0 || content_type.find("video/mpeg") == 0) {
|
||||||
_is_ts_content = true;
|
_is_ts_content = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +38,10 @@ public:
|
|||||||
template<typename ...ArgsType>
|
template<typename ...ArgsType>
|
||||||
ClientTypeImp(ArgsType &&...args): ClientType(std::forward<ArgsType>(args)...){}
|
ClientTypeImp(ArgsType &&...args): ClientType(std::forward<ArgsType>(args)...){}
|
||||||
~ClientTypeImp() override {};
|
~ClientTypeImp() override {};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* 发送前拦截并打包为websocket协议
|
* 发送前拦截并打包为websocket协议
|
||||||
* @param buf
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
int send(const Buffer::Ptr &buf) override{
|
int send(const Buffer::Ptr &buf) override{
|
||||||
if(_beforeSendCB){
|
if(_beforeSendCB){
|
||||||
@ -50,6 +49,7 @@ protected:
|
|||||||
}
|
}
|
||||||
return ClientType::send(buf);
|
return ClientType::send(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置发送数据截取回调函数
|
* 设置发送数据截取回调函数
|
||||||
* @param cb 截取回调函数
|
* @param cb 截取回调函数
|
||||||
@ -57,6 +57,7 @@ protected:
|
|||||||
void setOnBeforeSendCB(const onBeforeSendCB &cb){
|
void setOnBeforeSendCB(const onBeforeSendCB &cb){
|
||||||
_beforeSendCB = cb;
|
_beforeSendCB = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
onBeforeSendCB _beforeSendCB;
|
onBeforeSendCB _beforeSendCB;
|
||||||
};
|
};
|
||||||
@ -73,7 +74,7 @@ public:
|
|||||||
|
|
||||||
HttpWsClient(ClientTypeImp<ClientType,DataType> &delegate) : _delegate(delegate){
|
HttpWsClient(ClientTypeImp<ClientType,DataType> &delegate) : _delegate(delegate){
|
||||||
_Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false)));
|
_Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false)));
|
||||||
_poller = delegate.getPoller();
|
setPoller(delegate.getPoller());
|
||||||
}
|
}
|
||||||
~HttpWsClient(){}
|
~HttpWsClient(){}
|
||||||
|
|
||||||
@ -108,6 +109,7 @@ public:
|
|||||||
header._mask_flag = true;
|
header._mask_flag = true;
|
||||||
WebSocketSplitter::encode(header, nullptr);
|
WebSocketSplitter::encode(header, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//HttpClientImp override
|
//HttpClientImp override
|
||||||
|
|
||||||
@ -124,6 +126,8 @@ protected:
|
|||||||
if(Sec_WebSocket_Accept == const_cast<HttpHeader &>(headers)["Sec-WebSocket-Accept"]){
|
if(Sec_WebSocket_Accept == const_cast<HttpHeader &>(headers)["Sec-WebSocket-Accept"]){
|
||||||
//success
|
//success
|
||||||
onWebSocketException(SockException());
|
onWebSocketException(SockException());
|
||||||
|
//防止ws服务器返回Content-Length
|
||||||
|
const_cast<HttpHeader &>(headers).erase("Content-Length");
|
||||||
//后续全是websocket负载数据
|
//后续全是websocket负载数据
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -180,7 +184,6 @@ protected:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* tcp连接结果
|
* tcp连接结果
|
||||||
* @param ex
|
|
||||||
*/
|
*/
|
||||||
void onConnect(const SockException &ex) override{
|
void onConnect(const SockException &ex) override{
|
||||||
if(ex){
|
if(ex){
|
||||||
@ -194,7 +197,6 @@ protected:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* tcp连接断开
|
* tcp连接断开
|
||||||
* @param ex
|
|
||||||
*/
|
*/
|
||||||
void onErr(const SockException &ex) override{
|
void onErr(const SockException &ex) override{
|
||||||
//tcp断开或者shutdown导致的断开
|
//tcp断开或者shutdown导致的断开
|
||||||
@ -208,7 +210,7 @@ protected:
|
|||||||
* @param header 数据包头
|
* @param header 数据包头
|
||||||
*/
|
*/
|
||||||
void onWebSocketDecodeHeader(const WebSocketHeader &header) override{
|
void onWebSocketDecodeHeader(const WebSocketHeader &header) override{
|
||||||
_payload.clear();
|
_payload_section.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,10 +221,9 @@ protected:
|
|||||||
* @param recved 已接收数据长度(包含本次数据长度),等于header._payload_len时则接受完毕
|
* @param recved 已接收数据长度(包含本次数据长度),等于header._payload_len时则接受完毕
|
||||||
*/
|
*/
|
||||||
void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) override{
|
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数据包后回调
|
* 接收到完整的一个webSocket数据包后回调
|
||||||
* @param header 数据包包头
|
* @param header 数据包包头
|
||||||
@ -238,28 +239,46 @@ protected:
|
|||||||
//服务器主动关闭
|
//服务器主动关闭
|
||||||
WebSocketSplitter::encode(header,nullptr);
|
WebSocketSplitter::encode(header,nullptr);
|
||||||
shutdown(SockException(Err_eof,"websocket server close the connection"));
|
shutdown(SockException(Err_eof,"websocket server close the connection"));
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WebSocketHeader::PING:{
|
case WebSocketHeader::PING:{
|
||||||
//心跳包
|
//心跳包
|
||||||
header._opcode = WebSocketHeader::PONG;
|
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;
|
break;
|
||||||
case WebSocketHeader::CONTINUATION:{
|
}
|
||||||
|
|
||||||
}
|
case WebSocketHeader::CONTINUATION:
|
||||||
break;
|
|
||||||
case WebSocketHeader::TEXT:
|
case WebSocketHeader::TEXT:
|
||||||
case WebSocketHeader::BINARY:{
|
case WebSocketHeader::BINARY:{
|
||||||
//接收完毕websocket数据包,触发onRecv事件
|
if (!header._fin) {
|
||||||
_delegate.onRecv(std::make_shared<BufferString>(std::move(_payload)));
|
//还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||||
|
_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:
|
default: break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
_payload.clear();
|
_payload_section.clear();
|
||||||
header._mask_flag = flag;
|
header._mask_flag = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,6 +290,7 @@ protected:
|
|||||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||||
HttpClientImp::send(buffer);
|
HttpClientImp::send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onWebSocketException(const SockException &ex){
|
void onWebSocketException(const SockException &ex){
|
||||||
if(!ex){
|
if(!ex){
|
||||||
@ -292,7 +312,7 @@ private:
|
|||||||
});
|
});
|
||||||
|
|
||||||
//设置sock,否则shutdown等接口都无效
|
//设置sock,否则shutdown等接口都无效
|
||||||
_delegate.setSock(HttpClientImp::_sock);
|
_delegate.setSock(HttpClientImp::getSock());
|
||||||
//触发连接成功事件
|
//触发连接成功事件
|
||||||
_delegate.onConnect(ex);
|
_delegate.onConnect(ex);
|
||||||
//拦截websocket数据接收
|
//拦截websocket数据接收
|
||||||
@ -319,10 +339,10 @@ private:
|
|||||||
string _Sec_WebSocket_Key;
|
string _Sec_WebSocket_Key;
|
||||||
function<void(const char *data, int len)> _onRecv;
|
function<void(const char *data, int len)> _onRecv;
|
||||||
ClientTypeImp<ClientType,DataType> &_delegate;
|
ClientTypeImp<ClientType,DataType> &_delegate;
|
||||||
string _payload;
|
string _payload_section;
|
||||||
|
string _payload_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tcp客户端转WebSocket客户端模板,
|
* Tcp客户端转WebSocket客户端模板,
|
||||||
* 通过该模板,开发者再不修改TcpClient派生类任何代码的情况下快速实现WebSocket协议的包装
|
* 通过该模板,开发者再不修改TcpClient派生类任何代码的情况下快速实现WebSocket协议的包装
|
||||||
@ -365,6 +385,7 @@ public:
|
|||||||
void startWebSocket(const string &ws_url,float fTimeOutSec = 3){
|
void startWebSocket(const string &ws_url,float fTimeOutSec = 3){
|
||||||
_wsClient->startWsClient(ws_url,fTimeOutSec);
|
_wsClient->startWsClient(ws_url,fTimeOutSec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typename HttpWsClient<ClientType,DataType>::Ptr _wsClient;
|
typename HttpWsClient<ClientType,DataType>::Ptr _wsClient;
|
||||||
};
|
};
|
||||||
|
@ -78,7 +78,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过该模板类可以透明化WebSocket协议,
|
* 通过该模板类可以透明化WebSocket协议,
|
||||||
* 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等
|
* 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等
|
||||||
@ -107,8 +106,9 @@ public:
|
|||||||
|
|
||||||
void attachServer(const TcpServer &server) override{
|
void attachServer(const TcpServer &server) override{
|
||||||
HttpSessionType::attachServer(server);
|
HttpSessionType::attachServer(server);
|
||||||
_weakServer = const_cast<TcpServer &>(server).shared_from_this();
|
_weak_server = const_cast<TcpServer &>(server).shared_from_this();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* websocket客户端连接上事件
|
* websocket客户端连接上事件
|
||||||
@ -117,12 +117,12 @@ protected:
|
|||||||
*/
|
*/
|
||||||
bool onWebSocketConnect(const Parser &header) override{
|
bool onWebSocketConnect(const Parser &header) override{
|
||||||
//创建websocket session类
|
//创建websocket session类
|
||||||
_session = _creator(header, *this,HttpSessionType::_sock);
|
_session = _creator(header, *this,HttpSessionType::getSock());
|
||||||
if(!_session){
|
if(!_session){
|
||||||
//此url不允许创建websocket连接
|
//此url不允许创建websocket连接
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto strongServer = _weakServer.lock();
|
auto strongServer = _weak_server.lock();
|
||||||
if(strongServer){
|
if(strongServer){
|
||||||
_session->attachServer(*strongServer);
|
_session->attachServer(*strongServer);
|
||||||
}
|
}
|
||||||
@ -145,24 +145,20 @@ protected:
|
|||||||
//允许websocket客户端
|
//允许websocket客户端
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始收到一个webSocket数据包
|
* 开始收到一个webSocket数据包
|
||||||
* @param packet
|
|
||||||
*/
|
*/
|
||||||
void onWebSocketDecodeHeader(const WebSocketHeader &packet) override{
|
void onWebSocketDecodeHeader(const WebSocketHeader &packet) override{
|
||||||
//新包,原来的包残余数据清空掉
|
//新包,原来的包残余数据清空掉
|
||||||
_remian_data.clear();
|
_payload_section.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收到websocket数据包负载
|
* 收到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 {
|
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){
|
switch (header._opcode){
|
||||||
case WebSocketHeader::CLOSE:{
|
case WebSocketHeader::CLOSE:{
|
||||||
HttpSessionType::encode(header,nullptr);
|
HttpSessionType::encode(header,nullptr);
|
||||||
}
|
HttpSessionType::shutdown(SockException(Err_shutdown, "recv close request from client"));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WebSocketHeader::PING:{
|
case WebSocketHeader::PING:{
|
||||||
header._opcode = WebSocketHeader::PONG;
|
header._opcode = WebSocketHeader::PONG;
|
||||||
HttpSessionType::encode(header,std::make_shared<BufferString>(_remian_data));
|
HttpSessionType::encode(header,std::make_shared<BufferString>(_payload_section));
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case WebSocketHeader::CONTINUATION:{
|
}
|
||||||
|
|
||||||
}
|
case WebSocketHeader::CONTINUATION:
|
||||||
break;
|
|
||||||
case WebSocketHeader::TEXT:
|
case WebSocketHeader::TEXT:
|
||||||
case WebSocketHeader::BINARY:{
|
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:
|
default: break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
_remian_data.clear();
|
_payload_section.clear();
|
||||||
header._mask_flag = flag;
|
header._mask_flag = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送数据进行websocket协议打包后回调
|
* 发送数据进行websocket协议打包后回调
|
||||||
* @param buffer
|
|
||||||
*/
|
*/
|
||||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||||
HttpSessionType::send(buffer);
|
HttpSessionType::send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string _remian_data;
|
string _payload_cache;
|
||||||
weak_ptr<TcpServer> _weakServer;
|
string _payload_section;
|
||||||
|
weak_ptr<TcpServer> _weak_server;
|
||||||
TcpSession::Ptr _session;
|
TcpSession::Ptr _session;
|
||||||
Creator _creator;
|
Creator _creator;
|
||||||
};
|
};
|
||||||
|
@ -16,10 +16,12 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "Network/Buffer.h"
|
#include "Network/Buffer.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
|
//websocket组合包最大不得超过4MB(防止内存爆炸)
|
||||||
|
#define MAX_WS_PACKET (4 * 1024 * 1024)
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class WebSocketHeader {
|
class WebSocketHeader {
|
||||||
@ -44,6 +46,7 @@ public:
|
|||||||
CONTROL_RSVF = 0xF
|
CONTROL_RSVF = 0xF
|
||||||
} Type;
|
} Type;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
WebSocketHeader() : _mask(4){
|
WebSocketHeader() : _mask(4){
|
||||||
//获取_mask内部buffer的内存地址,该内存是malloc开辟的,地址为随机
|
//获取_mask内部buffer的内存地址,该内存是malloc开辟的,地址为随机
|
||||||
uint64_t ptr = (uint64_t)(&_mask[0]);
|
uint64_t ptr = (uint64_t)(&_mask[0]);
|
||||||
@ -51,6 +54,7 @@ public:
|
|||||||
_mask.assign((uint8_t*)(&ptr), (uint8_t*)(&ptr) + 4);
|
_mask.assign((uint8_t*)(&ptr), (uint8_t*)(&ptr) + 4);
|
||||||
}
|
}
|
||||||
virtual ~WebSocketHeader(){}
|
virtual ~WebSocketHeader(){}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool _fin;
|
bool _fin;
|
||||||
uint8_t _reserved;
|
uint8_t _reserved;
|
||||||
@ -60,6 +64,26 @@ public:
|
|||||||
vector<uint8_t > _mask;
|
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{
|
class WebSocketSplitter : public WebSocketHeader{
|
||||||
public:
|
public:
|
||||||
WebSocketSplitter(){}
|
WebSocketSplitter(){}
|
||||||
@ -80,6 +104,7 @@ public:
|
|||||||
* @param buffer 负载数据
|
* @param buffer 负载数据
|
||||||
*/
|
*/
|
||||||
void encode(const WebSocketHeader &header,const Buffer::Ptr &buffer);
|
void encode(const WebSocketHeader &header,const Buffer::Ptr &buffer);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePayload回调
|
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePayload回调
|
||||||
@ -96,7 +121,6 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) {};
|
virtual void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收到完整的一个webSocket数据包后回调
|
* 接收到完整的一个webSocket数据包后回调
|
||||||
* @param header 数据包包头
|
* @param header 数据包包头
|
||||||
@ -109,8 +133,10 @@ protected:
|
|||||||
* @param len 数据指针长度
|
* @param len 数据指针长度
|
||||||
*/
|
*/
|
||||||
virtual void onWebSocketEncodeData(const Buffer::Ptr &buffer){};
|
virtual void onWebSocketEncodeData(const Buffer::Ptr &buffer){};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onPayloadData(uint8_t *data, uint64_t len);
|
void onPayloadData(uint8_t *data, uint64_t len);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string _remain_data;
|
string _remain_data;
|
||||||
int _mask_offset = 0;
|
int _mask_offset = 0;
|
||||||
|
@ -17,31 +17,43 @@ using namespace toolkit;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) {
|
MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) {
|
||||||
_poller = poller;
|
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||||
if(!_poller){
|
|
||||||
_poller = EventPollerPool::Instance().getPoller();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaPlayer::~MediaPlayer() {
|
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->setOnShutdown(_shutdownCB);
|
||||||
_delegate->setOnPlayResult(_playResultCB);
|
_delegate->setOnPlayResult(_playResultCB);
|
||||||
_delegate->setOnResume(_resumeCB);
|
_delegate->setOnResume(_resumeCB);
|
||||||
_delegate->setMediaSouce(_pMediaSrc);
|
_delegate->setMediaSouce(_pMediaSrc);
|
||||||
_delegate->mINI::operator=(*this);
|
_delegate->mINI::operator=(*this);
|
||||||
_delegate->play(strUrl);
|
_delegate->play(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventPoller::Ptr MediaPlayer::getPoller(){
|
EventPoller::Ptr MediaPlayer::getPoller(){
|
||||||
return _poller;
|
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) {
|
if (_delegate) {
|
||||||
_delegate->pause(bPause);
|
_delegate->pause(pause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +27,15 @@ public:
|
|||||||
|
|
||||||
MediaPlayer(const EventPoller::Ptr &poller = nullptr);
|
MediaPlayer(const EventPoller::Ptr &poller = nullptr);
|
||||||
virtual ~MediaPlayer();
|
virtual ~MediaPlayer();
|
||||||
void play(const string &strUrl) override;
|
void play(const string &url) override;
|
||||||
void pause(bool bPause) override;
|
void pause(bool pause) override;
|
||||||
void teardown() override;
|
void teardown() override;
|
||||||
EventPoller::Ptr getPoller();
|
EventPoller::Ptr getPoller();
|
||||||
|
void setOnCreateSocket(Socket::onCreateSocket cb);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EventPoller::Ptr _poller;
|
EventPoller::Ptr _poller;
|
||||||
|
Socket::onCreateSocket _on_create_socket;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -105,7 +105,7 @@ vector<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
|
|||||||
ret.emplace_back(_audioTrack);
|
ret.emplace_back(_audioTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::move(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Demuxer::getDuration() const {
|
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_LEN sizeof(s_mute_adts)
|
||||||
#define MUTE_ADTS_DATA_MS 130
|
#define MUTE_ADTS_DATA_MS 130
|
||||||
|
|
||||||
PlayerProxy::PlayerProxy(const string &strVhost,
|
PlayerProxy::PlayerProxy(const string &vhost, const string &app, const string &stream_id,
|
||||||
const string &strApp,
|
bool enable_hls, bool enable_mp4, int retry_count, const EventPoller::Ptr &poller)
|
||||||
const string &strSrc,
|
: MediaPlayer(poller) {
|
||||||
bool bEnableRtsp,
|
_vhost = vhost;
|
||||||
bool bEnableRtmp,
|
_app = app;
|
||||||
bool bEnableHls,
|
_stream_id = stream_id;
|
||||||
bool bEnableMp4,
|
_enable_hls = enable_hls;
|
||||||
int iRetryCount,
|
_enable_mp4 = enable_mp4;
|
||||||
const EventPoller::Ptr &poller) : MediaPlayer(poller){
|
_retry_count = retry_count;
|
||||||
_strVhost = strVhost;
|
|
||||||
_strApp = strApp;
|
|
||||||
_strSrc = strSrc;
|
|
||||||
_bEnableRtsp = bEnableRtsp;
|
|
||||||
_bEnableRtmp = bEnableRtmp;
|
|
||||||
_bEnableHls = bEnableHls;
|
|
||||||
_bEnableMp4 = bEnableMp4;
|
|
||||||
_iRetryCount = iRetryCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb){
|
void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb){
|
||||||
_playCB = cb;
|
_on_play = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerProxy::setOnClose(const function<void()> &cb){
|
void PlayerProxy::setOnClose(const function<void()> &cb){
|
||||||
_onClose = cb;
|
_on_close = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerProxy::play(const string &strUrlTmp) {
|
void PlayerProxy::play(const string &strUrlTmp) {
|
||||||
@ -82,16 +74,16 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strongSelf->_playCB) {
|
if(strongSelf->_on_play) {
|
||||||
strongSelf->_playCB(err);
|
strongSelf->_on_play(err);
|
||||||
strongSelf->_playCB = nullptr;
|
strongSelf->_on_play = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!err) {
|
if(!err) {
|
||||||
// 播放成功
|
// 播放成功
|
||||||
*piFailedCnt = 0;//连续播放失败次数清0
|
*piFailedCnt = 0;//连续播放失败次数清0
|
||||||
strongSelf->onPlaySuccess();
|
strongSelf->onPlaySuccess();
|
||||||
}else if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
}else if(*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||||
// 播放失败,延时重试播放
|
// 播放失败,延时重试播放
|
||||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||||
}
|
}
|
||||||
@ -101,21 +93,21 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
|||||||
if(!strongSelf) {
|
if(!strongSelf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(strongSelf->_mediaMuxer) {
|
if(strongSelf->_muxer) {
|
||||||
auto tracks = strongSelf->getTracks(false);
|
auto tracks = strongSelf->MediaPlayer::getTracks(false);
|
||||||
for (auto & track : tracks){
|
for (auto & track : tracks){
|
||||||
track->delDelegate(strongSelf->_mediaMuxer.get());
|
track->delDelegate(strongSelf->_muxer.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||||
if (resetWhenRePlay) {
|
if (resetWhenRePlay) {
|
||||||
strongSelf->_mediaMuxer.reset();
|
strongSelf->_muxer.reset();
|
||||||
} else {
|
} 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)++);
|
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -125,14 +117,12 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
|||||||
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
||||||
//rtsp拉流
|
//rtsp拉流
|
||||||
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
||||||
if(directProxy && _bEnableRtsp){
|
if(directProxy){
|
||||||
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
|
mediaSource = std::make_shared<RtspMediaSource>(_vhost, _app, _stream_id);
|
||||||
}
|
}
|
||||||
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
||||||
//rtmp拉流
|
//rtmp拉流,rtmp强制直接代理
|
||||||
if(_bEnableRtmp){
|
mediaSource = std::make_shared<RtmpMediaSource>(_vhost, _app, _stream_id);
|
||||||
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(mediaSource){
|
if(mediaSource){
|
||||||
setMediaSouce(mediaSource);
|
setMediaSouce(mediaSource);
|
||||||
@ -143,6 +133,7 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
|||||||
PlayerProxy::~PlayerProxy() {
|
PlayerProxy::~PlayerProxy() {
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
|
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
|
||||||
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
|
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
|
||||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
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());
|
weak_ptr<PlayerProxy> weakSelf = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||||
getPoller()->async_first([weakSlef]() {
|
getPoller()->async_first([weakSelf]() {
|
||||||
auto stronSelf = weakSlef.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (stronSelf) {
|
if (!strongSelf) {
|
||||||
stronSelf->_mediaMuxer.reset();
|
return;
|
||||||
stronSelf->setMediaSouce(nullptr);
|
}
|
||||||
stronSelf->teardown();
|
strongSelf->_muxer.reset();
|
||||||
if(stronSelf->_onClose){
|
strongSelf->setMediaSouce(nullptr);
|
||||||
stronSelf->_onClose();
|
strongSelf->teardown();
|
||||||
}
|
if (strongSelf->_on_close) {
|
||||||
|
strongSelf->_on_close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||||
@ -181,7 +173,7 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PlayerProxy::totalReaderCount(){
|
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) {
|
int PlayerProxy::totalReaderCount(MediaSource &sender) {
|
||||||
@ -193,88 +185,90 @@ public:
|
|||||||
typedef std::shared_ptr<MuteAudioMaker> Ptr;
|
typedef std::shared_ptr<MuteAudioMaker> Ptr;
|
||||||
|
|
||||||
MuteAudioMaker(){};
|
MuteAudioMaker(){};
|
||||||
virtual ~MuteAudioMaker(){}
|
~MuteAudioMaker() override {}
|
||||||
|
|
||||||
void inputFrame(const Frame::Ptr &frame) override {
|
void inputFrame(const Frame::Ptr &frame) override {
|
||||||
if(frame->getTrackType() == TrackVideo){
|
if(frame->getTrackType() == TrackVideo){
|
||||||
auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS;
|
auto audio_idx = frame->dts() / MUTE_ADTS_DATA_MS;
|
||||||
if(_iAudioIndex != iAudioIndex){
|
if(_audio_idx != audio_idx){
|
||||||
_iAudioIndex = iAudioIndex;
|
_audio_idx = audio_idx;
|
||||||
auto aacFrame = std::make_shared<AACFrameCacheAble>((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS, 0);
|
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);
|
FrameDispatcher::inputFrame(aacFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
class FrameFromStaticPtr : public FrameFromPtr{
|
||||||
public:
|
public:
|
||||||
template <typename ... ARGS>
|
template <typename ... ARGS>
|
||||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
FrameFromStaticPtr(ARGS && ...args) : FrameFromPtr(std::forward<ARGS>(args)...) {};
|
||||||
virtual ~AACFrameCacheAble() = default;
|
~FrameFromStaticPtr() override = default;
|
||||||
|
|
||||||
bool cacheAble() const override {
|
bool cacheAble() const override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _iAudioIndex = 0;
|
int _audio_idx = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
void PlayerProxy::onPlaySuccess() {
|
void PlayerProxy::onPlaySuccess() {
|
||||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||||
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
|
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
|
||||||
//rtsp拉流代理
|
//rtsp拉流代理
|
||||||
if (resetWhenRePlay || !_mediaMuxer) {
|
if (resetWhenRePlay || !_muxer) {
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), false, true, _enable_hls, _enable_mp4));
|
||||||
}
|
}
|
||||||
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
|
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
|
||||||
//rtmp拉流代理
|
//rtmp拉流代理
|
||||||
if (resetWhenRePlay || !_mediaMuxer) {
|
if (resetWhenRePlay || !_muxer) {
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
|
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), true, false, _enable_hls, _enable_mp4));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//其他拉流代理
|
//其他拉流代理
|
||||||
if (resetWhenRePlay || !_mediaMuxer) {
|
if (resetWhenRePlay || !_muxer) {
|
||||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
_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);
|
auto videoTrack = getTrack(TrackVideo, false);
|
||||||
if(videoTrack){
|
if (videoTrack) {
|
||||||
//添加视频
|
//添加视频
|
||||||
_mediaMuxer->addTrack(videoTrack);
|
_muxer->addTrack(videoTrack);
|
||||||
//视频数据写入_mediaMuxer
|
//视频数据写入_mediaMuxer
|
||||||
videoTrack->addDelegate(_mediaMuxer);
|
videoTrack->addDelegate(_muxer);
|
||||||
}
|
}
|
||||||
|
|
||||||
//是否添加静音音频
|
//是否添加静音音频
|
||||||
GET_CONFIG(bool,addMuteAudio,General::kAddMuteAudio);
|
GET_CONFIG(bool, addMuteAudio, General::kAddMuteAudio);
|
||||||
|
|
||||||
auto audioTrack = getTrack(TrackAudio, false);
|
auto audioTrack = getTrack(TrackAudio, false);
|
||||||
if(audioTrack){
|
if (audioTrack) {
|
||||||
//添加音频
|
//添加音频
|
||||||
_mediaMuxer->addTrack(audioTrack);
|
_muxer->addTrack(audioTrack);
|
||||||
//音频数据写入_mediaMuxer
|
//音频数据写入_mediaMuxer
|
||||||
audioTrack->addDelegate(_mediaMuxer);
|
audioTrack->addDelegate(_muxer);
|
||||||
}else if(addMuteAudio && videoTrack){
|
} else if (addMuteAudio && videoTrack) {
|
||||||
//没有音频信息,产生一个静音音频
|
//没有音频信息,产生一个静音音频
|
||||||
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
|
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
|
||||||
//videoTrack把数据写入MuteAudioMaker
|
//videoTrack把数据写入MuteAudioMaker
|
||||||
videoTrack->addDelegate(audioMaker);
|
videoTrack->addDelegate(audioMaker);
|
||||||
//添加一个静音Track至_mediaMuxer
|
//添加一个静音Track至_mediaMuxer
|
||||||
_mediaMuxer->addTrack(std::make_shared<AACTrack>());
|
_muxer->addTrack(std::make_shared<AACTrack>());
|
||||||
//MuteAudioMaker生成静音音频然后写入_mediaMuxer;
|
//MuteAudioMaker生成静音音频然后写入_mediaMuxer;
|
||||||
audioMaker->addDelegate(_mediaMuxer);
|
audioMaker->addDelegate(_muxer);
|
||||||
}
|
}
|
||||||
|
|
||||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||||
_mediaMuxer->addTrackCompleted();
|
_muxer->addTrackCompleted();
|
||||||
|
|
||||||
if(_pMediaSrc){
|
if (_pMediaSrc) {
|
||||||
_pMediaSrc->setTrackSource(_mediaMuxer);
|
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||||
|
_pMediaSrc->setListener(_muxer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -15,32 +15,22 @@
|
|||||||
#include "Common/Device.h"
|
#include "Common/Device.h"
|
||||||
#include "Player/MediaPlayer.h"
|
#include "Player/MediaPlayer.h"
|
||||||
#include "Util/TimeTicker.h"
|
#include "Util/TimeTicker.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class PlayerProxy :public MediaPlayer,
|
class PlayerProxy : public MediaPlayer, public MediaSourceEvent, public std::enable_shared_from_this<PlayerProxy> {
|
||||||
public std::enable_shared_from_this<PlayerProxy> ,
|
|
||||||
public MediaSourceEvent{
|
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<PlayerProxy> Ptr;
|
typedef std::shared_ptr<PlayerProxy> Ptr;
|
||||||
|
|
||||||
//如果iRetryCount<0,则一直重试播放;否则重试iRetryCount次数
|
//如果retry_count<0,则一直重试播放;否则重试retry_count次数
|
||||||
//默认一直重试
|
//默认一直重试
|
||||||
PlayerProxy(const string &strVhost,
|
PlayerProxy(const string &vhost, const string &app, const string &stream_id,
|
||||||
const string &strApp,
|
bool enable_hls = true, bool enable_mp4 = false,
|
||||||
const string &strSrc,
|
int retry_count = -1, const EventPoller::Ptr &poller = nullptr);
|
||||||
bool bEnableRtsp = true,
|
|
||||||
bool bEnableRtmp = true,
|
|
||||||
bool bEnableHls = true,
|
|
||||||
bool bEnableMp4 = false,
|
|
||||||
int iRetryCount = -1,
|
|
||||||
const EventPoller::Ptr &poller = nullptr);
|
|
||||||
|
|
||||||
virtual ~PlayerProxy();
|
~PlayerProxy() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置play结果回调,只触发一次;在play执行之前有效
|
* 设置play结果回调,只触发一次;在play执行之前有效
|
||||||
@ -64,27 +54,26 @@ public:
|
|||||||
* 获取观看总人数
|
* 获取观看总人数
|
||||||
*/
|
*/
|
||||||
int totalReaderCount() ;
|
int totalReaderCount() ;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool close(MediaSource &sender,bool force) override;
|
bool close(MediaSource &sender,bool force) override;
|
||||||
int totalReaderCount(MediaSource &sender) override;
|
int totalReaderCount(MediaSource &sender) override;
|
||||||
void rePlay(const string &strUrl,int iFailedCnt);
|
void rePlay(const string &strUrl,int iFailedCnt);
|
||||||
void onPlaySuccess();
|
void onPlaySuccess();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _bEnableRtsp;
|
bool _enable_hls;
|
||||||
bool _bEnableRtmp;
|
bool _enable_mp4;
|
||||||
bool _bEnableHls;
|
int _retry_count;
|
||||||
bool _bEnableMp4;
|
string _vhost;
|
||||||
int _iRetryCount;
|
string _app;
|
||||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
string _stream_id;
|
||||||
string _strVhost;
|
|
||||||
string _strApp;
|
|
||||||
string _strSrc;
|
|
||||||
Timer::Ptr _timer;
|
Timer::Ptr _timer;
|
||||||
function<void(const SockException &ex)> _playCB;
|
function<void()> _on_close;
|
||||||
function<void()> _onClose;
|
function<void(const SockException &ex)> _on_play;
|
||||||
|
MultiMediaSourceMuxer::Ptr _muxer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
#endif /* SRC_DEVICE_PLAYERPROXY_H_ */
|
#endif /* SRC_DEVICE_PLAYERPROXY_H_ */
|
||||||
|
@ -19,34 +19,44 @@ namespace mediakit {
|
|||||||
MediaPusher::MediaPusher(const MediaSource::Ptr &src,
|
MediaPusher::MediaPusher(const MediaSource::Ptr &src,
|
||||||
const EventPoller::Ptr &poller) {
|
const EventPoller::Ptr &poller) {
|
||||||
_src = src;
|
_src = src;
|
||||||
_poller = poller;
|
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||||
if(!_poller){
|
|
||||||
_poller = EventPollerPool::Instance().getPoller();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaPusher::MediaPusher(const string &schema,
|
MediaPusher::MediaPusher(const string &schema,
|
||||||
const string &strVhost,
|
const string &vhost,
|
||||||
const string &strApp,
|
const string &app,
|
||||||
const string &strStream,
|
const string &stream,
|
||||||
const EventPoller::Ptr &poller) :
|
const EventPoller::Ptr &poller) :
|
||||||
MediaPusher(MediaSource::find(schema,strVhost,strApp,strStream),poller){
|
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaPusher::~MediaPusher() {
|
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->setOnShutdown(_shutdownCB);
|
||||||
_delegate->setOnPublished(_publishCB);
|
_delegate->setOnPublished(_publishCB);
|
||||||
_delegate->mINI::operator=(*this);
|
_delegate->mINI::operator=(*this);
|
||||||
_delegate->publish(strUrl);
|
_delegate->publish(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventPoller::Ptr MediaPusher::getPoller(){
|
EventPoller::Ptr MediaPusher::getPoller(){
|
||||||
return _poller;
|
return _poller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaPusher::setOnCreateSocket(Socket::onCreateSocket cb){
|
||||||
|
setOnCreateSocket_l(_delegate, cb);
|
||||||
|
_on_create_socket = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -24,20 +24,24 @@ public:
|
|||||||
typedef std::shared_ptr<MediaPusher> Ptr;
|
typedef std::shared_ptr<MediaPusher> Ptr;
|
||||||
|
|
||||||
MediaPusher(const string &schema,
|
MediaPusher(const string &schema,
|
||||||
const string &strVhost,
|
const string &vhost,
|
||||||
const string &strApp,
|
const string &app,
|
||||||
const string &strStream,
|
const string &stream,
|
||||||
const EventPoller::Ptr &poller = nullptr);
|
const EventPoller::Ptr &poller = nullptr);
|
||||||
|
|
||||||
MediaPusher(const MediaSource::Ptr &src,
|
MediaPusher(const MediaSource::Ptr &src,
|
||||||
const EventPoller::Ptr &poller = nullptr);
|
const EventPoller::Ptr &poller = nullptr);
|
||||||
|
|
||||||
virtual ~MediaPusher();
|
virtual ~MediaPusher();
|
||||||
void publish(const string &strUrl) override;
|
|
||||||
|
void publish(const string &url) override;
|
||||||
EventPoller::Ptr getPoller();
|
EventPoller::Ptr getPoller();
|
||||||
|
void setOnCreateSocket(Socket::onCreateSocket cb);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::weak_ptr<MediaSource> _src;
|
std::weak_ptr<MediaSource> _src;
|
||||||
EventPoller::Ptr _poller;
|
EventPoller::Ptr _poller;
|
||||||
|
Socket::onCreateSocket _on_create_socket;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* 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;
|
string m3u8;
|
||||||
snprintf(file_content,sizeof(file_content),
|
snprintf(file_content, sizeof(file_content),
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:3\n"
|
"#EXT-X-VERSION:3\n"
|
||||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||||
"#EXT-X-TARGETDURATION:%u\n"
|
"#EXT-X-TARGETDURATION:%u\n"
|
||||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||||
(maxSegmentDuration + 999) / 1000,
|
(maxSegmentDuration + 999) / 1000,
|
||||||
sequence);
|
sequence);
|
||||||
|
|
||||||
m3u8.assign(file_content);
|
m3u8.assign(file_content);
|
||||||
|
|
||||||
for (auto &tp : _seg_dur_list) {
|
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);
|
m3u8.append(file_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eof) {
|
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);
|
m3u8.append(file_content);
|
||||||
}
|
}
|
||||||
onWriteHls(m3u8.data(), m3u8.size());
|
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) {
|
void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet) {
|
||||||
if (data && len) {
|
if (data && len) {
|
||||||
if(is_idr_fast_packet){
|
if (is_idr_fast_packet) {
|
||||||
|
//尝试切片ts
|
||||||
addNewSegment(timestamp);
|
addNewSegment(timestamp);
|
||||||
}
|
}
|
||||||
onWriteSegment((char *) data, len);
|
if (!_last_file_name.empty()) {
|
||||||
//记录上次写入数据时间
|
//存在切片才写入ts数据
|
||||||
_ticker_last_data.resetTime();
|
onWriteSegment((char *) data, len);
|
||||||
|
_last_timestamp = timestamp;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//resetTracks时触发此逻辑
|
//resetTracks时触发此逻辑
|
||||||
flushLastSegment(true);
|
flushLastSegment(true);
|
||||||
@ -74,7 +77,7 @@ void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_i
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HlsMaker::delOldSegment() {
|
void HlsMaker::delOldSegment() {
|
||||||
if(_seg_number == 0){
|
if (_seg_number == 0) {
|
||||||
//如果设置为保留0个切片,则认为是保存为点播
|
//如果设置为保留0个切片,则认为是保存为点播
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -83,15 +86,15 @@ void HlsMaker::delOldSegment() {
|
|||||||
_seg_dur_list.pop_front();
|
_seg_dur_list.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
GET_CONFIG(uint32_t,segRetain,Hls::kSegmentRetain);
|
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
|
||||||
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
|
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
|
||||||
if (_file_index > _seg_number + segRetain) {
|
if (_file_index > _seg_number + segRetain) {
|
||||||
onDelSegment(_file_index - _seg_number - segRetain - 1);
|
onDelSegment(_file_index - _seg_number - segRetain - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMaker::addNewSegment(uint32_t) {
|
void HlsMaker::addNewSegment(uint32_t stamp) {
|
||||||
if(!_last_file_name.empty() && _ticker.elapsedTime() < _seg_duration * 1000){
|
if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) {
|
||||||
//存在上个切片,并且未到分片时间
|
//存在上个切片,并且未到分片时间
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,28 +103,36 @@ void HlsMaker::addNewSegment(uint32_t) {
|
|||||||
flushLastSegment(_seg_number == 0);
|
flushLastSegment(_seg_number == 0);
|
||||||
//新增切片
|
//新增切片
|
||||||
_last_file_name = onOpenSegment(_file_index++);
|
_last_file_name = onOpenSegment(_file_index++);
|
||||||
//重置切片计时器
|
//记录本次切片的起始时间戳
|
||||||
_ticker.resetTime();
|
_last_seg_timestamp = stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMaker::flushLastSegment(bool eof){
|
void HlsMaker::flushLastSegment(bool eof){
|
||||||
if(_last_file_name.empty()){
|
if (_last_file_name.empty()) {
|
||||||
//不存在上个切片
|
//不存在上个切片
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//文件创建到最后一次数据写入的时间即为切片长度
|
//文件创建到最后一次数据写入的时间即为切片长度
|
||||||
auto seg_dur = _ticker.elapsedTime() - _ticker_last_data.elapsedTime();
|
auto seg_dur = _last_timestamp - _last_seg_timestamp;
|
||||||
if(seg_dur <= 0){
|
if (seg_dur <= 0) {
|
||||||
seg_dur = 100;
|
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();
|
delOldSegment();
|
||||||
makeIndexFile(eof);
|
makeIndexFile(eof);
|
||||||
_last_file_name.clear();
|
onFlushLastSegment(seg_dur);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HlsMaker::isLive() {
|
bool HlsMaker::isLive() {
|
||||||
return _seg_number != 0;
|
return _seg_number != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HlsMaker::clear() {
|
||||||
|
_file_index = 0;
|
||||||
|
_last_seg_timestamp = 0;
|
||||||
|
_seg_dur_list.clear();
|
||||||
|
_last_file_name.clear();
|
||||||
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
@ -39,6 +39,17 @@ public:
|
|||||||
* @param is_idr_fast_packet 是否为关键帧第一个包
|
* @param is_idr_fast_packet 是否为关键帧第一个包
|
||||||
*/
|
*/
|
||||||
void inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet);
|
void inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为直播
|
||||||
|
*/
|
||||||
|
bool isLive();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空记录
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* 创建ts切片文件回调
|
* 创建ts切片文件回调
|
||||||
@ -68,15 +79,17 @@ protected:
|
|||||||
virtual void onWriteHls(const char *data, int len) = 0;
|
virtual void onWriteHls(const char *data, int len) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭上个ts切片并且写入m3u8索引
|
* 上一个 ts 切片写入完成, 可在这里进行通知处理
|
||||||
* @param eof
|
* @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:
|
private:
|
||||||
/**
|
/**
|
||||||
* 生成m3u8文件
|
* 生成m3u8文件
|
||||||
@ -94,12 +107,13 @@ private:
|
|||||||
* @param timestamp
|
* @param timestamp
|
||||||
*/
|
*/
|
||||||
void addNewSegment(uint32_t timestamp);
|
void addNewSegment(uint32_t timestamp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _seg_number = 0;
|
|
||||||
float _seg_duration = 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;
|
uint64_t _file_index = 0;
|
||||||
Ticker _ticker;
|
|
||||||
Ticker _ticker_last_data;
|
|
||||||
string _last_file_name;
|
string _last_file_name;
|
||||||
std::deque<tuple<int,string> > _seg_dur_list;
|
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.
|
* may be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include "HlsMakerImp.h"
|
#include "HlsMakerImp.h"
|
||||||
#include "Util/util.h"
|
#include "Util/util.h"
|
||||||
#include "Util/uv_errno.h"
|
#include "Util/uv_errno.h"
|
||||||
|
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
@ -24,45 +27,61 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
|||||||
_path_hls = m3u8_file;
|
_path_hls = m3u8_file;
|
||||||
_params = params;
|
_params = params;
|
||||||
_buf_size = bufSize;
|
_buf_size = bufSize;
|
||||||
_file_buf.reset(new char[bufSize],[](char *ptr){
|
_file_buf.reset(new char[bufSize], [](char *ptr) {
|
||||||
delete[] ptr;
|
delete[] ptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_info.folder = _path_prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
HlsMakerImp::~HlsMakerImp() {
|
HlsMakerImp::~HlsMakerImp() {
|
||||||
|
clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HlsMakerImp::clearCache() {
|
||||||
//录制完了
|
//录制完了
|
||||||
flushLastSegment(true);
|
flushLastSegment(true);
|
||||||
if(isLive()){
|
if (isLive()) {
|
||||||
//hls直播才删除文件
|
//hls直播才删除文件
|
||||||
|
clear();
|
||||||
|
_file = nullptr;
|
||||||
|
_segment_file_paths.clear();
|
||||||
File::delete_file(_path_prefix.data());
|
File::delete_file(_path_prefix.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string HlsMakerImp::onOpenSegment(int index) {
|
string HlsMakerImp::onOpenSegment(int index) {
|
||||||
string segment_name , segment_path;
|
string segment_name, segment_path;
|
||||||
{
|
{
|
||||||
auto strDate = getTimeStr("%Y-%m-%d");
|
auto strDate = getTimeStr("%Y-%m-%d");
|
||||||
auto strHour = getTimeStr("%H");
|
auto strHour = getTimeStr("%H");
|
||||||
auto strTime = getTimeStr("%M-%S");
|
auto strTime = getTimeStr("%M-%S");
|
||||||
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
|
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
|
||||||
segment_path = _path_prefix + "/" + segment_name;
|
segment_path = _path_prefix + "/" + segment_name;
|
||||||
if(isLive()){
|
if (isLive()) {
|
||||||
_segment_file_paths.emplace(index,segment_path);
|
_segment_file_paths.emplace(index, segment_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_file = makeFile(segment_path, true);
|
_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()){
|
if (_params.empty()) {
|
||||||
return std::move(segment_name);
|
return segment_name;
|
||||||
}
|
}
|
||||||
return std::move(segment_name + "?" + _params);
|
return segment_name + "?" + _params;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMakerImp::onDelSegment(int index) {
|
void HlsMakerImp::onDelSegment(int index) {
|
||||||
auto it = _segment_file_paths.find(index);
|
auto it = _segment_file_paths.find(index);
|
||||||
if(it == _segment_file_paths.end()){
|
if (it == _segment_file_paths.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
File::delete_file(it->second.data());
|
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) {
|
void HlsMakerImp::onWriteHls(const char *data, int len) {
|
||||||
auto hls = makeFile(_path_hls);
|
auto hls = makeFile(_path_hls);
|
||||||
if(hls){
|
if (hls) {
|
||||||
fwrite(data,len,1,hls.get());
|
fwrite(data, len, 1, hls.get());
|
||||||
hls.reset();
|
hls.reset();
|
||||||
if(_media_src){
|
if (_media_src) {
|
||||||
_media_src->registHls();
|
_media_src->registHls(true);
|
||||||
}
|
}
|
||||||
} else{
|
} else {
|
||||||
WarnL << "create hls file falied," << _path_hls << " " << get_uv_errmsg();
|
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
|
||||||
}
|
}
|
||||||
//DebugL << "\r\n" << string(data,len);
|
//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 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) {
|
if (fp) {
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(ret && setbuf){
|
if (ret && setbuf) {
|
||||||
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
||||||
}
|
}
|
||||||
return ret;
|
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) {
|
void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||||
_media_src = std::make_shared<HlsMediaSource>(vhost, app, 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;
|
return _media_src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "HlsMaker.h"
|
#include "HlsMaker.h"
|
||||||
#include "HlsMediaSource.h"
|
#include "HlsMediaSource.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
@ -27,7 +28,8 @@ public:
|
|||||||
uint32_t bufSize = 64 * 1024,
|
uint32_t bufSize = 64 * 1024,
|
||||||
float seg_duration = 5,
|
float seg_duration = 5,
|
||||||
uint32_t seg_number = 3);
|
uint32_t seg_number = 3);
|
||||||
virtual ~HlsMakerImp();
|
|
||||||
|
~HlsMakerImp() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置媒体信息
|
* 设置媒体信息
|
||||||
@ -41,23 +43,33 @@ public:
|
|||||||
* 获取MediaSource
|
* 获取MediaSource
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
MediaSource::Ptr getMediaSource() const;
|
HlsMediaSource::Ptr getMediaSource() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空缓存
|
||||||
|
*/
|
||||||
|
void clearCache();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
string onOpenSegment(int index) override ;
|
string onOpenSegment(int index) override ;
|
||||||
void onDelSegment(int index) override;
|
void onDelSegment(int index) override;
|
||||||
void onWriteSegment(const char *data, int len) override;
|
void onWriteSegment(const char *data, int len) override;
|
||||||
void onWriteHls(const char *data, int len) override;
|
void onWriteHls(const char *data, int len) override;
|
||||||
|
void onFlushLastSegment(uint32_t duration_ms) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false);
|
std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HlsMediaSource::Ptr _media_src;
|
int _buf_size;
|
||||||
map<int /*index*/,string/*file_path*/> _segment_file_paths;
|
string _params;
|
||||||
|
string _path_hls;
|
||||||
|
string _path_prefix;
|
||||||
|
RecordInfo _info;
|
||||||
std::shared_ptr<FILE> _file;
|
std::shared_ptr<FILE> _file;
|
||||||
std::shared_ptr<char> _file_buf;
|
std::shared_ptr<char> _file_buf;
|
||||||
string _path_prefix;
|
HlsMediaSource::Ptr _media_src;
|
||||||
string _path_hls;
|
map<int /*index*/,string/*file_path*/> _segment_file_paths;
|
||||||
string _params;
|
|
||||||
int _buf_size;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
@ -23,9 +23,7 @@ void HlsCookieData::addReaderCount(){
|
|||||||
if(!*_added){
|
if(!*_added){
|
||||||
auto src = dynamic_pointer_cast<HlsMediaSource>(MediaSource::find(HLS_SCHEMA,_info._vhost,_info._app,_info._streamid));
|
auto src = dynamic_pointer_cast<HlsMediaSource>(MediaSource::find(HLS_SCHEMA,_info._vhost,_info._app,_info._streamid));
|
||||||
if(src){
|
if(src){
|
||||||
src->modifyReaderCount(true);
|
|
||||||
*_added = true;
|
*_added = true;
|
||||||
_src = src;
|
|
||||||
_ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller());
|
_ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller());
|
||||||
auto added = _added;
|
auto added = _added;
|
||||||
_ring_reader->setDetachCB([added](){
|
_ring_reader->setDetachCB([added](){
|
||||||
@ -38,18 +36,15 @@ void HlsCookieData::addReaderCount(){
|
|||||||
|
|
||||||
HlsCookieData::~HlsCookieData() {
|
HlsCookieData::~HlsCookieData() {
|
||||||
if (*_added) {
|
if (*_added) {
|
||||||
auto src = _src.lock();
|
|
||||||
if (src) {
|
|
||||||
src->modifyReaderCount(false);
|
|
||||||
}
|
|
||||||
uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000;
|
uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000;
|
||||||
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port() << ") "
|
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port() << ") "
|
||||||
<< "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid
|
<< "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid
|
||||||
<< ")断开,耗时(s):" << duration;
|
<< ")断开,耗时(s):" << duration;
|
||||||
|
|
||||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||||
if (_bytes > iFlowThreshold * 1024) {
|
uint64_t bytes = _bytes.load();
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, _bytes, duration, true, static_cast<SockInfo&>(*_sock_info));
|
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;
|
friend class HlsCookieData;
|
||||||
typedef RingBuffer<string> RingType;
|
typedef RingBuffer<string> RingType;
|
||||||
typedef std::shared_ptr<HlsMediaSource> Ptr;
|
typedef std::shared_ptr<HlsMediaSource> Ptr;
|
||||||
HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){
|
HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){}
|
||||||
_readerCount = 0;
|
~HlsMediaSource() override = default;
|
||||||
_ring = std::make_shared<RingType>();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~HlsMediaSource() = default;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取媒体源的环形缓冲
|
* 获取媒体源的环形缓冲
|
||||||
@ -37,41 +33,57 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取播放器个数
|
* 获取播放器个数
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
int readerCount() override {
|
int readerCount() override {
|
||||||
return _readerCount.load();
|
return _ring ? _ring->readerCount() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册hls
|
* 生成m3u8文件时触发
|
||||||
|
* @param file_created 是否产生了hls文件
|
||||||
*/
|
*/
|
||||||
void registHls(){
|
void registHls(bool file_created){
|
||||||
if(!_registed){
|
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();
|
regist();
|
||||||
_registed = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
if (!file_created) {
|
||||||
/**
|
//没产生文件
|
||||||
* 修改观看者个数
|
|
||||||
* @param add 添加海思删除
|
|
||||||
*/
|
|
||||||
void modifyReaderCount(bool add) {
|
|
||||||
if (add) {
|
|
||||||
++_readerCount;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//m3u8文件生成,发送给播放器
|
||||||
if (--_readerCount == 0) {
|
decltype(_list_cb) copy;
|
||||||
onNoneReader();
|
{
|
||||||
|
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:
|
private:
|
||||||
atomic_int _readerCount;
|
bool _is_regist = false;
|
||||||
bool _registed = false;
|
|
||||||
RingType::Ptr _ring;
|
RingType::Ptr _ring;
|
||||||
|
mutex _mtx_cb;
|
||||||
|
List<function<void()> > _list_cb;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HlsCookieData{
|
class HlsCookieData{
|
||||||
@ -80,13 +92,14 @@ public:
|
|||||||
HlsCookieData(const MediaInfo &info, const std::shared_ptr<SockInfo> &sock_info);
|
HlsCookieData(const MediaInfo &info, const std::shared_ptr<SockInfo> &sock_info);
|
||||||
~HlsCookieData();
|
~HlsCookieData();
|
||||||
void addByteUsage(uint64_t bytes);
|
void addByteUsage(uint64_t bytes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addReaderCount();
|
void addReaderCount();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t _bytes = 0;
|
atomic<uint64_t> _bytes {0};
|
||||||
MediaInfo _info;
|
MediaInfo _info;
|
||||||
std::shared_ptr<bool> _added;
|
std::shared_ptr<bool> _added;
|
||||||
weak_ptr<HlsMediaSource> _src;
|
|
||||||
Ticker _ticker;
|
Ticker _ticker;
|
||||||
std::shared_ptr<SockInfo> _sock_info;
|
std::shared_ptr<SockInfo> _sock_info;
|
||||||
HlsMediaSource::RingType::RingReader::Ptr _ring_reader;
|
HlsMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||||
|
@ -15,37 +15,70 @@
|
|||||||
#include "TsMuxer.h"
|
#include "TsMuxer.h"
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class HlsRecorder
|
class HlsRecorder : public MediaSourceEventInterceptor, public TsMuxer, public std::enable_shared_from_this<HlsRecorder> {
|
||||||
#if defined(ENABLE_HLS)
|
|
||||||
: public TsMuxer
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<HlsRecorder> Ptr;
|
typedef std::shared_ptr<HlsRecorder> Ptr;
|
||||||
HlsRecorder(const string &m3u8_file, const string ¶ms){
|
HlsRecorder(const string &m3u8_file, const string ¶ms){
|
||||||
GET_CONFIG(uint32_t,hlsNum,Hls::kSegmentNum);
|
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
|
||||||
GET_CONFIG(uint32_t,hlsBufSize,Hls::kFileBufSize);
|
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
|
||||||
GET_CONFIG(uint32_t,hlsDuration,Hls::kSegmentDuration);
|
GET_CONFIG(uint32_t, hlsDuration, Hls::kSegmentDuration);
|
||||||
_hls = new HlsMakerImp(m3u8_file,params,hlsBufSize,hlsDuration,hlsNum);
|
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum);
|
||||||
|
//清空上次的残余文件
|
||||||
|
_hls->clearCache();
|
||||||
}
|
}
|
||||||
~HlsRecorder(){
|
|
||||||
delete _hls;
|
~HlsRecorder(){}
|
||||||
}
|
|
||||||
void setMediaSource(const string &vhost, const string &app, const string &stream_id){
|
void setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||||
_hls->setMediaSource(vhost, app, stream_id);
|
_hls->setMediaSource(vhost, app, stream_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSource::Ptr getMediaSource() const{
|
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||||
return _hls->getMediaSource();
|
_listener = listener;
|
||||||
|
_hls->getMediaSource()->setListener(shared_from_this());
|
||||||
|
//先注册媒体流,后续可以按需生成
|
||||||
|
_hls->getMediaSource()->registHls(false);
|
||||||
}
|
}
|
||||||
#if defined(ENABLE_HLS)
|
|
||||||
protected:
|
int readerCount() {
|
||||||
void onTs(const void *packet, int bytes,uint32_t timestamp,bool is_idr_fast_packet) override {
|
return _hls->getMediaSource()->readerCount();
|
||||||
_hls->inputData((char *)packet,bytes,timestamp, is_idr_fast_packet);
|
}
|
||||||
};
|
|
||||||
#endif
|
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:
|
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
|
}//namespace mediakit
|
||||||
#endif //HLSRECORDER_H
|
#endif //HLSRECORDER_H
|
||||||
|
@ -13,34 +13,125 @@
|
|||||||
#include "Util/File.h"
|
#include "Util/File.h"
|
||||||
#include "Util/logger.h"
|
#include "Util/logger.h"
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
|
#include "fmp4-writer.h"
|
||||||
|
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
namespace mediakit {
|
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 = {
|
static struct mov_buffer_t s_io = {
|
||||||
[](void* ctx, void* data, uint64_t bytes) {
|
[](void *ctx, void *data, uint64_t bytes) {
|
||||||
MP4File *thiz = (MP4File *)ctx;
|
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||||
return thiz->onRead(data,bytes);
|
return thiz->onRead(data, bytes);
|
||||||
},
|
},
|
||||||
[](void* ctx, const void* data, uint64_t bytes){
|
[](void *ctx, const void *data, uint64_t bytes) {
|
||||||
MP4File *thiz = (MP4File *)ctx;
|
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||||
return thiz->onWrite(data,bytes);
|
return thiz->onWrite(data, bytes);
|
||||||
},
|
},
|
||||||
[](void* ctx, uint64_t offset) {
|
[](void *ctx, uint64_t offset) {
|
||||||
MP4File *thiz = (MP4File *)ctx;
|
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||||
return thiz->onSeek(offset);
|
return thiz->onSeek(offset);
|
||||||
},
|
},
|
||||||
[](void* ctx){
|
[](void *ctx) {
|
||||||
MP4File *thiz = (MP4File *)ctx;
|
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||||
return thiz->onTell();
|
return thiz->onTell();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MP4File::Writer MP4File::createWriter(){
|
MP4FileIO::Writer MP4FileIO::createWriter(int flags, bool is_fmp4){
|
||||||
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
|
|
||||||
Writer writer;
|
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){
|
if(ptr){
|
||||||
mov_writer_destroy(ptr);
|
mp4_writer_destroy(ptr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(!writer){
|
if(!writer){
|
||||||
@ -49,9 +140,11 @@ MP4File::Writer MP4File::createWriter(){
|
|||||||
return writer;
|
return writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
MP4File::Reader MP4File::createReader(){
|
MP4FileIO::Reader MP4FileIO::createReader(){
|
||||||
Reader reader;
|
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){
|
if(ptr){
|
||||||
mov_reader_destroy(ptr);
|
mov_reader_destroy(ptr);
|
||||||
}
|
}
|
||||||
@ -62,15 +155,17 @@ MP4File::Reader MP4File::createReader(){
|
|||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////MP4FileDisk/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
#define fseek64 _fseeki64
|
#define fseek64 _fseeki64
|
||||||
#define ftell64 _ftelli64
|
#define ftell64 _ftelli64
|
||||||
#else
|
#else
|
||||||
#define fseek64 fseek
|
#define fseek64 fseek
|
||||||
#define ftell64 ftell
|
#define ftell64 ftell
|
||||||
#endif
|
#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);
|
auto fp = File::create_file(file, mode);
|
||||||
if(!fp){
|
if(!fp){
|
||||||
@ -98,28 +193,74 @@ void MP4File::openFile(const char *file,const char *mode) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4File::closeFile() {
|
void MP4FileDisk::closeFile() {
|
||||||
_file = nullptr;
|
_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())){
|
if (bytes == fread(data, 1, bytes, _file.get())){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0 != ferror(_file.get()) ? ferror(_file.get()) : -1 /*EOF*/;
|
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());
|
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);
|
return fseek64(_file.get(), offset, SEEK_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t MP4File::onTell() {
|
uint64_t MP4FileDisk::onTell() {
|
||||||
return ftell64(_file.get());
|
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
|
}//namespace mediakit
|
||||||
#endif //NABLE_MP4RECORD
|
#endif //NABLE_MP4RECORD
|
||||||
|
128
src/Record/MP4.h
128
src/Record/MP4.h
@ -23,27 +23,127 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class MP4File {
|
//以下是fmp4/mov的通用接口,简单包装了ireader/media-server的接口
|
||||||
public:
|
typedef struct mp4_writer_t mp4_writer_t;
|
||||||
friend struct mov_buffer_t;
|
mp4_writer_t* mp4_writer_create(int is_fmp4, const struct mov_buffer_t *buffer, void* param, int flags);
|
||||||
typedef std::shared_ptr<mov_writer_t> Writer;
|
void mp4_writer_destroy(mp4_writer_t* mp4);
|
||||||
typedef std::shared_ptr<mov_reader_t> Reader;
|
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);
|
||||||
MP4File() = default;
|
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);
|
||||||
virtual ~MP4File() = default;
|
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();
|
//mp4文件IO的抽象接口类
|
||||||
Reader createReader();
|
class MP4FileIO : public std::enable_shared_from_this<MP4FileIO> {
|
||||||
void openFile(const char *file,const char *mode);
|
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();
|
void closeFile();
|
||||||
|
|
||||||
int onRead(void* data, uint64_t bytes);
|
protected:
|
||||||
int onWrite(const void* data, uint64_t bytes);
|
uint64_t onTell() override;
|
||||||
int onSeek( uint64_t offset);
|
int onSeek(uint64_t offset) override;
|
||||||
uint64_t onTell();
|
int onRead(void *data, uint64_t bytes) override;
|
||||||
|
int onWrite(const void *data, uint64_t bytes) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<FILE> _file;
|
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
|
}//namespace mediakit
|
||||||
#endif //NABLE_MP4RECORD
|
#endif //NABLE_MP4RECORD
|
||||||
#endif //ZLMEDIAKIT_MP4_H
|
#endif //ZLMEDIAKIT_MP4_H
|
||||||
|
@ -15,21 +15,24 @@
|
|||||||
#include "Extension/H264.h"
|
#include "Extension/H264.h"
|
||||||
#include "Extension/AAC.h"
|
#include "Extension/AAC.h"
|
||||||
#include "Extension/G711.h"
|
#include "Extension/G711.h"
|
||||||
|
#include "Extension/Opus.h"
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
MP4Demuxer::MP4Demuxer(const char *file) {
|
MP4Demuxer::MP4Demuxer() {}
|
||||||
openFile(file,"rb+");
|
|
||||||
_mov_reader = createReader();
|
|
||||||
getAllTracks();
|
|
||||||
_duration_ms = mov_reader_getduration(_mov_reader.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
MP4Demuxer::~MP4Demuxer() {
|
MP4Demuxer::~MP4Demuxer() {
|
||||||
_mov_reader = nullptr;
|
_mov_reader = nullptr;
|
||||||
closeFile();
|
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() {
|
int MP4Demuxer::getAllTracks() {
|
||||||
static mov_reader_trackinfo_t s_on_track = {
|
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) {
|
[](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:{
|
case MOV_OBJECT_AAC:{
|
||||||
auto audio = std::make_shared<AACTrack>(bytes > 0 ? string((char *)extra,bytes) : "");
|
auto audio = std::make_shared<AACTrack>(bytes > 0 ? string((char *)extra,bytes) : "");
|
||||||
_track_to_codec.emplace(track_id, audio);
|
_track_to_codec.emplace(track_id, audio);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case MOV_OBJECT_G711a:
|
case MOV_OBJECT_G711a:
|
||||||
case MOV_OBJECT_G711u:{
|
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 );
|
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);
|
_track_to_codec.emplace(track_id, audio);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MOV_OBJECT_OPUS: {
|
||||||
|
auto audio = std::make_shared<OpusTrack>();
|
||||||
|
_track_to_codec.emplace(track_id, audio);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
WarnL << "不支持该编码类型的MP4,已忽略:" << getObjectName(object);
|
WarnL << "不支持该编码类型的MP4,已忽略:" << getObjectName(object);
|
||||||
break;
|
break;
|
||||||
@ -149,6 +160,8 @@ struct Context{
|
|||||||
BufferRaw::Ptr buffer;
|
BufferRaw::Ptr buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define DATA_OFFSET ADTS_HEADER_LEN
|
||||||
|
|
||||||
Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||||
keyFrame = false;
|
keyFrame = false;
|
||||||
eof = 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 * {
|
static mov_onalloc mov_onalloc = [](void *param, int bytes) -> void * {
|
||||||
Context *ctx = (Context *) param;
|
Context *ctx = (Context *) param;
|
||||||
ctx->buffer = ctx->thiz->_buffer_pool.obtain();
|
ctx->buffer = ctx->thiz->_buffer_pool.obtain();
|
||||||
ctx->buffer->setCapacity(bytes + 1);
|
ctx->buffer->setCapacity(bytes + DATA_OFFSET + 1);
|
||||||
ctx->buffer->setSize(bytes);
|
ctx->buffer->setSize(bytes + DATA_OFFSET);
|
||||||
return ctx->buffer->data();
|
return ctx->buffer->data() + DATA_OFFSET;
|
||||||
};
|
};
|
||||||
|
|
||||||
Context ctx = {this, 0};
|
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) {
|
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);
|
auto it = _track_to_codec.find(track_id);
|
||||||
if (it == _track_to_codec.end()) {
|
if (it == _track_to_codec.end()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto numBytes = buf->size();
|
auto bytes = buf->size() - DATA_OFFSET;
|
||||||
auto pBytes = buf->data();
|
auto data = buf->data() + DATA_OFFSET;
|
||||||
auto codec = it->second->getCodecId();
|
auto codec = it->second->getCodecId();
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case CodecH264 :
|
case CodecH264 :
|
||||||
case CodecH265 : {
|
case CodecH265 : {
|
||||||
uint32_t iOffset = 0;
|
uint32_t offset = 0;
|
||||||
while (iOffset < numBytes) {
|
while (offset < bytes) {
|
||||||
uint32_t iFrameLen;
|
uint32_t frame_len;
|
||||||
memcpy(&iFrameLen, pBytes + iOffset, 4);
|
memcpy(&frame_len, data + offset, 4);
|
||||||
iFrameLen = ntohl(iFrameLen);
|
frame_len = ntohl(frame_len);
|
||||||
if (iFrameLen + iOffset + 4 > numBytes) {
|
if (frame_len + offset + 4 > bytes) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4);
|
memcpy(data + offset, "\x0\x0\x0\x1", 4);
|
||||||
iOffset += (iFrameLen + 4);
|
offset += (frame_len + 4);
|
||||||
}
|
}
|
||||||
if (codec == CodecH264) {
|
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 :
|
case CodecAAC: {
|
||||||
return std::make_shared<FrameWrapper<AACFrameNoCacheAble> >(buf, pts, dts, 0);
|
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 CodecG711A:
|
||||||
case CodecG711U: {
|
case CodecG711U: {
|
||||||
auto frame = std::make_shared<FrameWrapper<G711FrameNoCacheAble> >(buf, pts, dts, 0);
|
return std::make_shared<FrameWrapper<FrameFromPtr> >(buf, dts, pts, 0, DATA_OFFSET, codec);
|
||||||
frame->setCodec(codec);
|
|
||||||
return frame;
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +256,7 @@ vector<Track::Ptr> MP4Demuxer::getTracks(bool trackReady) const {
|
|||||||
}
|
}
|
||||||
ret.push_back(pr.second);
|
ret.push_back(pr.second);
|
||||||
}
|
}
|
||||||
return std::move(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t MP4Demuxer::getDurationMS() const {
|
uint64_t MP4Demuxer::getDurationMS() const {
|
||||||
|
@ -16,24 +16,60 @@
|
|||||||
#include "Util/ResourcePool.h"
|
#include "Util/ResourcePool.h"
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class MP4Demuxer : public MP4File, public TrackSource{
|
class MP4Demuxer : public MP4FileDisk, public TrackSource{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<MP4Demuxer> Ptr;
|
typedef std::shared_ptr<MP4Demuxer> Ptr;
|
||||||
MP4Demuxer(const char *file);
|
|
||||||
|
/**
|
||||||
|
* 创建mp4解复用器
|
||||||
|
*/
|
||||||
|
MP4Demuxer();
|
||||||
~MP4Demuxer() override;
|
~MP4Demuxer() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开文件
|
||||||
|
* @param file mp4文件路径
|
||||||
|
*/
|
||||||
|
void openMP4(const string &file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动时间轴至某处
|
||||||
|
* @param stamp_ms 预期的时间轴位置,单位毫秒
|
||||||
|
* @return 时间轴位置
|
||||||
|
*/
|
||||||
int64_t seekTo(int64_t stamp_ms);
|
int64_t seekTo(int64_t stamp_ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取一帧数据
|
||||||
|
* @param keyFrame 是否为关键帧
|
||||||
|
* @param eof 是否文件读取完毕
|
||||||
|
* @return 帧数据,可能为空
|
||||||
|
*/
|
||||||
Frame::Ptr readFrame(bool &keyFrame, bool &eof);
|
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;
|
uint64_t getDurationMS() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int getAllTracks();
|
int getAllTracks();
|
||||||
void onVideoTrack(uint32_t track_id, uint8_t object, int width, int height, const void* extra, size_t bytes);
|
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);
|
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);
|
Frame::Ptr makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int64_t pts, int64_t dts);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MP4File::Reader _mov_reader;
|
Reader _mov_reader;
|
||||||
uint64_t _duration_ms = 0;
|
uint64_t _duration_ms = 0;
|
||||||
map<int, Track::Ptr > _track_to_codec;
|
map<int, Track::Ptr> _track_to_codec;
|
||||||
ResourcePool<BufferRaw> _buffer_pool;
|
ResourcePool<BufferRaw> _buffer_pool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,33 +14,57 @@
|
|||||||
#include "Extension/H264.h"
|
#include "Extension/H264.h"
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
MP4Muxer::MP4Muxer(const char *file) {
|
MP4Muxer::MP4Muxer() {}
|
||||||
_file_name = file;
|
|
||||||
openMP4();
|
|
||||||
}
|
|
||||||
|
|
||||||
MP4Muxer::~MP4Muxer() {
|
MP4Muxer::~MP4Muxer() {
|
||||||
closeMP4();
|
closeMP4();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Muxer::openMP4(){
|
void MP4Muxer::openMP4(const string &file){
|
||||||
closeMP4();
|
closeMP4();
|
||||||
openFile(_file_name.data(), "wb+");
|
_file_name = file;
|
||||||
_mov_writter = createWriter();
|
_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(){
|
void MP4Muxer::closeMP4(){
|
||||||
_mov_writter = nullptr;
|
MP4MuxerInterface::resetTracks();
|
||||||
closeFile();
|
_mp4_file = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Muxer::resetTracks() {
|
void MP4Muxer::resetTracks() {
|
||||||
_codec_to_trackid.clear();
|
MP4MuxerInterface::resetTracks();
|
||||||
_started = false;
|
openMP4(_file_name);
|
||||||
_have_video = false;
|
|
||||||
openMP4();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
auto it = _codec_to_trackid.find(frame->getCodecId());
|
||||||
if(it == _codec_to_trackid.end()){
|
if(it == _codec_to_trackid.end()){
|
||||||
//该Track不存在或初始化失败
|
//该Track不存在或初始化失败
|
||||||
@ -48,17 +72,13 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!_started) {
|
if (!_started) {
|
||||||
//还没开始
|
//该逻辑确保含有视频时,第一帧为关键帧
|
||||||
if (!_have_video) {
|
if (_have_video && !frame->keyFrame()) {
|
||||||
_started = true;
|
//含有视频,但是不是关键帧,那么前面的帧丢弃
|
||||||
} else {
|
return;
|
||||||
if (frame->getTrackType() != TrackVideo || !frame->keyFrame()) {
|
|
||||||
//如果首帧是音频或者是视频但是不是i帧,那么不能开始写文件
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//开始写文件
|
|
||||||
_started = true;
|
|
||||||
}
|
}
|
||||||
|
//开始写文件
|
||||||
|
_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//mp4文件时间戳需要从0开始
|
//mp4文件时间戳需要从0开始
|
||||||
@ -88,24 +108,23 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
|||||||
merged.append((char *) &nalu_size, 4);
|
merged.append((char *) &nalu_size, 4);
|
||||||
merged.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
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,
|
track_info.track_id,
|
||||||
merged.data(),
|
merged.data(),
|
||||||
merged.size(),
|
merged.size(),
|
||||||
pts_out,
|
pts_out,
|
||||||
dts_out,
|
dts_out,
|
||||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
|
||||||
1/*我们合并时已经生成了4个字节的MP4格式start code*/);
|
|
||||||
} else {
|
} else {
|
||||||
//缓存中只有一帧视频
|
//缓存中只有一帧视频
|
||||||
mov_writer_write_l(_mov_writter.get(),
|
mp4_writer_write_l(_mov_writter.get(),
|
||||||
track_info.track_id,
|
track_info.track_id,
|
||||||
back->data() + back->prefixSize(),
|
back->data() + back->prefixSize(),
|
||||||
back->size() - back->prefixSize(),
|
back->size() - back->prefixSize(),
|
||||||
pts_out,
|
pts_out,
|
||||||
dts_out,
|
dts_out,
|
||||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||||
0/*需要生成头4个字节的MP4格式start code*/);
|
1/*需要生成头4个字节的MP4格式start code*/);
|
||||||
}
|
}
|
||||||
_frameCached.clear();
|
_frameCached.clear();
|
||||||
}
|
}
|
||||||
@ -115,14 +134,13 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
|||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
|
track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
|
||||||
mov_writer_write_l(_mov_writter.get(),
|
mp4_writer_write(_mov_writter.get(),
|
||||||
track_info.track_id,
|
track_info.track_id,
|
||||||
frame->data() + frame->prefixSize(),
|
frame->data() + frame->prefixSize(),
|
||||||
frame->size() - frame->prefixSize(),
|
frame->size() - frame->prefixSize(),
|
||||||
pts_out,
|
pts_out,
|
||||||
dts_out,
|
dts_out,
|
||||||
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
|
||||||
1/*aac或其他类型frame不用添加4个nalu_size的字节*/);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -140,7 +158,7 @@ static uint8_t getObject(CodecId codecId){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Muxer::stampSync(){
|
void MP4MuxerInterface::stampSync(){
|
||||||
if(_codec_to_trackid.size() < 2){
|
if(_codec_to_trackid.size() < 2){
|
||||||
return;
|
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());
|
auto mp4_object = getObject(track->getCodecId());
|
||||||
if (!mp4_object) {
|
if (!mp4_object) {
|
||||||
WarnL << "MP4录制不支持该编码格式:" << track->getCodecName();
|
WarnL << "MP4录制不支持该编码格式:" << track->getCodecName();
|
||||||
@ -182,7 +203,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
auto track_id = mp4_writer_add_audio(_mov_writter.get(),
|
||||||
mp4_object,
|
mp4_object,
|
||||||
audio_track->getAudioChannel(),
|
audio_track->getAudioChannel(),
|
||||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||||
@ -203,7 +224,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
auto track_id = mp4_writer_add_audio(_mov_writter.get(),
|
||||||
mp4_object,
|
mp4_object,
|
||||||
audio_track->getAudioChannel(),
|
audio_track->getAudioChannel(),
|
||||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||||
@ -236,7 +257,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
auto track_id = mp4_writer_add_video(_mov_writter.get(),
|
||||||
mp4_object,
|
mp4_object,
|
||||||
h264_track->getVideoWidth(),
|
h264_track->getVideoWidth(),
|
||||||
h264_track->getVideoHeight(),
|
h264_track->getVideoHeight(),
|
||||||
@ -271,7 +292,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
auto track_id = mp4_writer_add_video(_mov_writter.get(),
|
||||||
mp4_object,
|
mp4_object,
|
||||||
h265_track->getVideoWidth(),
|
h265_track->getVideoWidth(),
|
||||||
h265_track->getVideoHeight(),
|
h265_track->getVideoHeight(),
|
||||||
@ -293,5 +314,54 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
|||||||
stampSync();
|
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
|
}//namespace mediakit
|
||||||
#endif//#ifdef ENABLE_MP4
|
#endif//#ifdef ENABLE_MP4
|
||||||
|
@ -23,17 +23,16 @@
|
|||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
class MP4Muxer : public MediaSinkInterface, public MP4File{
|
class MP4MuxerInterface : public MediaSinkInterface {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<MP4Muxer> Ptr;
|
MP4MuxerInterface() = default;
|
||||||
|
~MP4MuxerInterface() override = default;
|
||||||
MP4Muxer(const char *file);
|
|
||||||
~MP4Muxer() override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加已经ready状态的track
|
* 添加已经ready状态的track
|
||||||
*/
|
*/
|
||||||
void addTrack(const Track::Ptr & track) override;
|
void addTrack(const Track::Ptr &track) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入帧
|
* 输入帧
|
||||||
*/
|
*/
|
||||||
@ -42,30 +41,112 @@ public:
|
|||||||
/**
|
/**
|
||||||
* 重置所有track
|
* 重置所有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();
|
void closeMP4();
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
void openMP4();
|
MP4FileIO::Writer createWriter() override;
|
||||||
void stampSync();
|
|
||||||
|
|
||||||
private:
|
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;
|
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
|
}//namespace mediakit
|
||||||
#endif//#ifdef ENABLE_MP4
|
#endif//#ifdef ENABLE_MP4
|
||||||
#endif //ZLMEDIAKIT_MP4MUXER_H
|
#endif //ZLMEDIAKIT_MP4MUXER_H
|
||||||
|
@ -29,7 +29,8 @@ MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &
|
|||||||
strFileName = File::absolutePath(strFileName,recordPath);
|
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));
|
_mediaMuxer.reset(new MultiMediaSourceMuxer(strVhost, strApp, strId, _demuxer->getDurationMS() / 1000.0, true, true, false, false));
|
||||||
auto tracks = _demuxer->getTracks(false);
|
auto tracks = _demuxer->getTracks(false);
|
||||||
if(tracks.empty()){
|
if(tracks.empty()){
|
||||||
|
@ -35,6 +35,7 @@ public:
|
|||||||
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
||||||
*/
|
*/
|
||||||
void startReadMP4();
|
void startReadMP4();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//MediaSourceEvent override
|
//MediaSourceEvent override
|
||||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||||
@ -45,15 +46,16 @@ private:
|
|||||||
uint32_t getCurrentStamp();
|
uint32_t getCurrentStamp();
|
||||||
void setCurrentStamp(uint32_t ui32Stamp);
|
void setCurrentStamp(uint32_t ui32Stamp);
|
||||||
bool seekTo(uint32_t ui32Stamp);
|
bool seekTo(uint32_t ui32Stamp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
recursive_mutex _mtx;
|
bool _have_video = false;
|
||||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
|
||||||
uint32_t _seek_to;
|
uint32_t _seek_to;
|
||||||
|
recursive_mutex _mtx;
|
||||||
Ticker _seek_ticker;
|
Ticker _seek_ticker;
|
||||||
Timer::Ptr _timer;
|
Timer::Ptr _timer;
|
||||||
EventPoller::Ptr _poller;
|
EventPoller::Ptr _poller;
|
||||||
MP4Demuxer::Ptr _demuxer;
|
MP4Demuxer::Ptr _demuxer;
|
||||||
bool _have_video = false;
|
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* 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