Merge branch 'master' into dev

This commit is contained in:
xiongguangjie 2023-09-15 14:01:34 +08:00
commit 228cbb1127
121 changed files with 2753 additions and 1360 deletions

View File

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

View File

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

@ -1 +1 @@
Subproject commit e4744a0a523817356f2ec995ee5a732264c31629
Subproject commit 5d74e09b8c84cccc46036ed2ef1a62f670c423d4

11
AUTHORS
View File

@ -74,3 +74,14 @@ WuPeng <wp@zafu.edu.cn>
[朱如洪 ](https://github.com/zhu410289616)
[lijin](https://github.com/1461521844lijin)
[PioLing](https://github.com/PioLing)
[BackT0TheFuture](https://github.com/BackT0TheFuture)
[perara](https://github.com/perara)
[codeRATny](https://github.com/codeRATny)
[dengjfzh](https://github.com/dengjfzh)
[百鸣](https://github.com/ixingqiao)
[fruit Juice](https://github.com/xuandu)
[tbago](https://github.com/tbago)
[Luosh](https://github.com/Luosh)
[linxiaoyan87](https://github.com/linxiaoyan)
[waken](https://github.com/mc373906408)
[Deepslient](https://github.com/Deepslient)

View File

@ -41,6 +41,7 @@ option(ENABLE_HLS "Enable HLS" ON)
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF)
option(ENABLE_MP4 "Enable MP4" ON)
option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON)
option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON)
option(ENABLE_MYSQL "Enable MySQL" OFF)
option(ENABLE_OPENSSL "Enable OpenSSL" ON)
@ -200,8 +201,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
endif()
# mediakit runtime
update_cached_list(MK_LINK_LIBRARIES "")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
update_cached(MK_LINK_LIBRARIES "")
update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
if (DISABLE_REPORT)
update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT)
@ -461,7 +462,7 @@ if(ENABLE_SERVER)
endif()
# Android add_subdirectory
if(ENABLE_SERVER_LIB)
if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE)
set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE)
endif()

View File

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

View File

@ -45,7 +45,7 @@
## Feature List
### Overview of Features
<img width="800" alt="Overview of Features" src="https://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png">
<img width="800" alt="Overview of Features" src="https://github.com/ZLMediaKit/ZLMediaKit/assets/11495632/481ea769-5b27-495e-bf7d-31191e6af9d2">
- RTSP[S]
- RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show
@ -68,9 +68,10 @@
- Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol
- Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
- Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81)
- Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp)
- HLS
- Supports HLS file generation and comes with an HTTP file server
- Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server
- Through cookie tracking technology, it can simulate HLS playback as a long connection, which can achieve HLS on-demand pulling, playback statistics, and other businesses
- Supports HLS player and can pull HLS to rtsp/rtmp/mp4
- Supports H264/H265/AAC/G711/OPUS encoding
@ -349,6 +350,7 @@ bash build_docker_images.sh
- Media management platform
- [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro)
- [Powerful media control and management interface platform, supporting GB28181](https://github.com/chatop2020/AKStream)
- [GB28181 server implemented in C++](https://github.com/any12345com/BXC_SipServer)
- [GB28181 server implemented in Go](https://github.com/panjjo/gosip)
- [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http)
- [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome)
@ -363,6 +365,7 @@ bash build_docker_images.sh
- [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js)
- [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer)
- [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
- [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player)
## License
@ -479,6 +482,21 @@ Thanks to all those who have supported this project in various ways, including b
[朱如洪 ](https://github.com/zhu410289616)
[lijin](https://github.com/1461521844lijin)
[PioLing](https://github.com/PioLing)
[BackT0TheFuture](https://github.com/BackT0TheFuture)
[perara](https://github.com/perara)
[codeRATny](https://github.com/codeRATny)
[dengjfzh](https://github.com/dengjfzh)
[百鸣](https://github.com/ixingqiao)
[fruit Juice](https://github.com/xuandu)
[tbago](https://github.com/tbago)
[Luosh](https://github.com/Luosh)
[linxiaoyan87](https://github.com/linxiaoyan)
[waken](https://github.com/mc373906408)
[Deepslient](https://github.com/Deepslient)
Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion:
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
## Use Cases

View File

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

View File

@ -100,6 +100,9 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so
API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx);
// copy track reference by index from MediaSource, please use mk_track_unref to release it
API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index);
// MediaSource::broadcastMessage
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len);
/**
* ZLMediaKit中被称作为MediaSource
* 3RtmpMediaSourceRtspMediaSourceHlsMediaSource

View File

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

View File

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

View File

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

View File

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

View File

@ -214,6 +214,16 @@ API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx
return (mk_track) new Track::Ptr(std::move(tracks[index]));
}
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) {
assert(ctx && msg && len);
MediaSource *src = (MediaSource *)ctx;
Any any;
Buffer::Ptr buffer = std::make_shared<BufferLikeString>(std::string(msg, len));
any.set(std::move(buffer));
return src->broadcastMessage(any);
}
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){
assert(ctx);
MediaSource *src = (MediaSource *)ctx;

View File

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

View File

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

View File

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

View File

@ -40,13 +40,20 @@ modify_stamp=2
enable_audio=1
#添加acc静音音频在关闭音频时此开关无效
add_mute_audio=1
#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
#此配置置1时此流如果无人观看将不触发on_none_reader hook回调
#而是将直接关闭流
auto_close=0
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
#置0关闭此特性(推流断开会导致立即断开播放器)
#此参数不应大于播放器超时时间;单位毫秒
continue_push_ms=15000
#是否开启转换为hls
#是否开启转换为hls(mpegts)
enable_hls=1
#是否开启转换为hls(fmp4)
enable_hls_fmp4=0
#是否开启MP4录制
enable_mp4=0
#是否开启转换为rtsp/webrtc
@ -124,7 +131,7 @@ segDur=2
segNum=3
#HLS切片从m3u8文件中移除后继续保留在磁盘上的个数
segRetain=5
#是否广播 ts 切片完成通知
#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
broadcastRecordTs=0
#直播hls文件删除延时单位秒issue: #913
deleteDelaySec=10
@ -135,9 +142,6 @@ deleteDelaySec=10
segKeep=0
[hook]
#在推流时如果url参数匹对admin_params那么可以不经过hook鉴权直接推流成功播放时亦然
#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
#是否启用hook事件启用后推拉流都将进行鉴权
enable=0
#播放器或推流器使用流量事件,置空则关闭
@ -150,7 +154,7 @@ on_play=https://127.0.0.1/index/hook/on_play
on_publish=https://127.0.0.1/index/hook/on_publish
#录制mp4切片完成事件
on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
# 录制 hls ts 切片完成事件
# 录制 hls ts(或fmp4) 切片完成事件
on_record_ts=https://127.0.0.1/index/hook/on_record_ts
#rtsp播放鉴权事件此事件中比对rtsp的用户名密码
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
@ -162,6 +166,8 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm
on_shell_login=https://127.0.0.1/index/hook/on_shell_login
#直播流注册或注销事件
on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed
#过滤on_stream_changed hook的协议类型可以选择只监听某些感兴趣的协议置空则不过滤协议
stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4
#无人观看流事件通过该事件可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
#播放时未找到流事件通过配合hook.on_stream_none_reader事件可以完成按需拉流
@ -237,6 +243,8 @@ forbidCacheSuffix=
forwarded_ip_header=
#默认允许所有跨域请求
allow_cross_domains=1
#允许访问http api和http文件索引的ip地址范围白名单置空情况下不做限制
allow_ip_range=127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255
[multicast]
#rtp组播截止组播ip地址
@ -266,8 +274,6 @@ handshakeSecond=15
#rtmp超时时间如果该时间内未收到客户端的数据
#或者tcp发送缓存超过这个时间则会断开连接单位秒
keepAliveSecond=15
#在接收rtmp推流时是否重新生成时间戳(很多推流器的时间戳着实很烂)
modifyStamp=0
#rtmp服务器监听端口
port=1935
#rtmps服务器监听地址

View File

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

View File

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

View File

@ -60,4 +60,4 @@ RUN apt-get update && \
WORKDIR /opt/media/bin/
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
ENV PATH /opt/media/bin:$PATH
CMD MediaServer
CMD ["MediaServer"]

View File

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

View File

@ -60,4 +60,4 @@ RUN apt-get update && \
WORKDIR /opt/media/bin/
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
ENV PATH /opt/media/bin:$PATH
CMD MediaServer
CMD ["MediaServer"]

View File

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

View File

@ -25,7 +25,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -51,7 +51,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -77,7 +77,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -103,7 +103,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -129,7 +129,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -155,7 +155,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "api.apiDebug",
@ -186,7 +186,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -212,7 +212,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -262,7 +262,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -314,7 +314,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -366,7 +366,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "local_port",
@ -404,7 +404,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "id",
@ -435,7 +435,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "local_port",
@ -473,7 +473,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -516,7 +516,13 @@
{
"key": "enable_hls",
"value": null,
"description": "是否转hls",
"description": "是否转hls-ts",
"disabled": true
},
{
"key": "enable_hls_fmp4",
"value": null,
"description": "是否转hls-fmp4",
"disabled": true
},
{
@ -584,6 +590,12 @@
"value": null,
"description": "是否修改原始时间戳默认值2取值范围0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
"disabled": true
},
{
"key": "auto_close",
"value": null,
"description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)",
"disabled": true
}
]
}
@ -609,7 +621,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "key",
@ -640,7 +652,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -709,7 +721,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "key",
@ -740,7 +752,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "src_url",
@ -797,7 +809,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "key",
@ -827,7 +839,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -873,7 +885,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -900,6 +912,56 @@
},
"response": []
},
{
"name": "广播webrtc datachannel消息(broadcastMessage)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/broadcastMessage?secret={{ZLMediaKit_secret}}&schema=rtsp&vhost={{defaultVhost}}&app=live&stream=test&msg=Hello zlmediakit123",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"broadcastMessage"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
"value": "rtsp",
"description": "协议,例如 rtsp或rtmp目前仅支持rtsp协议"
},
{
"key": "vhost",
"value": "{{defaultVhost}}",
"description": "虚拟主机例如__defaultVhost__"
},
{
"key": "app",
"value": "live",
"description": "应用名,例如 live"
},
{
"key": "stream",
"value": "test",
"description": "流id例如 test"
},
{
"key": "msg",
"value": "Hello ZLMediakit"
}
]
}
},
"response": []
},
{
"name": "获取流信息(getMediaInfo)",
"request": {
@ -919,7 +981,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "schema",
@ -965,7 +1027,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1016,7 +1078,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1062,7 +1124,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "type",
@ -1120,7 +1182,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1166,7 +1228,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1212,7 +1274,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "type",
@ -1258,7 +1320,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "type",
@ -1304,7 +1366,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "url",
@ -1345,7 +1407,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1376,7 +1438,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "port",
@ -1435,7 +1497,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "dst_url",
@ -1476,7 +1538,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1507,7 +1569,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1543,7 +1605,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1574,7 +1636,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "stream_id",
@ -1605,7 +1667,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
}
]
}
@ -1631,7 +1693,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1734,7 +1796,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
@ -1822,7 +1884,7 @@
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)如果操作ip是127.0.0.1,则不需要此参数"
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",

View File

@ -34,7 +34,7 @@ if(ENABLE_SERVER_LIB)
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_link_libraries(MediaServer
PRIVATE ${MK_LINK_LIBRARIES})
update_cached(MK_LINK_LIBRARIES MediaServer)
update_cached_list(MK_LINK_LIBRARIES MediaServer)
return()
endif()

View File

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

View File

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

View File

@ -126,6 +126,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
exit(0);
});
signal(SIGTERM,[](int) {
WarnL << "收到主动退出信号,关闭父进程与子进程";
kill(pid, SIGINT);
exit(0);
});
do {
int status = 0;
if (waitpid(pid, &status, 0) >= 0) {

View File

@ -247,7 +247,7 @@ static inline void addHttpListener(){
size = body->remainSize();
}
LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__);
LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__);
log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n";
log << "# header:\r\n";
@ -537,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
if (s_proxyMap.find(key) != s_proxyMap.end()) {
//已经在拉流了
cb(SockException(Err_success), key);
cb(SockException(Err_other, "This stream already exists"), key);
return;
}
//添加拉流代理
@ -588,7 +588,8 @@ void installWebApi() {
//获取线程负载
//测试url http://127.0.0.1/index/api/getThreadsLoad
api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){
api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
Value val;
auto vec = EventPollerPool::Instance().getExecutorLoad();
@ -606,7 +607,8 @@ void installWebApi() {
//获取后台工作线程负载
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
Value val;
auto vec = WorkThreadPool::Instance().getExecutorLoad();
@ -652,6 +654,10 @@ void installWebApi() {
continue;
#endif
}
if (pr.first == FFmpeg::kBin) {
WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api.";
continue;
}
if (ini[pr.first] == pr.second) {
continue;
}
@ -660,7 +666,7 @@ void installWebApi() {
++changed;
}
if (changed > 0) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
ini.dumpFile(g_ini_file);
}
val["changed"] = changed;
@ -789,25 +795,40 @@ void installWebApi() {
throw ApiRetException("can not find the stream", API::NotFound);
}
src->getPlayerList(
[=](const std::list<std::shared_ptr<void>> &info_list) mutable {
[=](const std::list<toolkit::Any> &info_list) mutable {
val["code"] = API::Success;
auto &data = val["data"];
data = Value(arrayValue);
for (auto &info : info_list) {
auto obj = static_pointer_cast<Value>(info);
data.append(std::move(*obj));
auto &obj = info.get<Value>();
data.append(std::move(obj));
}
invoker(200, headerOut, val.toStyledString());
},
[](std::shared_ptr<void> &&info) -> std::shared_ptr<void> {
[](toolkit::Any &&info) -> toolkit::Any {
auto obj = std::make_shared<Value>();
auto session = static_pointer_cast<Session>(info);
fillSockInfo(*obj, session.get());
(*obj)["typeid"] = toolkit::demangle(typeid(*session).name());
return obj;
auto &sock = info.get<SockInfo>();
fillSockInfo(*obj, &sock);
(*obj)["typeid"] = toolkit::demangle(typeid(sock).name());
toolkit::Any ret;
ret.set(obj);
return ret;
});
});
api_regist("/index/api/broadcastMessage", [](API_ARGS_MAP) {
CHECK_SECRET();
CHECK_ARGS("schema", "vhost", "app", "stream", "msg");
auto src = MediaSource::find(allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]);
if (!src) {
throw ApiRetException("can not find the stream", API::NotFound);
}
Any any;
Buffer::Ptr buffer = std::make_shared<BufferLikeString>(allArgs["msg"]);
any.set(std::move(buffer));
src->broadcastMessage(any);
});
//测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){
CHECK_SECRET();

View File

@ -44,6 +44,8 @@ typedef enum {
OtherFailed = -1,//业务代码执行失败,
Success = 0//执行成功
} ApiErr;
extern const std::string kSecret;
}//namespace API
class ApiRetException: public std::runtime_error {
@ -219,14 +221,19 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) {
throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
}
//检查http参数中是否附带secret密钥的宏127.0.0.1的ip不检查密钥
// 检查http参数中是否附带secret密钥的宏127.0.0.1的ip不检查密钥
// 同时检测是否在ip白名单内
#define CHECK_SECRET() \
if(sender.get_peer_ip() != "127.0.0.1"){ \
do { \
auto ip = sender.get_peer_ip(); \
if (!HttpFileManager::isIPAllowed(ip)) { \
throw AuthException("Your ip is not allowed to access the service."); \
} \
CHECK_ARGS("secret"); \
if(api_secret != allArgs["secret"]){ \
if (api_secret != allArgs["secret"]) { \
throw AuthException("secret错误"); \
} \
}
} while(false);
void installWebApi();
void unInstallWebApi();

View File

@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report";
const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm";
const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth";
const string kOnStreamChanged = HOOK_FIELD "on_stream_changed";
const string kStreamChangedSchemas = HOOK_FIELD "stream_changed_schemas";
const string kOnStreamNotFound = HOOK_FIELD "on_stream_not_found";
const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4";
const string kOnRecordTs = HOOK_FIELD "on_record_ts";
@ -48,7 +49,6 @@ const string kOnServerExited = HOOK_FIELD "on_server_exited";
const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive";
const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped";
const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout";
const string kAdminParams = HOOK_FIELD "admin_params";
const string kAliveInterval = HOOK_FIELD "alive_interval";
const string kRetry = HOOK_FIELD "retry";
const string kRetryDelay = HOOK_FIELD "retry_delay";
@ -74,10 +74,10 @@ static onceToken token([]() {
mINI::Instance()[kOnServerKeepalive] = "";
mINI::Instance()[kOnSendRtpStopped] = "";
mINI::Instance()[kOnRtpServerTimeout] = "";
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
mINI::Instance()[kAliveInterval] = 30.0;
mINI::Instance()[kRetry] = 1;
mINI::Instance()[kRetryDelay] = 3.0;
mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4";
});
} // namespace Hook
@ -166,12 +166,16 @@ string getVhost(const HttpArgs &value) {
return val != value.end() ? val->second : "";
}
static atomic<uint64_t> s_hook_index { 0 };
void do_http_hook(const string &url, const ArgsType &body, const function<void(const Value &, const string &)> &func, uint32_t retry) {
GET_CONFIG(string, mediaServerId, General::kMediaServerId);
GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
GET_CONFIG(float, retry_delay, Hook::kRetryDelay);
const_cast<ArgsType &>(body)["mediaServerId"] = mediaServerId;
const_cast<ArgsType &>(body)["hook_index"] = s_hook_index++;
auto requester = std::make_shared<HttpRequester>();
requester->setMethod("POST");
auto bodyStr = to_string(body);
@ -331,11 +335,10 @@ static mINI jsonToMini(const Value &obj) {
void installWebHook() {
GET_CONFIG(bool, hook_enable, Hook::kEnable);
GET_CONFIG(string, hook_adminparams, Hook::kAdminParams);
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
if (!hook_enable || args.param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_publish.empty()) {
invoker("", ProtocolOption());
return;
}
@ -360,7 +363,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
GET_CONFIG(string, hook_play, Hook::kOnPlay);
if (!hook_enable || args.param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_play.empty()) {
invoker("");
return;
}
@ -374,7 +377,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
if (!hook_enable || args.param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_flowreport.empty()) {
return;
}
auto body = make_json(args);
@ -393,7 +396,7 @@ void installWebHook() {
// 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm);
if (!hook_enable || args.param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_rtsp_realm.empty()) {
// 无需认证
invoker("");
return;
@ -441,10 +444,26 @@ void installWebHook() {
// 监听rtsp、rtmp源注册或注销事件
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged);
if (!hook_enable || hook_stream_chaned.empty()) {
GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged);
if (!hook_enable || hook_stream_changed.empty()) {
return;
}
GET_CONFIG_FUNC(std::set<std::string>, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) {
std::set<std::string> ret;
auto vec = split(str, "/");
for (auto &schema : vec) {
trim(schema);
if (!schema.empty()) {
ret.emplace(schema);
}
}
return ret;
});
if (!stream_changed_set.empty() && stream_changed_set.find(sender.getSchema()) == stream_changed_set.end()) {
// 该协议注册注销事件被忽略
return;
}
ArgsType body;
if (bRegist) {
body = makeMediaSourceJson(sender);
@ -455,7 +474,7 @@ void installWebHook() {
body["regist"] = bRegist;
}
// 执行hook
do_http_hook(hook_stream_chaned, body, nullptr);
do_http_hook(hook_stream_changed, body, nullptr);
});
GET_CONFIG_FUNC(vector<string>, origin_urls, Cluster::kOriginUrl, [](const string &str) {
@ -542,7 +561,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) {
GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin);
if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || hook_shell_login.empty()) {
invoker("");
return;
}
@ -585,7 +604,7 @@ void installWebHook() {
});
});
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) {
GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped);
if (!hook_enable || hook_send_rtp_stopped.empty()) {
return;
@ -620,15 +639,14 @@ void installWebHook() {
// 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) {
GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess);
if (sender.get_peer_ip() == "127.0.0.1" || parser.params() == hook_adminparams) {
// 如果是本机或超级管理员访问那么不做访问鉴权权限有效期1个小时
invoker("", "", 60 * 60);
return;
}
if (!hook_enable || hook_http_access.empty()) {
// 未开启http文件访问鉴权那么允许访问但是每次访问都要鉴权
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
invoker("", "", 0);
if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) {
invoker("Your ip is not allowed to access the service.", "", 0);
} else {
invoker("", "", 0);
}
return;
}
@ -656,7 +674,7 @@ void installWebHook() {
});
});
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) {
GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout);
if (!hook_enable || rtp_server_timeout.empty()) {
return;

View File

@ -351,6 +351,14 @@ int start_main(int argc,char *argv[]) {
#endif //defined(ENABLE_SRT)
try {
auto &secret = mINI::Instance()[API::kSecret];
if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) {
// 使用默认secret被禁止启动
secret = makeRandStr(32, true);
mINI::Instance().dumpFile(g_ini_file);
WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret
<< ", saved config file: " << g_ini_file;
}
//rtsp服务器端口默认554
if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort); }
//rtsps服务器端口默认322
@ -388,8 +396,7 @@ int start_main(int argc,char *argv[]) {
#endif//defined(ENABLE_SRT)
} catch (std::exception &ex) {
WarnL << "端口占用或无权限:" << ex.what();
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!";
ErrorL << "Start server failed: " << ex.what();
sleep(1);
#if !defined(_WIN32)
if (pid != getpid() && kill_parent_if_failed) {
@ -413,6 +420,12 @@ int start_main(int argc,char *argv[]) {
sem.post();
}); // 设置退出信号
signal(SIGTERM,[](int) {
WarnL << "SIGTERM:exit";
signal(SIGTERM, SIG_IGN);
sem.post();
});
#if !defined(_WIN32)
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
#endif

View File

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

View File

@ -15,8 +15,10 @@
#include "MediaSource.h"
#include "Common/config.h"
#include "Common/Parser.h"
#include "Common/MultiMediaSourceMuxer.h"
#include "Record/MP4Reader.h"
#include "PacketCache.h"
using namespace std;
using namespace toolkit;
@ -56,9 +58,11 @@ ProtocolOption::ProtocolOption() {
GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp);
GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio);
GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio);
GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose);
GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls);
GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4);
GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4);
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() {
modify_stamp = s_modify_stamp;
enable_audio = s_enabel_audio;
add_mute_audio = s_add_mute_audio;
auto_close = s_auto_close;
continue_push_ms = s_continue_push_ms;
enable_hls = s_enable_hls;
enable_hls_fmp4 = s_enable_hls_fmp4;
enable_mp4 = s_enable_mp4;
enable_rtsp = s_enable_rtsp;
enable_rtmp = s_enable_rtmp;
@ -170,20 +176,8 @@ void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
_listener = listener;
}
std::weak_ptr<MediaSourceEvent> MediaSource::getListener(bool next) const{
if (!next) {
return _listener;
}
auto listener = dynamic_pointer_cast<MediaSourceEventInterceptor>(_listener.lock());
if (!listener) {
//不是MediaSourceEventInterceptor对象或者对象已经销毁
return _listener;
}
//获取被拦截的对象
auto next_obj = listener->getDelegate();
//有则返回之
return next_obj ? next_obj : _listener;
std::weak_ptr<MediaSourceEvent> MediaSource::getListener() const {
return _listener;
}
int MediaSource::totalReaderCount(){
@ -275,6 +269,11 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() {
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl());
}
std::shared_ptr<MultiMediaSourceMuxer> MediaSource::getMuxer() {
auto listener = _listener.lock();
return listener ? listener->getMuxer(*this) : nullptr;
}
void MediaSource::onReaderChanged(int size) {
try {
weak_ptr<MediaSource> weak_self = shared_from_this();
@ -470,7 +469,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &s
});
};
//广播未找到流,此时可以立即去拉流,这样还来得及
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), close_player);
NOTICE_EMIT(BroadcastNotFoundStreamArgs, Broadcast::kBroadcastNotFoundStream, info, *session, close_player);
}
void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr<Session> &session, const function<void (const Ptr &)> &cb) {
@ -500,7 +499,7 @@ void MediaSource::emitEvent(bool regist){
listener->onRegist(*this, regist);
}
//触发广播
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this);
NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this);
InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl();
}
@ -663,8 +662,15 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
}
if (!is_mp4_vod) {
//直播时触发无人观看事件,让开发者自行选择是否关闭
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
auto muxer = strong_sender->getMuxer();
if (muxer && muxer->getOption().auto_close) {
// 此流被标记为无人观看自动关闭流
WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl();
strong_sender->close(false);
} else {
// 直播时触发无人观看事件,让开发者自行选择是否关闭
NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender);
}
} else {
//这个是mp4点播我们自动关闭
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
@ -778,6 +784,11 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed");
}
std::shared_ptr<MultiMediaSourceMuxer> MediaSourceEventInterceptor::getMuxer(MediaSource &sender) {
auto listener = _listener.lock();
return listener ? listener->getMuxer(sender) : nullptr;
}
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
auto listener = _listener.lock();
if (!listener) {

View File

@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t {
std::string getOriginTypeString(MediaOriginType type);
class MediaSource;
class MultiMediaSourceMuxer;
class MediaSourceEvent {
public:
friend class MediaSource;
@ -88,6 +89,8 @@ public:
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }
// 获取所有track相关信息
virtual std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
// 获取MultiMediaSourceMuxer对象
virtual std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) { return nullptr; }
class SendRtpArgs {
public:
@ -148,11 +151,18 @@ public:
bool enable_audio;
//添加静音音频,在关闭音频时,此开关无效
bool add_mute_audio;
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
// 此配置置1时此流如果无人观看将不触发on_none_reader hook回调
// 而是将直接关闭流
bool auto_close;
//断连续推延时,单位毫秒,默认采用配置文件
uint32_t continue_push_ms;
//是否开启转换为hls
//是否开启转换为hls(mpegts)
bool enable_hls;
//是否开启转换为hls(fmp4)
bool enable_hls_fmp4;
//是否开启MP4录制
bool enable_mp4;
//是否开启转换为rtsp/webrtc
@ -185,15 +195,20 @@ public:
//hls录制保存路径
std::string hls_save_path;
// 支持通过on_publish返回值替换stream_id
std::string stream_replace;
template <typename MAP>
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
GET_OPT_VALUE(modify_stamp);
GET_OPT_VALUE(enable_audio);
GET_OPT_VALUE(add_mute_audio);
GET_OPT_VALUE(auto_close);
GET_OPT_VALUE(continue_push_ms);
GET_OPT_VALUE(enable_hls);
GET_OPT_VALUE(enable_hls_fmp4);
GET_OPT_VALUE(enable_mp4);
GET_OPT_VALUE(enable_rtsp);
GET_OPT_VALUE(enable_rtmp);
@ -211,6 +226,7 @@ public:
GET_OPT_VALUE(mp4_save_path);
GET_OPT_VALUE(hls_save_path);
GET_OPT_VALUE(stream_replace);
}
private:
@ -250,6 +266,7 @@ public:
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
float getLossRate(MediaSource &sender, TrackType type) override;
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
private:
std::weak_ptr<MediaSourceEvent> _listener;
@ -323,19 +340,21 @@ public:
// 设置监听者
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
// 获取监听者
std::weak_ptr<MediaSourceEvent> getListener(bool next = false) const;
std::weak_ptr<MediaSourceEvent> getListener() const;
// 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数
virtual int readerCount() = 0;
// 观看者个数,包括(hls/rtsp/rtmp)
virtual int totalReaderCount();
// 获取播放器列表
virtual void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) {
virtual void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) {
assert(cb);
cb(std::list<std::shared_ptr<void>>());
cb(std::list<toolkit::Any>());
}
virtual bool broadcastMessage(const toolkit::Any &data) { return false; }
// 获取媒体源类型
MediaOriginType getOriginType() const;
// 获取媒体源url或者文件路径
@ -365,6 +384,8 @@ public:
float getLossRate(mediakit::TrackType type);
// 获取所在线程
toolkit::EventPoller::Ptr getOwnerPoller();
// 获取MultiMediaSourceMuxer对象
std::shared_ptr<MultiMediaSourceMuxer> getMuxer();
////////////////static方法查找或生成MediaSource////////////////

View File

@ -37,6 +37,7 @@ static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, con
for (auto &track : tracks) {
recorder->addTrack(track);
}
recorder->addTrackCompleted();
return recorder;
}
@ -70,6 +71,14 @@ static string getTrackInfoStr(const TrackSource *track_src){
return std::move(codec_info);
}
const ProtocolOption &MultiMediaSourceMuxer::getOption() const {
return _option;
}
const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const {
return _tuple;
}
std::string MultiMediaSourceMuxer::shortUrl() const {
auto ret = getOriginUrl(MediaSource::NullMediaSource());
if (!ret.empty()) {
@ -79,6 +88,10 @@ std::string MultiMediaSourceMuxer::shortUrl() const {
}
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) {
if (!option.stream_replace.empty()) {
// 支持在on_publish hook中替换stream_id
_tuple.stream = option.stream_replace;
}
_poller = EventPollerPool::Instance().getPoller();
_create_in_poller = _poller->isCurrentThread();
_option = option;
@ -97,17 +110,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_
if (option.enable_hls) {
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option));
}
if (option.enable_hls_fmp4) {
_hls_fmp4 = dynamic_pointer_cast<HlsFMP4Recorder>(Recorder::createRecorder(Recorder::type_hls_fmp4, _tuple, option));
}
if (option.enable_mp4) {
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
}
if (option.enable_ts) {
_ts = std::make_shared<TSMediaSourceMuxer>(_tuple, option);
_ts = dynamic_pointer_cast<TSMediaSourceMuxer>(Recorder::createRecorder(Recorder::type_ts, _tuple, option));
}
#if defined(ENABLE_MP4)
if (option.enable_fmp4) {
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(_tuple, option);
_fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option));
}
#endif
//音频相关设置
enableAudio(option.enable_audio);
@ -128,14 +142,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven
if (_ts) {
_ts->setListener(self);
}
#if defined(ENABLE_MP4)
if (_fmp4) {
_fmp4->setListener(self);
}
#endif
auto hls = _hls;
if (hls) {
hls->setListener(self);
if (_hls_fmp4) {
_hls_fmp4->setListener(self);
}
if (_hls) {
_hls->setListener(self);
}
}
@ -148,15 +162,13 @@ std::weak_ptr<MultiMediaSourceMuxer::Listener> MultiMediaSourceMuxer::getTrackLi
}
int MultiMediaSourceMuxer::totalReaderCount() const {
auto hls = _hls;
return (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) +
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->readerCount() : 0) +
#endif
(_mp4 ? _option.mp4_as_player : 0) +
(hls ? hls->readerCount() : 0) +
(_hls ? _hls->readerCount() : 0) +
(_hls_fmp4 ? _hls_fmp4->readerCount() : 0) +
(_ring ? _ring->readerCount() : 0);
}
@ -184,6 +196,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
//此函数可能跨线程调用
bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread(), "Can only call setupRecord in it's owner poller");
onceToken token(nullptr, [&]() {
if (_option.mp4_as_player && type == Recorder::type_mp4) {
//开启关闭mp4录制触发观看人数变化相关事件
@ -219,19 +232,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
}
return true;
}
case Recorder::type_hls_fmp4: {
if (start && !_hls_fmp4) {
//开始录制
_option.hls_save_path = custom_path;
auto hls = dynamic_pointer_cast<HlsFMP4Recorder>(makeRecorder(sender, getTracks(), type, _option));
if (hls) {
//设置HlsMediaSource的事件监听器
hls->setListener(shared_from_this());
}
_hls_fmp4 = hls;
} else if (!start && _hls_fmp4) {
//停止录制
_hls_fmp4 = nullptr;
}
return true;
}
case Recorder::type_fmp4: {
if (start && !_fmp4) {
auto fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(makeRecorder(sender, getTracks(), type, _option));
if (fmp4) {
fmp4->setListener(shared_from_this());
}
_fmp4 = fmp4;
} else if (!start && _fmp4) {
_fmp4 = nullptr;
}
return true;
}
case Recorder::type_ts: {
if (start && !_ts) {
auto ts = dynamic_pointer_cast<TSMediaSourceMuxer>(makeRecorder(sender, getTracks(), type, _option));
if (ts) {
ts->setListener(shared_from_this());
}
_ts = ts;
} else if (!start && _ts) {
_ts = nullptr;
}
return true;
}
default : return false;
}
}
//此函数可能跨线程调用
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
switch (type){
case Recorder::type_hls :
return !!_hls;
case Recorder::type_mp4 :
return !!_mp4;
default:
return false;
switch (type) {
case Recorder::type_hls: return !!_hls;
case Recorder::type_mp4: return !!_mp4;
case Recorder::type_hls_fmp4: return !!_hls_fmp4;
case Recorder::type_fmp4: return !!_fmp4;
case Recorder::type_ts: return !!_ts;
default: return false;
}
}
@ -263,7 +316,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex;
strong_self->_rtp_sender.erase(ssrc);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
});
}
});
@ -320,6 +373,10 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
}
}
std::shared_ptr<MultiMediaSourceMuxer> MultiMediaSourceMuxer::getMuxer(MediaSource &sender) {
return shared_from_this();
}
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
bool ret = false;
if (_rtmp) {
@ -331,20 +388,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
if (_ts) {
ret = _ts->addTrack(track) ? true : ret;
}
#if defined(ENABLE_MP4)
if (_fmp4) {
ret = _fmp4->addTrack(track) ? true : ret;
}
#endif
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls;
if (hls) {
ret = hls->addTrack(track) ? true : ret;
if (_hls) {
ret = _hls->addTrack(track) ? true : ret;
}
auto mp4 = _mp4;
if (mp4) {
ret = mp4->addTrack(track) ? true : ret;
if (_hls_fmp4) {
ret = _hls_fmp4->addTrack(track) ? true : ret;
}
if (_mp4) {
ret = _mp4->addTrack(track) ? true : ret;
}
return ret;
}
@ -354,16 +408,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
setMediaListener(getDelegate());
if (_rtmp) {
_rtmp->onAllTrackReady();
_rtmp->addTrackCompleted();
}
if (_rtsp) {
_rtsp->onAllTrackReady();
_rtsp->addTrackCompleted();
}
if (_ts) {
_ts->addTrackCompleted();
}
if (_mp4) {
_mp4->addTrackCompleted();
}
#if defined(ENABLE_MP4)
if (_fmp4) {
_fmp4->onAllTrackReady();
_fmp4->addTrackCompleted();
}
#endif
if (_hls) {
_hls->addTrackCompleted();
}
if (_hls_fmp4) {
_hls_fmp4->addTrackCompleted();
}
auto listener = _track_listener.lock();
if (listener) {
listener->onAllTrackReady();
@ -411,21 +476,17 @@ void MultiMediaSourceMuxer::resetTracks() {
if (_ts) {
_ts->resetTracks();
}
#if defined(ENABLE_MP4)
if (_fmp4) {
_fmp4->resetTracks();
}
#endif
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls;
if (hls) {
hls->resetTracks();
if (_hls_fmp4) {
_hls_fmp4->resetTracks();
}
auto mp4 = _mp4;
if (mp4) {
mp4->resetTracks();
if (_hls) {
_hls->resetTracks();
}
if (_mp4) {
_mp4->resetTracks();
}
}
@ -447,23 +508,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
ret = _ts->inputFrame(frame) ? true : ret;
}
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
auto hls = _hls;
if (hls) {
ret = hls->inputFrame(frame) ? true : ret;
}
auto mp4 = _mp4;
if (mp4) {
ret = mp4->inputFrame(frame) ? true : ret;
if (_hls) {
ret = _hls->inputFrame(frame) ? true : ret;
}
#if defined(ENABLE_MP4)
if (_hls_fmp4) {
ret = _hls_fmp4->inputFrame(frame) ? true : ret;
}
if (_mp4) {
ret = _mp4->inputFrame(frame) ? true : ret;
}
if (_fmp4) {
ret = _fmp4->inputFrame(frame) ? true : ret;
}
#endif
if (_ring) {
if (frame->getTrackType() == TrackVideo) {
// 视频时遇到第一帧配置帧或关键帧则标记为gop开始处
@ -485,15 +543,14 @@ bool MultiMediaSourceMuxer::isEnabled(){
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
//无人观看时,每次检查是否真的无人观看
//有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能)
auto hls = _hls;
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
(_rtsp ? _rtsp->isEnabled() : false) ||
(_ts ? _ts->isEnabled() : false) ||
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->isEnabled() : false) ||
#endif
(_ring ? (bool)_ring->readerCount() : false) ||
(hls ? hls->isEnabled() : false) || _mp4;
(_hls ? _hls->isEnabled() : false) ||
(_hls_fmp4 ? _hls_fmp4->isEnabled() : false) ||
_mp4;
if (_is_enable) {
//无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍所以刷新计数器无意义且浪费cpu

View File

@ -131,9 +131,13 @@ public:
*/
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
const MediaTuple& getMediaTuple() const {
return _tuple;
}
/**
*
*/
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
const ProtocolOption &getOption() const;
const MediaTuple &getMediaTuple() const;
std::string shortUrl() const;
protected:
@ -169,18 +173,14 @@ private:
toolkit::Ticker _last_check;
Stamp _stamp[2];
std::weak_ptr<Listener> _track_listener;
#if defined(ENABLE_RTPPROXY)
std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender;
#endif //ENABLE_RTPPROXY
#if defined(ENABLE_MP4)
FMP4MediaSourceMuxer::Ptr _fmp4;
#endif
RtmpMediaSourceMuxer::Ptr _rtmp;
RtspMediaSourceMuxer::Ptr _rtsp;
TSMediaSourceMuxer::Ptr _ts;
MediaSinkInterface::Ptr _mp4;
HlsRecorder::Ptr _hls;
HlsFMP4Recorder::Ptr _hls_fmp4;
toolkit::EventPoller::Ptr _poller;
RingType::Ptr _ring;

View File

@ -48,12 +48,17 @@ void Parser::parse(const char *buf, size_t size) {
clear();
auto ptr = buf;
while (true) {
auto next_line = strstr(ptr, "\r\n");
CHECK(next_line);
auto next_line = strchr(ptr, '\n');
auto offset = 1;
CHECK(next_line && next_line > ptr);
if (*(next_line - 1) == '\r') {
next_line -= 1;
offset = 2;
}
if (ptr == buf) {
auto blank = strchr(ptr, ' ');
CHECK(blank > ptr && blank < next_line);
_method = std::string(ptr, blank);
_method = std::string(ptr, blank - ptr);
auto next_blank = strchr(blank + 1, ' ');
CHECK(next_blank && next_blank < next_line);
_url.assign(blank + 1, next_blank);
@ -67,7 +72,7 @@ void Parser::parse(const char *buf, size_t size) {
} else {
auto pos = strchr(ptr, ':');
CHECK(pos > ptr && pos < next_line);
std::string key { ptr, pos };
std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
std::string value;
if (pos[1] == ' ') {
value.assign(pos + 2, next_line);
@ -76,7 +81,7 @@ void Parser::parse(const char *buf, size_t size) {
}
_headers.emplace_force(trim(std::move(key)), trim(std::move(value)));
}
ptr = next_line + 2;
ptr = next_line + offset;
if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕
_content.assign(ptr + 2, buf + size);
break;
@ -320,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
host = url.substr(0, pos);
checkHost(host);
}
#if 0
//测试代码
static onceToken token([](){

View File

@ -31,7 +31,7 @@ bool loadIniConfig(const char *ini_path) {
}
try {
mINI::Instance().parseFile(ini);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
return true;
} catch (std::exception &) {
InfoL << "dump ini file to:" << ini;
@ -99,9 +99,11 @@ namespace Protocol {
const string kModifyStamp = PROTOCOL_FIELD "modify_stamp";
const string kEnableAudio = PROTOCOL_FIELD "enable_audio";
const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio";
const string kAutoClose = PROTOCOL_FIELD "auto_close";
const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms";
const string kEnableHls = PROTOCOL_FIELD "enable_hls";
const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4";
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp";
@ -125,8 +127,10 @@ static onceToken token([]() {
mINI::Instance()[kEnableAudio] = 1;
mINI::Instance()[kAddMuteAudio] = 1;
mINI::Instance()[kContinuePushMS] = 15000;
mINI::Instance()[kAutoClose] = 0;
mINI::Instance()[kEnableHls] = 1;
mINI::Instance()[kEnableHlsFmp4] = 0;
mINI::Instance()[kEnableMP4] = 0;
mINI::Instance()[kEnableRtsp] = 1;
mINI::Instance()[kEnableRtmp] = 1;
@ -161,6 +165,7 @@ const string kDirMenu = HTTP_FIELD "dirMenu";
const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix";
const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header";
const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains";
const string kAllowIPRange = HTTP_FIELD "allow_ip_range";
static onceToken token([]() {
mINI::Instance()[kSendBufSize] = 64 * 1024;
@ -189,6 +194,7 @@ static onceToken token([]() {
mINI::Instance()[kForbidCacheSuffix] = "";
mINI::Instance()[kForwardedIpHeader] = "";
mINI::Instance()[kAllowCrossDomains] = 1;
mINI::Instance()[kAllowIPRange] = "127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255";
});
} // namespace Http
@ -225,12 +231,10 @@ static onceToken token([]() {
////////////RTMP服务器配置///////////
namespace Rtmp {
#define RTMP_FIELD "rtmp."
const string kModifyStamp = RTMP_FIELD "modifyStamp";
const string kHandshakeSecond = RTMP_FIELD "handshakeSecond";
const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond";
static onceToken token([]() {
mINI::Instance()[kModifyStamp] = false;
mINI::Instance()[kHandshakeSecond] = 15;
mINI::Instance()[kKeepAliveSecond] = 15;
});

View File

@ -99,7 +99,7 @@ extern const std::string kBroadcastStreamNoneReader;
// rtp推流被动停止时触发
extern const std::string kBroadcastSendRtpStopped;
#define BroadcastSendRtpStopped MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
#define BroadcastSendRtpStoppedArgs MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
// 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播
extern const std::string kBroadcastReloadConfig;
@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig;
// rtp server 超时
extern const std::string KBroadcastRtpServerTimeout;
#define BroadcastRtpServerTimeout uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
#define ReloadConfigTag ((void *)(0xFF))
#define RELOAD_KEY(arg, key) \
@ -190,11 +190,17 @@ extern const std::string kModifyStamp;
extern const std::string kEnableAudio;
//添加静音音频,在关闭音频时,此开关无效
extern const std::string kAddMuteAudio;
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
// 此配置置1时此流如果无人观看将不触发on_none_reader hook回调
// 而是将直接关闭流
extern const std::string kAutoClose;
//断连续推延时,单位毫秒,默认采用配置文件
extern const std::string kContinuePushMS;
//是否开启转换为hls
//是否开启转换为hls(mpegts)
extern const std::string kEnableHls;
//是否开启转换为hls(fmp4)
extern const std::string kEnableHlsFmp4;
//是否开启MP4录制
extern const std::string kEnableMP4;
//是否开启转换为rtsp/webrtc
@ -248,6 +254,8 @@ extern const std::string kForbidCacheSuffix;
extern const std::string kForwardedIpHeader;
// 是否允许所有跨域请求
extern const std::string kAllowCrossDomains;
// 允许访问http api和http文件索引的ip地址范围白名单置空情况下不做限制
extern const std::string kAllowIPRange;
} // namespace Http
////////////SHELL配置///////////

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -201,17 +201,20 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){
}
if (val.type() != AMF_NULL) {
auto type_id = val.as_integer();
auto type_id = (RtmpVideoCodec)val.as_integer();
switch (type_id) {
case FLV_CODEC_H264 : return CodecH264;
case FLV_CODEC_H265 : return CodecH265;
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
case RtmpVideoCodec::h264: return CodecH264;
case RtmpVideoCodec::fourcc_hevc:
case RtmpVideoCodec::h265: return CodecH265;
case RtmpVideoCodec::fourcc_av1: return CodecAV1;
case RtmpVideoCodec::fourcc_vp9: return CodecVP9;
default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid;
}
}
return CodecInvalid;
}
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
Track::Ptr Factory::getTrackByCodecId(CodecId codecId, int sample_rate, int channels, int sample_bit) {
switch (codecId){
case CodecH264 : return std::make_shared<H264Track>();
case CodecH265 : return std::make_shared<H265Track>();
@ -243,13 +246,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
}
if (val.type() != AMF_NULL) {
auto type_id = val.as_integer();
auto type_id = (RtmpAudioCodec)val.as_integer();
switch (type_id) {
case FLV_CODEC_AAC : return CodecAAC;
case FLV_CODEC_G711A : return CodecG711A;
case FLV_CODEC_G711U : return CodecG711U;
case FLV_CODEC_OPUS : return CodecOpus;
default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid;
case RtmpAudioCodec::aac : return CodecAAC;
case RtmpAudioCodec::g711a : return CodecG711A;
case RtmpAudioCodec::g711u : return CodecG711U;
case RtmpAudioCodec::opus : return CodecOpus;
default : WarnL << "暂不支持该音频Amf:" << (int)type_id; return CodecInvalid;
}
}
@ -291,13 +294,13 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
}
AMFValue Factory::getAmfByCodecId(CodecId codecId) {
switch (codecId){
case CodecAAC: return AMFValue(FLV_CODEC_AAC);
case CodecH264: return AMFValue(FLV_CODEC_H264);
case CodecH265: return AMFValue(FLV_CODEC_H265);
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
switch (codecId) {
case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac);
case CodecH264: return AMFValue((int)RtmpVideoCodec::h264);
case CodecH265: return AMFValue((int)RtmpVideoCodec::h265);
case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a);
case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u);
case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus);
default: return AMFValue(AMF_NULL);
}
}

View File

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

View File

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

View File

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

View File

@ -13,9 +13,7 @@
namespace mediakit{
#if defined(_WIN32)
#pragma pack(push, 1)
#endif // defined(_WIN32)
class FuFlags {
public:
@ -30,11 +28,9 @@ public:
unsigned end_bit: 1;
unsigned start_bit: 1;
#endif
} PACKED;
};
#if defined(_WIN32)
#pragma pack(pop)
#endif // defined(_WIN32)
H264RtpDecoder::H264RtpDecoder() {
_frame = obtainFrame();

View File

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

View File

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

View File

@ -51,8 +51,8 @@ public:
return _ring;
}
void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) override {
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
_ring->getInfoList(cb, on_change);
}

View File

@ -11,8 +11,6 @@
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#if defined(ENABLE_MP4)
#include "FMP4MediaSource.h"
#include "Record/MP4Muxer.h"
@ -63,7 +61,8 @@ public:
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
}
void onAllTrackReady() {
void addTrackCompleted() override {
MP4MuxerMemory::addTrackCompleted();
_media_src->setInitSegment(getInitSegment());
}
@ -86,5 +85,4 @@ private:
}//namespace mediakit
#endif// defined(ENABLE_MP4)
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H

View File

@ -71,6 +71,11 @@ void HlsPlayer::teardown() {
void HlsPlayer::fetchSegment() {
if (_ts_list.empty()) {
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628
if(!HlsParser::isLive()){
teardown();
return;
}
//播放列表为空那么立即重新下载m3u8文件
_timer.reset();
fetchIndexFile();
@ -121,18 +126,21 @@ void HlsPlayer::fetchSegment() {
WarnL << "Download ts segment " << url << " failed:" << err;
if (err.getErrCode() == Err_timeout) {
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE);
}else{
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple -1 , MIN_TIMEOUT_MULTIPLE);
} else {
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple - 1, MIN_TIMEOUT_MULTIPLE);
}
}
//提前半秒下载好
auto delay = duration - ticker.elapsedTime() / 1000.0f - 0.5;
if (delay <= 0) {
//延时最小10ms
delay = 10;
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628
auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f;
if (delay > 2.0) {
// 提前1秒下载
delay -= 1.0;
} else if (delay <= 0) {
// 延时最小10ms
delay = 0.01;
}
//延时下载下一个切片
strong_self->_timer_ts.reset(new Timer(delay / 1000.0f, [weak_self]() {
// 延时下载下一个切片
strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->fetchSegment();

View File

@ -33,6 +33,7 @@ namespace mediakit {
static int kHlsCookieSecond = 60;
static const string kCookieName = "ZL_COOKIE";
static const string kHlsSuffix = "/hls.m3u8";
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
struct HttpCookieAttachment {
//是否已经查找到过MediaSource
@ -49,6 +50,67 @@ const string &HttpFileManager::getContentType(const char *name) {
return HttpConst::getHttpContentType(name);
}
#ifndef ntohll
static uint64_t ntohll(uint64_t val) {
return (((uint64_t)ntohl(val)) << 32) + ntohl(val >> 32);
}
#endif
static uint64_t get_ip_uint64(const std::string &ip) {
try {
auto storage = SockUtil::make_sockaddr(ip.data(), 0);
if (storage.ss_family == AF_INET) {
return ntohl(reinterpret_cast<uint32_t &>(reinterpret_cast<struct sockaddr_in &>(storage).sin_addr));
}
if (storage.ss_family == AF_INET6) {
return ntohll(reinterpret_cast<uint64_t &>(reinterpret_cast<struct sockaddr_in6 &>(storage).sin6_addr));
}
} catch (std::exception &ex) {
WarnL << ex.what();
}
return 0;
}
bool HttpFileManager::isIPAllowed(const std::string &ip) {
using IPRangs = std::vector<std::pair<uint64_t /*min_ip*/, uint64_t /*max_ip*/>>;
GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs {
IPRangs ret;
auto vec = split(str, ",");
for (auto &item : vec) {
if (trim(item).empty()) {
continue;
}
auto range = split(item, "-");
if (range.size() == 2) {
auto ip_min = get_ip_uint64(trim(range[0]));
auto ip_max = get_ip_uint64(trim(range[1]));
if (ip_min && ip_max) {
ret.emplace_back(ip_min, ip_max);
}
} else if (range.size() == 1) {
auto ip = get_ip_uint64(trim(range[0]));
if (ip) {
ret.emplace_back(ip, ip);
}
} else {
WarnL << "Invalid ip range: " << item;
}
}
return ret;
});
if (allow_ip_range.empty()) {
return true;
}
auto ip_int = get_ip_uint64(ip);
for (auto &range : allow_ip_range) {
if (ip_int >= range.first && ip_int <= range.second) {
return true;
}
}
return false;
}
static string searchIndexFile(const string &dir){
DIR *pDir;
dirent *pDirent;
@ -57,7 +119,7 @@ static string searchIndexFile(const string &dir){
}
set<string> setFile;
while ((pDirent = readdir(pDir)) != NULL) {
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm"};
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
string ret = pDirent->d_name;
closedir(pDir);
@ -188,7 +250,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con
//cookie有效期为kHlsCookieSecond
invoker(err, "", kHlsCookieSecond);
};
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast<SockInfo &>(sender));
bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender);
if (!flag) {
//未开启鉴权,那么允许播放
auth_invoker("");
@ -278,7 +340,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
HttpCookieManager::Instance().delCookie(cookie);
}
bool is_hls = media_info.schema == HLS_SCHEMA;
bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA;
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
info->_identifier = sender.getIdentifier();
@ -320,10 +382,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
return;
}
//事件未被拦截则认为是http下载请求
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast<SockInfo &>(sender));
// 事件未被拦截则认为是http下载请求
bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
if (!flag) {
//此事件无人监听,我们默认都有权限访问
// 此事件无人监听,我们默认都有权限访问
callback("", nullptr);
}
}
@ -355,7 +417,7 @@ static string pathCat(const string &a, const string &b){
* @param cb
*/
static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) {
bool is_hls = end_with(file_path, kHlsSuffix);
bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix);
if (!is_hls && !File::fileExist(file_path.data())) {
//文件不存在且不是hls,那么直接返回404
sendNotFound(cb);
@ -363,8 +425,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
}
if (is_hls) {
// hls那么移除掉后缀获取真实的stream_id并且修改协议为HLS
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
if (end_with(file_path, kHlsSuffix)) {
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
} else {
const_cast<string &>(media_info.schema) = HLS_FMP4_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsFMP4Suffix, "");
}
}
weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this());
@ -457,7 +524,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
});
}
static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){
static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) {
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
GET_CONFIG(string, rootPath, Http::kRootPath);
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
@ -482,7 +549,14 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
}
}
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path);
if (!start_with(ret, http_root)) {
// 访问的http文件不得在http根目录之外
throw std::runtime_error("Attempting to access files outside of the http root directory");
}
// 替换url防止返回的目录索引网页被注入非法内容
const_cast<Parser&>(parser).setUrl("/" + ret.substr(http_root.size()));
NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
return ret;
}
@ -504,11 +578,14 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
if (File::is_dir(file_path.data())) {
auto indexFile = searchIndexFile(file_path);
if (!indexFile.empty()) {
//发现该文件夹下有index文件
// 发现该文件夹下有index文件
file_path = pathCat(file_path, indexFile);
parser.setUrl(pathCat(parser.url(), indexFile));
accessFile(sender, parser, media_info, file_path, cb);
return;
if (!File::is_dir(file_path.data())) {
// 不是文件夹
parser.setUrl(pathCat(parser.url(), indexFile));
accessFile(sender, parser, media_info, file_path, cb);
return;
}
}
string strMenu;
//生成文件夹菜单索引

View File

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

View File

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

View File

@ -271,7 +271,7 @@ static void sendReport() {
}
static toolkit::onceToken s_token([]() {
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPool &pool, size_t &size) {
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) {
// 第一次汇报在程序启动后5分钟
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
sendReport();

View File

@ -170,7 +170,7 @@ void HttpSession::onError(const SockException &err) {
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_total_bytes_usage >= iFlowThreshold * 1024) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, static_cast<SockInfo &>(*this));
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this);
}
return;
}
@ -311,7 +311,7 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix
}
};
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast<SockInfo &>(*this));
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this);
if (!flag) {
// 该事件无人监听,默认不鉴权
onRes("");
@ -338,7 +338,11 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
fmp4_src->pause(false);
_fmp4_reader = fmp4_src->getRing()->attach(getPoller());
_fmp4_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
_fmp4_reader->setGetInfoCB([weak_self]() {
Any ret;
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_fmp4_reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
@ -378,7 +382,11 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
ts_src->pause(false);
_ts_reader = ts_src->getRing()->attach(getPoller());
_ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
_ts_reader->setGetInfoCB([weak_self]() {
Any ret;
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_ts_reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
@ -711,7 +719,7 @@ bool HttpSession::emitHttpEvent(bool doInvoke) {
};
///////////////////广播HTTP事件///////////////////////////
bool consumed = false; // 该事件是否被消费
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, static_cast<SockInfo &>(*this));
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
if (!consumed && doInvoke) {
// 该事件无人消费所以返回404
invoker(404, KeyValue(), HttpBody::Ptr());

View File

@ -8,6 +8,7 @@
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <iomanip>
#include "HlsMaker.h"
#include "Common/config.h"
@ -15,83 +16,76 @@ using namespace std;
namespace mediakit {
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) {
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
_is_fmp4 = is_fmp4;
//最小允许设置为00个切片代表点播
_seg_number = seg_number;
_seg_duration = seg_duration;
_seg_keep = seg_keep;
}
HlsMaker::~HlsMaker() = default;
void HlsMaker::makeIndexFile(bool eof) {
char file_content[1024];
int maxSegmentDuration = 0;
for (auto &tp : _seg_dur_list) {
int dur = std::get<0>(tp);
if (dur > maxSegmentDuration) {
maxSegmentDuration = dur;
}
}
auto index_seq = _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;
if (_seg_number == 0) {
// 录像点播支持时移
snprintf(file_content, sizeof(file_content),
"#EXTM3U\n"
"#EXT-X-PLAYLIST-TYPE:EVENT\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:%u\n"
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
(maxSegmentDuration + 999) / 1000,
sequence);
string index_str;
index_str.reserve(2048);
index_str += "#EXTM3U\n";
index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n");
if (_seg_number == 0) {
index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
} else {
snprintf(file_content, sizeof(file_content),
"#EXTM3U\n"
"#EXT-X-VERSION:3\n"
"#EXT-X-ALLOW-CACHE:NO\n"
"#EXT-X-TARGETDURATION:%u\n"
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
(maxSegmentDuration + 999) / 1000,
sequence);
index_str += "#EXT-X-ALLOW-CACHE:NO\n";
}
index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n";
index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n";
if (_is_fmp4) {
index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n";
}
m3u8.assign(file_content);
stringstream ss;
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());
m3u8.append(file_content);
ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n";
}
index_str += ss.str();
if (eof) {
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
m3u8.append(file_content);
index_str += "#EXT-X-ENDLIST\n";
}
onWriteHls(m3u8);
onWriteHls(index_str);
}
void HlsMaker::inputInitSegment(const char *data, size_t len) {
if (!_is_fmp4) {
throw std::invalid_argument("Only fmp4-hls can input init segment");
}
onWriteInitSegment(data, len);
}
void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
if (data && len) {
if (timestamp < _last_timestamp) {
//时间戳回退了,切片时长重新计时
WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp;
// 时间戳回退了,切片时长重新计时
WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp;
_last_seg_timestamp = _last_timestamp = timestamp;
}
if (is_idr_fast_packet) {
//尝试切片ts
// 尝试切片ts
addNewSegment(timestamp);
}
if (!_last_file_name.empty()) {
//存在切片才写入ts数据
onWriteSegment((char *) data, len);
// 存在切片才写入ts数据
onWriteSegment(data, len);
_last_timestamp = timestamp;
}
} else {
//resetTracks时触发此逻辑
// resetTracks时触发此逻辑
flushLastSegment(false);
}
}
@ -148,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){
makeIndexFile(eof);
}
bool HlsMaker::isLive() {
bool HlsMaker::isLive() const {
return _seg_number != 0;
}
bool HlsMaker::isKeep() {
bool HlsMaker::isKeep() const {
return _seg_keep;
}
bool HlsMaker::isFmp4() const {
return _is_fmp4;
}
void HlsMaker::clear() {
_file_index = 0;
_last_timestamp = 0;

View File

@ -21,12 +21,13 @@ namespace mediakit {
class HlsMaker {
public:
/**
* @param is_fmp4 使fmp4还是mpegts
* @param seg_duration
* @param seg_number
* @param seg_keep
*/
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
virtual ~HlsMaker();
HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
virtual ~HlsMaker() = default;
/**
* ts数据
@ -35,17 +36,29 @@ public:
* @param timestamp
* @param is_idr_fast_packet
*/
void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet);
void inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet);
/**
* fmp4 init segment
* @param data
* @param len
*/
void inputInitSegment(const char *data, size_t len);
/**
*
*/
bool isLive();
bool isLive() const;
/**
*
*/
bool isKeep();
bool isKeep() const;
/**
* fmp4切片还是mpegts
*/
bool isFmp4() const;
/**
*
@ -66,6 +79,13 @@ protected:
*/
virtual void onDelSegment(uint64_t index) = 0;
/**
* init.mp4切片文件回调
* @param data
* @param len
*/
virtual void onWriteInitSegment(const char *data, size_t len) = 0;
/**
* ts切片文件回调
* @param data
@ -109,6 +129,7 @@ private:
void addNewSegment(uint64_t timestamp);
private:
bool _is_fmp4 = false;
float _seg_duration = 0;
uint32_t _seg_number = 0;
bool _seg_keep = false;

View File

@ -21,21 +21,14 @@ using namespace toolkit;
namespace mediakit {
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
const string &params,
uint32_t bufSize,
float seg_duration,
uint32_t seg_number,
bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) {
HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string &params, uint32_t bufSize, float seg_duration,
uint32_t seg_number, bool seg_keep) : HlsMaker(is_fmp4, seg_duration, seg_number, seg_keep) {
_poller = EventPollerPool::Instance().getPoller();
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
_path_hls = m3u8_file;
_params = params;
_buf_size = bufSize;
_file_buf.reset(new char[bufSize], [](char *ptr) {
delete[] ptr;
});
_file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; });
_info.folder = _path_prefix;
}
@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() {
}
void HlsMakerImp::clearCache(bool immediately, bool eof) {
//录制完了
// 录制完了
flushLastSegment(eof);
if (!isLive()||isKeep()) {
if (!isLive() || isKeep()) {
return;
}
@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) {
_file = nullptr;
_segment_file_paths.clear();
//hls直播才删除文件
// hls直播才删除文件
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
if (!delay || immediately) {
File::delete_file(_path_prefix.data());
@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
auto strDate = getTimeStr("%Y-%m-%d");
auto strHour = getTimeStr("%H");
auto strTime = getTimeStr("%M-%S");
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << (isFmp4() ? ".mp4" : ".ts");
segment_path = _path_prefix + "/" + segment_name;
if (isLive()) {
_segment_file_paths.emplace(index, segment_path);
@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
}
_file = makeFile(segment_path, true);
//保存本切片的元数据
// 保存本切片的元数据
_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();
WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg();
}
if (_params.empty()) {
return segment_name;
@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) {
_segment_file_paths.erase(it);
}
void HlsMakerImp::onWriteInitSegment(const char *data, size_t len) {
string init_seg_path = _path_prefix + "/init.mp4";
_file = makeFile(init_seg_path, true);
if (_file) {
fwrite(data, len, 1, _file.get());
_file = nullptr;
} else {
WarnL << "Create file failed," << init_seg_path << " " << get_uv_errmsg();
}
}
void HlsMakerImp::onWriteSegment(const char *data, size_t len) {
if (_file) {
fwrite(data, len, 1, _file.get());
@ -132,20 +137,19 @@ void HlsMakerImp::onWriteHls(const std::string &data) {
_media_src->setIndexFile(data);
}
} else {
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
WarnL << "Create hls file failed," << _path_hls << " " << get_uv_errmsg();
}
//DebugL << "\r\n" << string(data,len);
}
void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
//关闭并flush文件到磁盘
// 关闭并flush文件到磁盘
_file = nullptr;
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
if (broadcastRecordTs) {
_info.time_len = duration_ms / 1000.0f;
_info.file_size = File::fileSize(_info.file_path.data());
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info);
NOTICE_EMIT(BroadcastRecordTsArgs, Broadcast::kBroadcastRecordTs, _info);
}
}
@ -166,11 +170,11 @@ void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const s
_info.app = app;
_info.stream = stream_id;
_info.vhost = vhost;
_media_src = std::make_shared<HlsMediaSource>(_info);
_media_src = std::make_shared<HlsMediaSource>(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info);
}
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
return _media_src;
}
}//namespace mediakit
} // namespace mediakit

View File

@ -19,15 +19,10 @@
namespace mediakit {
class HlsMakerImp : public HlsMaker{
class HlsMakerImp : public HlsMaker {
public:
HlsMakerImp(const std::string &m3u8_file,
const std::string &params,
uint32_t bufSize = 64 * 1024,
float seg_duration = 5,
uint32_t seg_number = 3,
bool seg_keep = false);
HlsMakerImp(bool is_fmp4, const std::string &m3u8_file, const std::string &params, uint32_t bufSize = 64 * 1024,
float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
~HlsMakerImp() override;
/**
@ -52,6 +47,7 @@ public:
protected:
std::string onOpenSegment(uint64_t index) override ;
void onDelSegment(uint64_t index) override;
void onWriteInitSegment(const char *data, size_t len) override;
void onWriteSegment(const char *data, size_t len) override;
void onWriteHls(const std::string &data) override;
void onFlushLastSegment(uint64_t duration_ms) override;

View File

@ -33,6 +33,12 @@ void HlsCookieData::addReaderCount() {
// HlsMediaSource已经销毁
*added = false;
});
auto info = _sock_info;
_ring_reader->setGetInfoCB([info]() {
Any ret;
ret.set(info);
return ret;
});
}
}
}
@ -47,7 +53,7 @@ HlsCookieData::~HlsCookieData() {
uint64_t bytes = _bytes.load();
if (bytes >= iFlowThreshold * 1024) {
try {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast<SockInfo &>(*_sock_info));
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, *_sock_info);
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}

View File

@ -25,7 +25,7 @@ public:
using RingType = toolkit::RingBuffer<std::string>;
using Ptr = std::shared_ptr<HlsMediaSource>;
HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {}
HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {}
~HlsMediaSource() override = default;
/**
@ -58,6 +58,11 @@ public:
void onSegmentSize(size_t bytes) { _speed[TrackVideo] += bytes; }
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
_ring->getInfoList(cb, on_change);
}
private:
RingType::Ptr _ring;
std::string _index_file;

View File

@ -13,27 +13,27 @@
#include "HlsMakerImp.h"
#include "MPEG.h"
#include "MP4Muxer.h"
#include "Common/config.h"
namespace mediakit {
class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this<HlsRecorder> {
template <typename Muxer>
class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this<HlsRecorderBase<Muxer> > {
public:
using Ptr = std::shared_ptr<HlsRecorder>;
HlsRecorder(const std::string &m3u8_file, const std::string &params, const ProtocolOption &option) : MpegMuxer(false) {
HlsRecorderBase(bool is_fmp4, const std::string &m3u8_file, const std::string &params, const ProtocolOption &option) {
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep);
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration);
_option = option;
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
//清空上次的残余文件
_hls = std::make_shared<HlsMakerImp>(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
// 清空上次的残余文件
_hls->clearCache();
}
~HlsRecorder() { MpegMuxer::flush(); };
~HlsRecorderBase() override = default;
void setMediaSource(const MediaTuple& tuple) {
_hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream);
@ -41,7 +41,7 @@ public:
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
setDelegate(listener);
_hls->getMediaSource()->setListener(shared_from_this());
_hls->getMediaSource()->setListener(this->shared_from_this());
}
int readerCount() { return _hls->getMediaSource()->readerCount(); }
@ -64,7 +64,7 @@ public:
_hls->getMediaSource()->setIndexFile("");
}
if (_enabled || !_option.hls_demand) {
return MpegMuxer::inputFrame(frame);
return Muxer::inputFrame(frame);
}
return false;
}
@ -74,20 +74,54 @@ public:
return _option.hls_demand ? (_clear_cache ? true : _enabled) : true;
}
private:
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
if (!buffer) {
_hls->inputData(nullptr, 0, timestamp, key_pos);
} else {
_hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos);
}
}
private:
protected:
bool _enabled = true;
bool _clear_cache = false;
ProtocolOption _option;
std::shared_ptr<HlsMakerImp> _hls;
};
class HlsRecorder final : public HlsRecorderBase<MpegMuxer> {
public:
using Ptr = std::shared_ptr<HlsRecorder>;
template <typename ...ARGS>
HlsRecorder(ARGS && ...args) : HlsRecorderBase<MpegMuxer>(false, std::forward<ARGS>(args)...) {}
~HlsRecorder() override { this->flush(); }
private:
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
if (!buffer) {
// reset tracks
_hls->inputData(nullptr, 0, timestamp, key_pos);
} else {
_hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos);
}
}
};
class HlsFMP4Recorder final : public HlsRecorderBase<MP4MuxerMemory> {
public:
using Ptr = std::shared_ptr<HlsFMP4Recorder>;
template <typename ...ARGS>
HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase<MP4MuxerMemory>(true, std::forward<ARGS>(args)...) {}
~HlsFMP4Recorder() override { this->flush(); }
void addTrackCompleted() override {
HlsRecorderBase<MP4MuxerMemory>::addTrackCompleted();
auto data = getInitSegment();
_hls->inputInitSegment(data.data(), data.size());
}
private:
void onSegmentData(std::string buffer, uint64_t timestamp, bool key_pos) override {
if (buffer.empty()) {
// reset tracks
_hls->inputData(nullptr, 0, timestamp, key_pos);
} else {
_hls->inputData((char *)buffer.data(), buffer.size(), timestamp, key_pos);
}
}
};
}//namespace mediakit
#endif //HLSRECORDER_H

View File

@ -8,7 +8,8 @@
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifdef ENABLE_MP4
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include "MP4.h"
#include "Util/File.h"
#include "Util/logger.h"
@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){
}
}//namespace mediakit
#endif //NABLE_MP4RECORD
#endif // defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)

View File

@ -11,7 +11,7 @@
#ifndef ZLMEDIAKIT_MP4_H
#define ZLMEDIAKIT_MP4_H
#ifdef ENABLE_MP4
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include <memory>
#include <string>
@ -136,5 +136,5 @@ private:
};
}//namespace mediakit
#endif //NABLE_MP4RECORD
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#endif //ZLMEDIAKIT_MP4_H

View File

@ -102,7 +102,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei
uint8_t config[1024 * 10] = {0};
int size = mpeg4_avc_to_nalu(&avc, config, sizeof(config));
if (size > 0) {
video->inputFrame(std::make_shared<H264FrameNoCacheAble>((char *)config, size, 0, 4));
video->inputFrame(std::make_shared<H264FrameNoCacheAble>((char *)config, size, 0, 0,4));
}
}
}
@ -117,7 +117,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei
uint8_t config[1024 * 10] = {0};
int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config));
if (size > 0) {
video->inputFrame(std::make_shared<H265FrameNoCacheAble>((char *) config, size, 0, 4));
video->inputFrame(std::make_shared<H265FrameNoCacheAble>((char *) config, size, 0, 0,4));
}
}
}
@ -247,7 +247,7 @@ Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int6
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);
dumpAacConfig(track->getConfig(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN);
ret = std::make_shared<FrameWrapper<FrameFromPtr> >(buf, (uint64_t)dts, (uint64_t)pts, ADTS_HEADER_LEN, DATA_OFFSET - ADTS_HEADER_LEN, codec);
break;
}

View File

@ -8,7 +8,7 @@
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifdef ENABLE_MP4
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include "MP4Muxer.h"
#include "Extension/AAC.h"
@ -234,8 +234,8 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
audio_track->getAudioChannel(),
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
audio_track->getAudioSampleRate(),
audio_track->getAacCfg().data(),
audio_track->getAacCfg().size());
audio_track->getConfig().data(),
audio_track->getConfig().size());
if (track_id < 0) {
WarnL << "添加AAC Track失败:" << track_id;
return false;
@ -377,24 +377,24 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
return false;
}
bool key_frame = frame->keyFrame();
//flush切片
// flush切片
saveSegment();
auto data = _memory_file->getAndClearMemory();
if (!data.empty()) {
//输出切片数据
onSegmentData(std::move(data), frame->dts(), _key_frame);
// 输出切片数据
onSegmentData(std::move(data), _last_dst, _key_frame);
_key_frame = false;
}
if (key_frame) {
if (frame->keyFrame()) {
_key_frame = true;
}
if (frame->getTrackType() == TrackVideo || !haveVideo()) {
_last_dst = frame->dts();
}
return MP4MuxerInterface::inputFrame(frame);
}
}//namespace mediakit
#endif//#ifdef ENABLE_MP4
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)

View File

@ -11,13 +11,13 @@
#ifndef ZLMEDIAKIT_MP4MUXER_H
#define ZLMEDIAKIT_MP4MUXER_H
#ifdef ENABLE_MP4
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include "Common/MediaSink.h"
#include "Common/Stamp.h"
#include "MP4.h"
namespace mediakit{
namespace mediakit {
class MP4MuxerInterface : public MediaSinkInterface {
public:
@ -147,11 +147,39 @@ protected:
private:
bool _key_frame = false;
uint64_t _last_dst = 0;
std::string _init_segment;
MP4FileMemory::Ptr _memory_file;
};
} // namespace mediakit
}//namespace mediakit
#endif//#ifdef ENABLE_MP4
#else
#include "Common/MediaSink.h"
namespace mediakit {
class MP4MuxerMemory : public MediaSinkInterface {
public:
MP4MuxerMemory() = default;
~MP4MuxerMemory() override = default;
bool addTrack(const Track::Ptr & track) override { return false; }
bool inputFrame(const Frame::Ptr &frame) override { return false; }
const std::string &getInitSegment() { static std::string kNull; return kNull; };
protected:
/**
* fmp4切片回调函数
* @param std::string
* @param stamp
* @param key_frame
*/
virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0;
};
} // namespace mediakit
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#endif //ZLMEDIAKIT_MP4MUXER_H

View File

@ -184,8 +184,10 @@ bool MP4Reader::speed(MediaSource &sender, float speed) {
WarnL << "播放速度取值范围非法:" << speed;
return false;
}
//设置播放速度后应该恢复播放
pause(sender, false);
//_seek_ticker重置赋值_seek_to
setCurrentStamp(getCurrentStamp());
// 设置播放速度后应该恢复播放
_paused = false;
if (_speed == speed) {
return true;
}

View File

@ -54,6 +54,7 @@ void MP4Recorder::createFile() {
try {
_muxer = std::make_shared<MP4Muxer>();
TraceL << "Open tmp mp4 file: " << full_path_tmp;
_muxer->openMP4(full_path_tmp);
for (auto &track :_tracks) {
//添加track
@ -71,10 +72,13 @@ void MP4Recorder::asyncClose() {
auto full_path_tmp = _full_path_tmp;
auto full_path = _full_path;
auto info = _info;
TraceL << "Start close tmp mp4 file: " << full_path_tmp;
WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable {
info.time_len = muxer->getDuration() / 1000.0f;
// 关闭mp4可能非常耗时所以要放在后台线程执行
TraceL << "Closing tmp mp4 file: " << full_path_tmp;
muxer->closeMP4();
TraceL << "Closed tmp mp4 file: " << full_path_tmp;
if (!full_path_tmp.empty()) {
// 获取文件大小
info.file_size = File::fileSize(full_path_tmp.data());
@ -86,8 +90,9 @@ void MP4Recorder::asyncClose() {
// 临时文件名改成正式文件名防止mp4未完成时被访问
rename(full_path_tmp.data(), full_path.data());
}
TraceL << "Emit mp4 record event: " << full_path;
//触发mp4录制切片生成事件
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info);
NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info);
});
}

View File

@ -25,7 +25,7 @@ namespace mediakit {
//该类用于产生MPEG-TS/MPEG-PS
class MpegMuxer : public MediaSinkInterface {
public:
MpegMuxer(bool is_ps);
MpegMuxer(bool is_ps = false);
~MpegMuxer() override;
/**
@ -86,7 +86,7 @@ namespace mediakit {
class MpegMuxer : public MediaSinkInterface {
public:
MpegMuxer(bool is_ps) {}
MpegMuxer(bool is_ps = false) {}
~MpegMuxer() override = default;
bool addTrack(const Track::Ptr &track) override { return false; }
void resetTracks() override {}

View File

@ -14,7 +14,8 @@
#include "Common/MediaSource.h"
#include "MP4Recorder.h"
#include "HlsRecorder.h"
#include "Util/File.h"
#include "FMP4/FMP4MediaSourceMuxer.h"
#include "TS/TSMediaSourceMuxer.h"
using namespace std;
using namespace toolkit;
@ -53,6 +54,20 @@ string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, con
}
return File::absolutePath(mp4FilePath, recordPath);
}
case Recorder::type_hls_fmp4: {
GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath);
string m3u8FilePath;
if (enableVhost) {
m3u8FilePath = tuple.shortUrl() + "/hls.fmp4.m3u8";
} else {
m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.fmp4.m3u8";
}
// Here we use the customized file path.
if (!customized_path.empty()) {
return File::absolutePath(m3u8FilePath, customized_path);
}
return File::absolutePath(m3u8FilePath, hlsPath);
}
default:
return "";
}
@ -82,6 +97,34 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const Me
#endif
}
case Recorder::type_hls_fmp4: {
#if defined(ENABLE_HLS_FMP4)
auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path);
GET_CONFIG(bool, enable_vhost, General::kEnableVhost);
auto ret = std::make_shared<HlsFMP4Recorder>(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option);
ret->setMediaSource(tuple);
return ret;
#else
throw std::invalid_argument("hls.fmp4相关功能未打开请开启ENABLE_HLS_FMP4宏后编译再测试");
#endif
}
case Recorder::type_fmp4: {
#if defined(ENABLE_HLS_FMP4) || defined(ENABLE_MP4)
return std::make_shared<FMP4MediaSourceMuxer>(tuple, option);
#else
throw std::invalid_argument("fmp4相关功能未打开请开启ENABLE_HLS_FMP4或ENABLE_MP4宏后编译再测试");
#endif
}
case Recorder::type_ts: {
#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY)
return std::make_shared<TSMediaSourceMuxer>(tuple, option);
#else
throw std::invalid_argument("mpegts相关功能未打开请开启ENABLE_HLS或ENABLE_RTPPROXY宏后编译再测试");
#endif
}
default: throw std::invalid_argument("未知的录制类型");
}
}

View File

@ -44,7 +44,13 @@ public:
// 录制hls
type_hls = 0,
// 录制MP4
type_mp4 = 1
type_mp4 = 1,
// 录制hls.fmp4
type_hls_fmp4 = 2,
// fmp4直播
type_fmp4 = 3,
// ts直播
type_ts = 4,
} type;
/**

View File

@ -19,9 +19,7 @@
namespace mediakit {
#if defined(_WIN32)
#pragma pack(push, 1)
#endif // defined(_WIN32)
// http://www.networksorcery.com/enp/protocol/rtcp.htm
#define RTCP_PT_MAP(XX) \
@ -235,7 +233,7 @@ private:
*/
void net2Host(size_t size);
} PACKED;
};
/////////////////////////////////////////////////////////////////////////////
@ -272,7 +270,7 @@ private:
*
*/
void net2Host();
} PACKED;
};
/*
* 6.4.1 SR: Sender Report RTCP Packet
@ -371,7 +369,7 @@ private:
* @param size
*/
void net2Host(size_t size);
} PACKED;
};
/////////////////////////////////////////////////////////////////////////////
@ -441,7 +439,7 @@ private:
*/
std::string dumpString() const;
} PACKED;
};
/////////////////////////////////////////////////////////////////////////////
@ -512,7 +510,7 @@ private:
*
*/
void net2Host();
} PACKED;
};
// Source description
class RtcpSdes : public RtcpHeader {
@ -548,7 +546,7 @@ private:
* @param size
*/
void net2Host(size_t size);
} PACKED;
};
// https://tools.ietf.org/html/rfc4585#section-6.1
// 6.1. Common Packet Format for Feedback Messages
@ -624,7 +622,7 @@ private:
private:
static std::shared_ptr<RtcpFB> create_l(RtcpType type, int fmt, const void *fci, size_t fci_len);
} PACKED;
};
// BYE
/*
@ -684,7 +682,7 @@ private:
* @param size
*/
void net2Host(size_t size);
} PACKED;
};
/*
0 1 2 3
@ -738,7 +736,7 @@ private:
*/
void net2Host(size_t size);
} PACKED;
};
/*
@ -777,7 +775,7 @@ private:
* @param size
*/
void net2Host();
} PACKED;
};
class RtcpXRDLRR : public RtcpHeader {
public:
@ -814,11 +812,9 @@ private:
*/
void net2Host(size_t size);
} PACKED;
};
#if defined(_WIN32)
#pragma pack(pop)
#endif // defined(_WIN32)
} // namespace mediakit
#endif // ZLMEDIAKIT_RTCP_H

View File

@ -240,7 +240,7 @@ public:
RunLengthChunk(SymbolStatus status, uint16_t run_length);
// 打印本对象
string dumpString() const;
} PACKED;
};
RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) {
type = 0;
@ -291,7 +291,7 @@ public:
StatusVecChunk(bool symbol_bit, const vector<SymbolStatus> &status);
// 打印本对象
string dumpString() const;
} PACKED;
};
StatusVecChunk::StatusVecChunk(bool symbol_bit, const vector<SymbolStatus> &status) {
CHECK(status.size() << symbol_bit <= 14);

View File

@ -55,7 +55,7 @@ public:
private:
uint32_t data;
} PACKED;
};
#if 0
//PSFB fmt = 3
@ -97,7 +97,7 @@ public:
uint8_t padding;
static size_t constexpr kSize = 8;
} PACKED;
};
#endif
// PSFB fmt = 4
@ -125,7 +125,7 @@ private:
uint32_t ssrc;
uint8_t seq_number;
uint8_t reserved[3];
} PACKED;
};
#if 0
//PSFB fmt = 5
@ -147,7 +147,7 @@ public:
private:
uint8_t data[kSize];
} PACKED;
};
//PSFB fmt = 6
//https://tools.ietf.org/html/rfc5104#section-4.3.2.1
@ -160,7 +160,7 @@ private:
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_TSTN : public FCI_TSTR{
} PACKED;
};
//PSFB fmt = 7
//https://tools.ietf.org/html/rfc5104#section-4.3.4.1
@ -183,7 +183,7 @@ public:
private:
uint8_t data[kSize];
} PACKED;
};
#endif
@ -233,7 +233,7 @@ private:
// SSRC feedback (32 bits) Consists of one or more SSRC entries which
// this feedback message applies to.
uint32_t ssrc_feedback[1];
} PACKED;
};
/////////////////////////////////////////// RTPFB ////////////////////////////////////////////////////
@ -265,7 +265,7 @@ private:
uint16_t pid;
// bitmask of following lost packets (BLP): 16 bits
uint16_t blp;
} PACKED;
};
#if 0
//RTPFB fmt = 3
@ -300,7 +300,7 @@ private:
// to the description in section 4.2.1.2. The value is an
// unsigned integer [0..511].
uint32_t max_tbr;
} PACKED;
};
//RTPFB fmt = 4
// https://tools.ietf.org/html/rfc5104#section-4.2.2.1
@ -314,7 +314,7 @@ private:
class FCI_TMMBN : public FCI_TMMBR{
public:
} PACKED;
};
#endif
enum class SymbolStatus : uint8_t {
@ -374,7 +374,7 @@ private:
uint8_t ref_time[3];
// feedback packet count,反馈包号,本包是第几个transport-cc包每次加1 |
uint8_t fb_pkt_count;
} PACKED;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_RTCPFCI_H

View File

@ -46,7 +46,11 @@ void FlvMuxer::start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr
std::weak_ptr<FlvMuxer> weak_self = getSharedPtr();
media->pause(false);
_ring_reader = media->getRing()->attach(poller);
_ring_reader->setGetInfoCB([weak_self]() { return dynamic_pointer_cast<HttpSession>(weak_self.lock()); });
_ring_reader->setGetInfoCB([weak_self]() {
Any ret;
ret.set(dynamic_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_ring_reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
@ -107,14 +111,12 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) {
//flv header
onWrite(buffer, false);
auto &metadata = src->getMetaData();
if (metadata) {
//在有metadata的情况下才发送metadata
//其实metadata没什么用有些推流器不产生metadata
// metadata
src->getMetaData([&](const AMFValue &metadata) {
AMFEncoder invoke;
invoke << "onMetaData" << metadata;
onWriteFlvTag(MSG_DATA, std::make_shared<BufferString>(invoke.data()), 0, false);
}
});
//config frame
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {

View File

@ -62,7 +62,7 @@ bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) {
}
void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) {
if (!_play_result && !packet->isCfgFrame()) {
if (!_play_result && !packet->isConfigFrame()) {
_play_result = true;
onPlayResult(SockException(Err_success, "play http-flv success"));
}

View File

@ -91,21 +91,26 @@ void FlvSplitter::onRecvContent(const char *data, size_t len) {
case MSG_DATA3: {
BufferLikeString buffer(string(data, len));
AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0);
std::string type = dec.load<std::string>();
auto first = dec.load<AMFValue>();
bool flag = true;
if (type == "@setDataFrame") {
std::string type = dec.load<std::string>();
if (type == "onMetaData") {
if (first.type() == AMFType::AMF_STRING) {
auto type = first.as_string();
if (type == "@setDataFrame") {
type = dec.load<std::string>();
if (type == "onMetaData") {
flag = onRecvMetadata(dec.load<AMFValue>());
} else {
WarnL << "unknown type:" << type;
}
} else if (type == "onMetaData") {
flag = onRecvMetadata(dec.load<AMFValue>());
} else {
WarnL << "unknown type:" << type;
WarnL << "unknown notify:" << type;
}
} else if (type == "onMetaData") {
flag = onRecvMetadata(dec.load<AMFValue>());
} else {
WarnL << "unknown notify:" << type;
WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string();
}
if(!flag){
if (!flag) {
throw std::invalid_argument("check rtmp metadata failed");
}
return;

View File

@ -10,10 +10,10 @@
#include "Rtmp.h"
#include "Extension/Factory.h"
namespace mediakit{
TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map<std::string, std::string> &header)
{
namespace mediakit {
TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map<std::string, std::string> &header) {
_metadata.set("duration", dur_sec);
_metadata.set("fileSize", (int)fileSize);
_metadata.set("title", std::string("Streamed by ") + kServerName);
@ -22,14 +22,14 @@ TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map<std::string,
}
}
VideoMeta::VideoMeta(const VideoTrack::Ptr &video){
if(video->getVideoWidth() > 0 ){
VideoMeta::VideoMeta(const VideoTrack::Ptr &video) {
if (video->getVideoWidth() > 0) {
_metadata.set("width", video->getVideoWidth());
}
if(video->getVideoHeight() > 0 ){
if (video->getVideoHeight() > 0) {
_metadata.set("height", video->getVideoHeight());
}
if(video->getVideoFps() > 0 ){
if (video->getVideoFps() > 0) {
_metadata.set("framerate", video->getVideoFps());
}
if (video->getBitRate()) {
@ -39,26 +39,26 @@ VideoMeta::VideoMeta(const VideoTrack::Ptr &video){
_metadata.set("videocodecid", Factory::getAmfByCodecId(_codecId));
}
AudioMeta::AudioMeta(const AudioTrack::Ptr &audio){
AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) {
if (audio->getBitRate()) {
_metadata.set("audiodatarate", audio->getBitRate() / 1024);
}
if(audio->getAudioSampleRate() > 0){
if (audio->getAudioSampleRate() > 0) {
_metadata.set("audiosamplerate", audio->getAudioSampleRate());
}
if(audio->getAudioSampleBit() > 0){
if (audio->getAudioSampleBit() > 0) {
_metadata.set("audiosamplesize", audio->getAudioSampleBit());
}
if(audio->getAudioChannel() > 0){
if (audio->getAudioChannel() > 0) {
_metadata.set("stereo", audio->getAudioChannel() > 1);
}
_codecId = audio->getCodecId();
_metadata.set("audiocodecid", Factory::getAmfByCodecId(_codecId));
}
uint8_t getAudioRtmpFlags(const Track::Ptr &track){
switch (track->getTrackType()){
case TrackAudio : {
uint8_t getAudioRtmpFlags(const Track::Ptr &track) {
switch (track->getTrackType()) {
case TrackAudio: {
auto audioTrack = std::dynamic_pointer_cast<AudioTrack>(track);
if (!audioTrack) {
WarnL << "获取AudioTrack失败";
@ -68,21 +68,21 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
auto iChannel = audioTrack->getAudioChannel();
auto iSampleBit = audioTrack->getAudioSampleBit();
uint8_t flvAudioType ;
switch (track->getCodecId()){
case CodecG711A : flvAudioType = FLV_CODEC_G711A; break;
case CodecG711U : flvAudioType = FLV_CODEC_G711U; break;
case CodecOpus : {
flvAudioType = FLV_CODEC_OPUS;
//opus不通过flags获取音频相关信息
uint8_t flvAudioType;
switch (track->getCodecId()) {
case CodecG711A: flvAudioType = (uint8_t)RtmpAudioCodec::g711a; break;
case CodecG711U: flvAudioType = (uint8_t)RtmpAudioCodec::g711u; break;
case CodecOpus: {
flvAudioType = (uint8_t)RtmpAudioCodec::opus;
// opus不通过flags获取音频相关信息
iSampleRate = 44100;
iSampleBit = 16;
iChannel = 2;
break;
}
case CodecAAC : {
flvAudioType = FLV_CODEC_AAC;
//aac不通过flags获取音频相关信息
case CodecAAC: {
flvAudioType = (uint8_t)RtmpAudioCodec::aac;
// aac不通过flags获取音频相关信息
iSampleRate = 44100;
iSampleBit = 16;
iChannel = 2;
@ -93,23 +93,15 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
uint8_t flvSampleRate;
switch (iSampleRate) {
case 44100:
flvSampleRate = 3;
break;
case 22050:
flvSampleRate = 2;
break;
case 11025:
flvSampleRate = 1;
break;
case 44100: flvSampleRate = 3; break;
case 22050: flvSampleRate = 2; break;
case 11025: flvSampleRate = 1; break;
case 16000: // nellymoser only
case 8000: // nellymoser only
case 5512: // not MP3
flvSampleRate = 0;
break;
default:
WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)";
return 0;
default: WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; return 0;
}
uint8_t flvStereoOrMono = (iChannel > 1);
@ -117,32 +109,28 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
return (flvAudioType << 4) | (flvSampleRate << 2) | (flvSampleBit << 1) | flvStereoOrMono;
}
default : return 0;
default: return 0;
}
}
void Metadata::addTrack(AMFValue &metadata, const Track::Ptr &track) {
Metadata::Ptr new_metadata;
switch (track->getTrackType()) {
case TrackVideo: {
new_metadata = std::make_shared<VideoMeta>(std::dynamic_pointer_cast<VideoTrack>(track));
}
break;
}
case TrackAudio: {
new_metadata = std::make_shared<AudioMeta>(std::dynamic_pointer_cast<AudioTrack>(track));
}
break;
default:
return;
}
default: return;
}
new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) {
metadata.set(key, value);
});
new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { metadata.set(key, value); });
}
RtmpPacket::Ptr RtmpPacket::create(){
RtmpPacket::Ptr RtmpPacket::create() {
#if 0
static ResourcePool<RtmpPacket> packet_pool;
static onceToken token([]() {
@ -156,8 +144,7 @@ RtmpPacket::Ptr RtmpPacket::create(){
#endif
}
void RtmpPacket::clear()
{
void RtmpPacket::clear() {
is_abs_stamp = false;
time_stamp = 0;
ts_field = 0;
@ -165,36 +152,56 @@ void RtmpPacket::clear()
buffer.clear();
}
bool RtmpPacket::isVideoKeyFrame() const
{
return type_id == MSG_VIDEO && (uint8_t)buffer[0] >> 4 == FLV_KEY_FRAME && (uint8_t)buffer[1] == 1;
bool RtmpPacket::isVideoKeyFrame() const {
if (type_id != MSG_VIDEO) {
return false;
}
RtmpFrameType frame_type;
if (buffer[0] >> 7) {
// IsExHeader == 1
frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07);
} else {
// IsExHeader == 0
frame_type = (RtmpFrameType)(buffer[0] >> 4);
}
return frame_type == RtmpFrameType::key_frame;
}
bool RtmpPacket::isCfgFrame() const
{
bool RtmpPacket::isConfigFrame() const {
switch (type_id) {
case MSG_VIDEO: return buffer[1] == 0;
case MSG_AUDIO: {
switch (getMediaType()) {
case FLV_CODEC_AAC: return buffer[1] == 0;
default: return false;
case MSG_AUDIO: {
return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header;
}
}
default: return false;
case MSG_VIDEO: {
if (!isVideoKeyFrame()) {
return false;
}
if (buffer[0] >> 7) {
// IsExHeader == 1
return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart;
}
// IsExHeader == 0
switch ((RtmpVideoCodec)getRtmpCodecId()) {
case RtmpVideoCodec::h265:
case RtmpVideoCodec::h264: {
return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header;
}
default: return false;
}
}
default: return false;
}
}
int RtmpPacket::getMediaType() const
{
int RtmpPacket::getRtmpCodecId() const {
switch (type_id) {
case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F;
case MSG_AUDIO: return (uint8_t)buffer[0] >> 4;
default: return 0;
case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F;
case MSG_AUDIO: return (uint8_t)buffer[0] >> 4;
default: return 0;
}
}
int RtmpPacket::getAudioSampleRate() const
{
int RtmpPacket::getAudioSampleRate() const {
if (type_id != MSG_AUDIO) {
return 0;
}
@ -203,8 +210,7 @@ int RtmpPacket::getAudioSampleRate() const
return sampleRate[flvSampleRate];
}
int RtmpPacket::getAudioSampleBit() const
{
int RtmpPacket::getAudioSampleBit() const {
if (type_id != MSG_AUDIO) {
return 0;
}
@ -213,8 +219,7 @@ int RtmpPacket::getAudioSampleBit() const
return sampleBit[flvSampleBit];
}
int RtmpPacket::getAudioChannel() const
{
int RtmpPacket::getAudioChannel() const {
if (type_id != MSG_AUDIO) {
return 0;
}
@ -223,8 +228,7 @@ int RtmpPacket::getAudioChannel() const
return channel[flvStereoOrMono];
}
RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that)
{
RtmpPacket &RtmpPacket::operator=(const RtmpPacket &that) {
is_abs_stamp = that.is_abs_stamp;
stream_index = that.stream_index;
body_size = that.body_size;
@ -234,32 +238,101 @@ RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that)
return *this;
}
RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/)
{
RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) {
_time = htonl(_time);
memcpy(time_stamp, &_time, 4);
if (!_random) {
random_generate((char *)random, sizeof(random));
}
else {
} else {
memcpy(random, _random, sizeof(random));
}
}
void RtmpHandshake::random_generate(char *bytes, int size)
{
static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d };
void RtmpHandshake::random_generate(char *bytes, int size) {
static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x6e,
0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63,
0x6f, 0x6d };
for (int i = 0; i < size; i++) {
bytes[i] = cdata[rand() % (sizeof(cdata) - 1)];
}
}
}//namespace mediakit
#pragma pack(push, 1)
struct RtmpVideoHeaderEnhanced {
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t enhanced : 1;
uint8_t frame_type : 3;
uint8_t pkt_type : 4;
uint32_t fourcc;
#else
uint8_t pkt_type : 4;
uint8_t frame_type : 3;
uint8_t enhanced : 1;
uint32_t fourcc;
#endif
};
struct RtmpVideoHeaderClassic {
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t frame_type : 4;
uint8_t codec_id : 4;
uint8_t h264_pkt_type;
#else
uint8_t codec_id : 4;
uint8_t frame_type : 4;
uint8_t h264_pkt_type;
#endif
};
#pragma pack(pop)
CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) {
RtmpPacketInfo save;
info = info ? info : &save;
info->codec = CodecInvalid;
CHECK(size > 0);
RtmpVideoHeaderEnhanced *enhanced_header = (RtmpVideoHeaderEnhanced *)data;
if (enhanced_header->enhanced) {
// IsExHeader == 1
CHECK(size >= 5, "Invalid rtmp buffer size: ", size);
info->is_enhanced = true;
info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type);
info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type);
switch ((RtmpVideoCodec)ntohl(enhanced_header->fourcc)) {
case RtmpVideoCodec::fourcc_av1: info->codec = CodecAV1; break;
case RtmpVideoCodec::fourcc_vp9: info->codec = CodecVP9; break;
case RtmpVideoCodec::fourcc_hevc: info->codec = CodecH265; break;
default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4);
}
} else {
// IsExHeader == 0
RtmpVideoHeaderClassic *classic_header = (RtmpVideoHeaderClassic *)data;
info->is_enhanced = false;
info->video.frame_type = (RtmpFrameType)(classic_header->frame_type);
switch ((RtmpVideoCodec)(classic_header->codec_id)) {
case RtmpVideoCodec::h264: {
CHECK(size >= 1, "Invalid rtmp buffer size: ", size);
info->codec = CodecH264;
info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type;
break;
}
case RtmpVideoCodec::h265: {
CHECK(size >= 1, "Invalid rtmp buffer size: ", size);
info->codec = CodecH265;
info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type;
break;
}
default: WarnL << "Rtmp video codec not supported: " << (int)classic_header->codec_id; break;
}
}
return info->codec;
}
} // namespace mediakit
namespace toolkit {
StatisticImp(mediakit::RtmpPacket);
StatisticImp(mediakit::RtmpPacket);
}

View File

@ -18,13 +18,6 @@
#include "Network/Buffer.h"
#include "Extension/Track.h"
#if !defined(_WIN32)
#define PACKED __attribute__((packed))
#else
#define PACKED
#endif //!defined(_WIN32)
#define DEFAULT_CHUNK_LEN 128
#define HANDSHAKE_PLAINTEXT 0x03
#define RANDOM_LEN (1536 - 8)
@ -63,23 +56,9 @@
#define CHUNK_AUDIO 6 /*音频chunkID*/
#define CHUNK_VIDEO 7 /*视频chunkID*/
#define FLV_KEY_FRAME 1
#define FLV_INTER_FRAME 2
#define FLV_CODEC_AAC 10
#define FLV_CODEC_H264 7
//金山扩展: https://github.com/ksvc/FFmpeg/wiki
#define FLV_CODEC_H265 12
#define FLV_CODEC_G711A 7
#define FLV_CODEC_G711U 8
//参考学而思网校: https://github.com/notedit/rtmp/commit/6e314ac5b29611431f8fb5468596b05815743c10
#define FLV_CODEC_OPUS 13
namespace mediakit {
#if defined(_WIN32)
#pragma pack(push, 1)
#endif // defined(_WIN32)
class RtmpHandshake {
public:
@ -93,7 +72,7 @@ public:
void create_complex_c0c1();
}PACKED;
};
class RtmpHeader {
public:
@ -109,7 +88,7 @@ public:
uint8_t body_size[3];
uint8_t type_id;
uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */
}PACKED;
};
class FLVHeader {
public:
@ -142,7 +121,7 @@ public:
uint32_t length;
//固定为0
uint32_t previous_tag_size0;
} PACKED;
};
class RtmpTagHeader {
public:
@ -151,11 +130,9 @@ public:
uint8_t timestamp[3] = {0};
uint8_t timestamp_ex = 0;
uint8_t streamid[3] = {0}; /* Always 0. */
} PACKED;
};
#if defined(_WIN32)
#pragma pack(pop)
#endif // defined(_WIN32)
class RtmpPacket : public toolkit::Buffer{
public:
@ -182,11 +159,15 @@ public:
void clear();
// video config frame和key frame都返回true
// 用于gop缓存定位
bool isVideoKeyFrame() const;
bool isCfgFrame() const;
int getMediaType() const;
// aac config或h264/h265 config返回true支持增强型rtmp
// 用于缓存解码配置信息
bool isConfigFrame() const;
int getRtmpCodecId() const;
int getAudioSampleRate() const;
int getAudioSampleBit() const;
int getAudioChannel() const;
@ -269,5 +250,121 @@ private:
//根据音频track获取flags
uint8_t getAudioRtmpFlags(const Track::Ptr &track);
////////////////// rtmp video //////////////////////////
//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf
// UB [4]; Type of video frame.
enum class RtmpFrameType : uint8_t {
reserved = 0,
key_frame = 1, // key frame (for AVC, a seekable frame)
inter_frame = 2, // inter frame (for AVC, a non-seekable frame)
disposable_inter_frame = 3, // disposable inter frame (H.263 only)
generated_key_frame = 4, // generated key frame (reserved for server use only)
video_info_frame = 5, // video info/command frame
};
#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
// UB [4]; Codec Identifier.
enum class RtmpVideoCodec : uint32_t {
h263 = 2, // Sorenson H.263
screen_video = 3, // Screen video
vp6 = 4, // On2 VP6
vp6_alpha = 5, // On2 VP6 with alpha channel
screen_video2 = 6, // Screen video version 2
h264 = 7, // avc
h265 = 12, // 国内扩展
// 增强型rtmp FourCC
fourcc_vp9 = MKBETAG('v', 'p', '0', '9'),
fourcc_av1 = MKBETAG('a', 'v', '0', '1'),
fourcc_hevc = MKBETAG('h', 'v', 'c', '1')
};
// UI8;
enum class RtmpH264PacketType : uint8_t {
h264_config_header = 0, // AVC or HEVC sequence header(sps/pps)
h264_nalu = 1, // AVC or HEVC NALU
h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported)
};
// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf
// UB[4]
enum class RtmpPacketType : uint8_t {
PacketTypeSequenceStart = 0,
PacketTypeCodedFrames = 1,
PacketTypeSequenceEnd = 2,
// CompositionTime Offset is implied to equal zero. This is
// an optimization to save putting SI24 composition time value of zero on
// the wire. See pseudo code below in the VideoTagBody section
PacketTypeCodedFramesX = 3,
// VideoTagBody does not contain video data. VideoTagBody
// instead contains an AMF encoded metadata. See Metadata Frame
// section for an illustration of its usage. As an example, the metadata
// can be HDR information. This is a good way to signal HDR
// information. This also opens up future ways to express additional
// metadata that is meant for the next video sequence.
//
// note: presence of PacketTypeMetadata means that FrameType
// flags at the top of this table should be ignored
PacketTypeMetadata = 4,
// Carriage of bitstream in MPEG-2 TS format
// note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart
// are mutually exclusive
PacketTypeMPEG2TSSequenceStart = 5,
};
////////////////// rtmp audio //////////////////////////
//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf
// UB [4]; Format of SoundData
enum class RtmpAudioCodec : uint8_t {
/**
0 = Linear PCM, platform endian
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16 kHz mono
5 = Nellymoser 8 kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
11 = Speex
14 = MP3 8 kHz
15 = Device-specific sound
*/
g711a = 7,
g711u = 8,
aac = 10,
opus = 13 // 国内扩展
};
// UI8;
enum class RtmpAACPacketType : uint8_t {
aac_config_header = 0, // AAC sequence header
aac_raw = 1, // AAC raw
};
////////////////////////////////////////////
struct RtmpPacketInfo {
CodecId codec = CodecInvalid;
bool is_enhanced;
union {
struct {
RtmpFrameType frame_type;
RtmpPacketType pkt_type; // enhanced = true
RtmpH264PacketType h264_pkt_type; // enhanced = false
} video;
};
};
// https://github.com/veovera/enhanced-rtmp
CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr);
}//namespace mediakit
#endif//__rtmp_h

View File

@ -19,12 +19,12 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) {
size_t ret = 0;
metadata.object_for_each([&](const string &key, const AMFValue &val) {
if (key == "videocodecid") {
//找到视频
// 找到视频
++ret;
return;
}
if (key == "audiocodecid") {
//找到音频
// 找到音频
++ret;
return;
}
@ -32,7 +32,7 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) {
return ret;
}
bool RtmpDemuxer::loadMetaData(const AMFValue &val){
bool RtmpDemuxer::loadMetaData(const AMFValue &val) {
bool ret = false;
try {
int audiosamplerate = 0;
@ -60,12 +60,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
return;
}
if (key == "videocodecid") {
//找到视频
// 找到视频
videocodecid = &val;
return;
}
if (key == "audiocodecid") {
//找到音频
// 找到音频
audiocodecid = &val;
return;
}
@ -79,12 +79,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
}
});
if (videocodecid) {
//有视频
// 有视频
ret = true;
makeVideoTrack(*videocodecid, videodatarate * 1024);
}
if (audiocodecid) {
//有音频
// 有音频
ret = true;
makeAudioTrack(*audiocodecid, audiosamplerate, audiochannels, audiosamplesize, audiodatarate * 1024);
}
@ -93,7 +93,7 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
}
if (ret) {
//metadata中存在track相关的描述那么我们根据metadata判断有多少个track
// metadata中存在track相关的描述那么我们根据metadata判断有多少个track
addTrackCompleted();
}
return ret;
@ -108,8 +108,8 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
case MSG_VIDEO: {
if (!_try_get_video_track) {
_try_get_video_track = true;
auto codec = AMFValue(pkt->getMediaType());
makeVideoTrack(codec, 0);
auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size());
makeVideoTrack(Factory::getTrackByCodecId(codec_id), 0);
}
if (_video_rtmp_decoder) {
_video_rtmp_decoder->inputRtmp(pkt);
@ -120,7 +120,7 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
case MSG_AUDIO: {
if (!_try_get_audio_track) {
_try_get_audio_track = true;
auto codec = AMFValue(pkt->getMediaType());
auto codec = AMFValue(pkt->getRtmpCodecId());
makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit(), 0);
}
if (_audio_rtmp_decoder) {
@ -128,51 +128,55 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
}
break;
}
default : break;
default: break;
}
}
void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec, int bit_rate) {
makeVideoTrack(Factory::getVideoTrackByAmf(videoCodec), bit_rate);
}
void RtmpDemuxer::makeVideoTrack(const Track::Ptr &track, int bit_rate) {
if (_video_rtmp_decoder) {
return;
}
//生成Track对象
_video_track = dynamic_pointer_cast<VideoTrack>(Factory::getVideoTrackByAmf(videoCodec));
// 生成Track对象
_video_track = dynamic_pointer_cast<VideoTrack>(track);
if (!_video_track) {
return;
}
//生成rtmpCodec对象以便解码rtmp
// 生成rtmpCodec对象以便解码rtmp
_video_rtmp_decoder = Factory::getRtmpCodecByTrack(_video_track, false);
if (!_video_rtmp_decoder) {
//找不到相应的rtmp解码器该track无效
// 找不到相应的rtmp解码器该track无效
_video_track.reset();
return;
}
_video_track->setBitRate(bit_rate);
//设置rtmp解码器代理生成的frame写入该Track
// 设置rtmp解码器代理生成的frame写入该Track
_video_rtmp_decoder->addDelegate(_video_track);
addTrack(_video_track);
_try_get_video_track = true;
}
void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec,int sample_rate, int channels, int sample_bit, int bit_rate) {
void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec, int sample_rate, int channels, int sample_bit, int bit_rate) {
if (_audio_rtmp_decoder) {
return;
}
//生成Track对象
// 生成Track对象
_audio_track = dynamic_pointer_cast<AudioTrack>(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit));
if (!_audio_track) {
return;
}
//生成rtmpCodec对象以便解码rtmp
// 生成rtmpCodec对象以便解码rtmp
_audio_rtmp_decoder = Factory::getRtmpCodecByTrack(_audio_track, false);
if (!_audio_rtmp_decoder) {
//找不到相应的rtmp解码器该track无效
// 找不到相应的rtmp解码器该track无效
_audio_track.reset();
return;
}
_audio_track->setBitRate(bit_rate);
//设置rtmp解码器代理生成的frame写入该Track
// 设置rtmp解码器代理生成的frame写入该Track
_audio_rtmp_decoder->addDelegate(_audio_track);
addTrack(_audio_track);
_try_get_audio_track = true;

View File

@ -45,6 +45,7 @@ public:
private:
void makeVideoTrack(const AMFValue &val, int bit_rate);
void makeVideoTrack(const Track::Ptr &val, int bit_rate);
void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit, int bit_rate);
private:

View File

@ -57,8 +57,8 @@ public:
return _ring;
}
void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) override {
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
_ring->getInfoList(cb, on_change);
}
@ -73,42 +73,29 @@ public:
/**
* metadata
*/
const AMFValue &getMetaData() const {
template <typename FUNC>
void getMetaData(const FUNC &func) const {
std::lock_guard<std::recursive_mutex> lock(_mtx);
return _metadata;
if (_metadata) {
func(_metadata);
}
}
/**
* config帧
*/
template<typename FUNC>
void getConfigFrame(const FUNC &f) {
template <typename FUNC>
void getConfigFrame(const FUNC &func) {
std::lock_guard<std::recursive_mutex> lock(_mtx);
for (auto &pr : _config_frame_map) {
f(pr.second);
func(pr.second);
}
}
/**
* metadata
*/
virtual void setMetaData(const AMFValue &metadata) {
_metadata = metadata;
_metadata.set("title", std::string("Streamed by ") + kServerName);
_have_video = _metadata["videocodecid"];
_have_audio = _metadata["audiocodecid"];
if (_ring) {
regist();
}
}
/**
* metadata
*/
void updateMetaData(const AMFValue &metadata) {
std::lock_guard<std::recursive_mutex> lock(_mtx);
_metadata = metadata;
}
virtual void setMetaData(const AMFValue &metadata);
/**
* rtmp包

View File

@ -2,15 +2,15 @@
#include "RtmpMediaSourceImp.h"
namespace mediakit {
uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType)
{
uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) {
assert(trackType >= TrackInvalid && trackType < TrackMax);
if (trackType != TrackInvalid) {
//获取某track的时间戳
// 获取某track的时间戳
return _track_stamps[trackType];
}
//获取所有track的最小时间戳
// 获取所有track的最小时间戳
uint32_t ret = UINT32_MAX;
for (auto &stamp : _track_stamps) {
if (stamp > 0 && stamp < ret) {
@ -20,38 +20,61 @@ uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType)
return ret;
}
void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
{
bool is_video = pkt->type_id == MSG_VIDEO;
_speed[is_video ? TrackVideo : TrackAudio] += pkt->size();
//保存当前时间戳
switch (pkt->type_id) {
case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break;
case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break;
default: break;
void RtmpMediaSource::setMetaData(const AMFValue &metadata) {
{
std::lock_guard<std::recursive_mutex> lock(_mtx);
_metadata = metadata;
_metadata.set("title", std::string("Streamed by ") + kServerName);
}
if (pkt->isCfgFrame()) {
_have_video = _metadata["videocodecid"];
_have_audio = _metadata["audiocodecid"];
if (_ring) {
regist();
AMFEncoder enc;
enc << "onMetaData" << _metadata;
RtmpPacket::Ptr packet = RtmpPacket::create();
packet->buffer = enc.data();
packet->type_id = MSG_DATA;
packet->time_stamp = 0;
packet->chunk_id = CHUNK_CLIENT_REQUEST_AFTER;
packet->stream_index = STREAM_MEDIA;
onWrite(std::move(packet));
}
}
void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) {
bool is_video = pkt->type_id == MSG_VIDEO;
_speed[is_video ? TrackVideo : TrackAudio] += pkt->size();
// 保存当前时间戳
switch (pkt->type_id) {
case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break;
case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break;
default: break;
}
if (pkt->isConfigFrame()) {
std::lock_guard<std::recursive_mutex> lock(_mtx);
_config_frame_map[pkt->type_id] = pkt;
if (!_ring) {
//注册后收到config帧更新到各播放器
// 注册后收到config帧更新到各播放器
return;
}
}
if (!_ring) {
std::weak_ptr<RtmpMediaSource> weakSelf = std::static_pointer_cast<RtmpMediaSource>(shared_from_this());
auto lam = [weakSelf](int size) {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
std::weak_ptr<RtmpMediaSource> weak_self = std::static_pointer_cast<RtmpMediaSource>(shared_from_this());
auto lam = [weak_self](int size) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strongSelf->onReaderChanged(size);
strong_self->onReaderChanged(size);
};
//GOP默认缓冲512组RTMP包每组RTMP包时间戳相同(如果开启合并写了那么每组为合并写时间内的RTMP包),
//每次遇到关键帧第一个RTMP包则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开)
// GOP默认缓冲512组RTMP包每组RTMP包时间戳相同(如果开启合并写了那么每组为合并写时间内的RTMP包),
// 每次遇到关键帧第一个RTMP包则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开)
_ring = std::make_shared<RingType>(_ring_size, std::move(lam));
if (_metadata) {
regist();
@ -62,47 +85,42 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
PacketCache<RtmpPacket>::inputPacket(stamp, is_video, std::move(pkt), key);
}
RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize) : RtmpMediaSource(tuple, ringSize)
{
RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple &tuple, int ringSize)
: RtmpMediaSource(tuple, ringSize) {
_demuxer = std::make_shared<RtmpDemuxer>();
_demuxer->setTrackListener(this);
}
void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata)
{
void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) {
if (!_demuxer->loadMetaData(metadata)) {
//该metadata无效需要重新生成
// 该metadata无效需要重新生成
_metadata = metadata;
_recreate_metadata = true;
}
RtmpMediaSource::setMetaData(metadata);
}
void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
{
void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) {
if (!_all_track_ready || _muxer->isEnabled()) {
//未获取到所有Track后或者开启转协议那么需要解复用rtmp
// 未获取到所有Track后或者开启转协议那么需要解复用rtmp
_demuxer->inputRtmp(pkt);
}
RtmpMediaSource::onWrite(std::move(pkt));
}
int RtmpMediaSourceImp::totalReaderCount()
{
int RtmpMediaSourceImp::totalReaderCount() {
return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0);
}
void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option)
{
//不重复生成rtmp
void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) {
// 不重复生成rtmp
_option = option;
//不重复生成rtmp协议
// 不重复生成rtmp协议
_option.enable_rtmp = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, _demuxer->getDuration(), _option);
_muxer->setMediaListener(getListener());
_muxer->setTrackListener(std::static_pointer_cast<RtmpMediaSourceImp>(shared_from_this()));
//让_muxer对象拦截一部分事件(比如说录像相关事件)
// 让_muxer对象拦截一部分事件(比如说录像相关事件)
MediaSource::setListener(_muxer);
for (auto &track : _demuxer->getTracks(false)) {
@ -111,8 +129,7 @@ void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option)
}
}
bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track)
{
bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) {
if (_muxer) {
if (_muxer->addTrack(track)) {
track->addDelegate(_muxer);
@ -122,45 +139,38 @@ bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track)
return false;
}
void RtmpMediaSourceImp::addTrackCompleted()
{
void RtmpMediaSourceImp::addTrackCompleted() {
if (_muxer) {
_muxer->addTrackCompleted();
}
}
void RtmpMediaSourceImp::resetTracks()
{
void RtmpMediaSourceImp::resetTracks() {
if (_muxer) {
_muxer->resetTracks();
}
}
void RtmpMediaSourceImp::onAllTrackReady()
{
void RtmpMediaSourceImp::onAllTrackReady() {
_all_track_ready = true;
if (_recreate_metadata) {
//更新metadata
// 更新metadata
for (auto &track : _muxer->getTracks()) {
Metadata::addTrack(_metadata, track);
}
RtmpMediaSource::updateMetaData(_metadata);
RtmpMediaSource::setMetaData(_metadata);
}
}
void RtmpMediaSourceImp::setListener(const std::weak_ptr<MediaSourceEvent> &listener)
{
void RtmpMediaSourceImp::setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
if (_muxer) {
//_muxer对象不能处理的事件再给listener处理
_muxer->setMediaListener(listener);
}
else {
//未创建_muxer对象事件全部给listener处理
} else {
// 未创建_muxer对象事件全部给listener处理
MediaSource::setListener(listener);
}
}
}
} // namespace mediakit

View File

@ -44,7 +44,8 @@ public:
return _media_src->readerCount();
}
void onAllTrackReady(){
void addTrackCompleted() override {
RtmpMuxer::addTrackCompleted();
makeConfigPacket();
_media_src->setMetaData(getMetadata());
}

View File

@ -191,6 +191,13 @@ void RtmpPlayer::send_connect() {
obj.set("audioCodecs", (double) (0x0400));
//只支持H264
obj.set("videoCodecs", (double) (0x0080));
AMFValue fourCcList(AMF_STRICT_ARRAY);
fourCcList.add("av01");
fourCcList.add("vp09");
fourCcList.add("hvc1");
obj.set("fourCcList", fourCcList);
sendInvoke("connect", obj);
addOnResultCB([this](AMFDecoder &dec) {
//TraceL << "connect result";
@ -332,7 +339,7 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) {
return;
}
if (chunk_data->isCfgFrame()) {
if (chunk_data->isConfigFrame()) {
//输入配置帧以便初始化完成各个track
onRtmpPacket(chunk_data);
} else {

View File

@ -135,6 +135,13 @@ void RtmpPusher::send_connect() {
obj.set("type", "nonprivate");
obj.set("tcUrl", _tc_url);
obj.set("swfUrl", _tc_url);
AMFValue fourCcList(AMF_STRICT_ARRAY);
fourCcList.add("av01");
fourCcList.add("vp09");
fourCcList.add("hvc1");
obj.set("fourCcList", fourCcList);
sendInvoke("connect", obj);
addOnResultCB([this](AMFDecoder &dec) {
//TraceL << "connect result";
@ -183,10 +190,14 @@ void RtmpPusher::send_metaData(){
throw std::runtime_error("the media source was released");
}
AMFEncoder enc;
enc << "@setDataFrame" << "onMetaData" << src->getMetaData();
sendRequest(MSG_DATA, enc.data());
// metadata
src->getMetaData([&](const AMFValue &metadata) {
AMFEncoder enc;
enc << "@setDataFrame" << "onMetaData" << metadata;
sendRequest(MSG_DATA, enc.data());
});
// config frame
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {
sendRtmp(pkt->type_id, _stream_index, pkt, pkt->time_stamp, pkt->chunk_id);
});
@ -207,7 +218,16 @@ void RtmpPusher::send_metaData(){
if (++i == size) {
strong_self->setSendFlushFlag(true);
}
strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id);
if (rtmp->type_id == MSG_DATA) {
// update metadata
AMFEncoder enc;
enc << "@setDataFrame";
auto pkt = enc.data();
pkt.append(rtmp->data(), rtmp->size());
strong_self->sendRequest(MSG_DATA, pkt);
} else {
strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id);
}
});
});
_rtmp_reader->setDetachCB([weak_self]() {

View File

@ -37,7 +37,7 @@ void RtmpSession::onError(const SockException& err) {
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_total_bytes >= iFlowThreshold * 1024) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, static_cast<SockInfo &>(*this));
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, *this);
}
//如果是主动关闭的,那么不延迟注销
@ -240,7 +240,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
on_res(err, option);
});
};
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, static_cast<SockInfo &>(*this));
auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, *this);
if(!flag){
//该事件无人监听,默认鉴权成功
on_res("", ProtocolOption());
@ -316,17 +316,14 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
"description", "Now published." ,
"details", _media_info.stream,
"clientid", "0"});
auto &metadata = src->getMetaData();
if(metadata){
//在有metadata的情况下才发送metadata
//其实metadata没什么用有些推流器不产生metadata
// onMetaData
// metadata
src->getMetaData([&](const AMFValue &metadata) {
invoke.clear();
invoke << "onMetaData" << metadata;
sendResponse(MSG_DATA, invoke.data());
}
});
// config frame
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {
onSendMedia(pkt);
});
@ -334,7 +331,11 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
src->pause(false);
_ring_reader = src->getRing()->attach(getPoller());
weak_ptr<RtmpSession> weak_self = static_pointer_cast<RtmpSession>(shared_from_this());
_ring_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
_ring_reader->setGetInfoCB([weak_self]() {
Any ret;
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_ring_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) {
auto strong_self = weak_self.lock();
if (!strong_self) {
@ -355,6 +356,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
if (!strong_self) {
return;
}
strong_self->sendUserControl(CONTROL_STREAM_EOF/*or CONTROL_STREAM_DRY ?*/, STREAM_MEDIA);
strong_self->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
});
src->pause(false);
@ -408,7 +410,7 @@ void RtmpSession::doPlay(AMFDecoder &dec){
});
};
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast<SockInfo &>(*this));
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this);
if (!flag) {
// 该事件无人监听,默认不鉴权
doPlayResponse("", [token](bool) {});
@ -506,6 +508,7 @@ void RtmpSession::setMetaData(AMFDecoder &dec) {
throw std::runtime_error("can only set metadata");
}
_push_metadata = dec.load<AMFValue>();
_set_meta_data = false;
}
void RtmpSession::onProcessCmd(AMFDecoder &dec) {
@ -553,6 +556,7 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
} else if (type == "onMetaData") {
//兼容某些不规范的推流器
_push_metadata = dec.load<AMFValue>();
_set_meta_data = false;
} else {
TraceP(this) << "unknown notify:" << type;
}
@ -562,7 +566,14 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
case MSG_AUDIO:
case MSG_VIDEO: {
if (!_push_src) {
WarnL << "Not a rtmp push!";
if (_ring_reader) {
throw std::runtime_error("Rtmp player send media packets");
}
if (packet->isConfigFrame()) {
auto id = packet->type_id;
_push_config_packets.emplace(id, std::move(packet));
}
WarnL << "Rtmp pusher send media packet before handshake completed!";
return;
}
@ -570,6 +581,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
_set_meta_data = true;
_push_src->setMetaData(_push_metadata ? _push_metadata : TitleMeta().getMetadata());
}
if (!_push_config_packets.empty()) {
for (auto &pr : _push_config_packets) {
_push_src->onWrite(std::move(pr.second));
}
_push_config_packets.clear();
}
_push_src->onWrite(std::move(packet));
break;
}

View File

@ -97,6 +97,7 @@ private:
MediaInfo _media_info;
std::weak_ptr<RtmpMediaSource> _play_src;
AMFValue _push_metadata;
std::map<uint8_t, RtmpPacket::Ptr> _push_config_packets;
RtmpMediaSourceImp::Ptr _push_src;
std::shared_ptr<void> _push_src_ownership;
RtmpMediaSource::RingType::RingReader::Ptr _ring_reader;

View File

@ -67,7 +67,7 @@ RtpProcess::~RtpProcess() {
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_total_bytes >= iFlowThreshold * 1024) {
try {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, static_cast<SockInfo &>(*this));
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this);
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}
@ -210,31 +210,27 @@ void RtpProcess::setOnDetach(function<void()> cb) {
}
string RtpProcess::get_peer_ip() {
if (!_addr) {
try {
return _addr ? SockUtil::inet_ntoa((sockaddr *)_addr.get()) : "::";
} catch (std::exception &ex) {
return "::";
}
return SockUtil::inet_ntoa((sockaddr *)_addr.get());
}
uint16_t RtpProcess::get_peer_port() {
if (!_addr) {
try {
return _addr ? SockUtil::inet_port((sockaddr *)_addr.get()) : 0;
} catch (std::exception &ex) {
return 0;
}
return SockUtil::inet_port((sockaddr *)_addr.get());
}
string RtpProcess::get_local_ip() {
if (_sock) {
return _sock->get_local_ip();
}
return "::";
return _sock ? _sock->get_local_ip() : "::";
}
uint16_t RtpProcess::get_local_port() {
if (_sock) {
return _sock->get_local_port();
}
return 0;
return _sock ? _sock->get_local_port() : 0;
}
string RtpProcess::getIdentifier() const {
@ -270,9 +266,9 @@ void RtpProcess::emitOnPublish() {
};
//触发推流鉴权事件
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, static_cast<SockInfo &>(*this));
auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, *this);
if (!flag) {
//该事件无人监听,默认不鉴权
// 该事件无人监听,默认不鉴权
invoker("", ProtocolOption());
}
}

View File

@ -102,8 +102,8 @@ public:
process->setOnDetach(std::move(strong_self->_on_detach));
}
if (!process) { // process 未创建触发rtp server 超时事件
NoticeCenter::Instance().emitEvent(Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id,
(int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc);
NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id,
(int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc);
}
}
return 0;

View File

@ -39,7 +39,7 @@ RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bo
++_seq;
header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000);
header->ssrc = htonl(_ssrc);
rtp->ntp_stamp = stamp;
//有效负载
if (data) {
memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len);

View File

@ -71,7 +71,7 @@ RtpPacket::Ptr RtpTrack::inputRtp(TrackType type, int sample_rate, uint8_t *ptr,
} else {
//ssrc错误
if (_ssrc_alive.elapsedTime() < 3 * 1000) {
//接正确ssrc的rtp在10秒内那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp
//接正确ssrc的rtp在10秒内那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp
WarnL << "ssrc mismatch, rtp dropped:" << ssrc << " != " << _ssrc;
return nullptr;
}

View File

@ -10,6 +10,7 @@
#include <cstdlib>
#include <cinttypes>
#include <random>
#include "Rtsp.h"
#include "Common/Parser.h"
#include "Common/config.h"
@ -22,7 +23,8 @@ namespace mediakit {
int RtpPayload::getClockRate(int pt) {
switch (pt) {
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return clock_rate;
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
case value: return clock_rate;
RTP_PT_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return 90000;
@ -31,7 +33,8 @@ int RtpPayload::getClockRate(int pt) {
TrackType RtpPayload::getTrackType(int pt) {
switch (pt) {
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return type;
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
case value: return type;
RTP_PT_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return TrackInvalid;
@ -40,7 +43,8 @@ TrackType RtpPayload::getTrackType(int pt) {
int RtpPayload::getAudioChannel(int pt) {
switch (pt) {
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return channel;
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
case value: return channel;
RTP_PT_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return 1;
@ -49,7 +53,8 @@ int RtpPayload::getAudioChannel(int pt) {
const char *RtpPayload::getName(int pt) {
switch (pt) {
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name;
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
case value: return #name;
RTP_PT_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return "unknown payload type";
@ -58,7 +63,8 @@ const char *RtpPayload::getName(int pt) {
CodecId RtpPayload::getCodecId(int pt) {
switch (pt) {
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return codec_id;
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
case value: return codec_id;
RTP_PT_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return CodecInvalid;
@ -85,7 +91,8 @@ static void getAttrSdp(const multimap<string, string> &attr, _StrPrinter &printe
string SdpTrack::getName() const {
switch (_pt) {
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name;
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
case value: return #name;
RTP_PT_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return _codec;
@ -94,7 +101,7 @@ string SdpTrack::getName() const {
string SdpTrack::getControlUrl(const string &base_url) const {
if (_control.find("://") != string::npos) {
//以rtsp://开头
// 以rtsp://开头
return _control;
}
return base_url + "/" + _control;
@ -197,14 +204,14 @@ void SdpParser::load(const string &sdp) {
auto &track = *track_ptr;
auto it = track._attr.find("range");
if (it != track._attr.end()) {
char name[16] = {0}, start[16] = {0}, end[16] = {0};
char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 };
int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end);
if (3 == ret || 2 == ret) {
if (strcmp(start, "now") == 0) {
strcpy(start, "0");
}
track._start = (float) atof(start);
track._end = (float) atof(end);
track._start = (float)atof(start);
track._end = (float)atof(end);
track._duration = track._end - track._start;
}
}
@ -212,11 +219,11 @@ void SdpParser::load(const string &sdp) {
for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) {
auto &rtpmap = it->second;
int pt, samplerate, channel;
char codec[16] = {0};
char codec[16] = { 0 };
sscanf(rtpmap.data(), "%d", &pt);
if (track._pt != pt && track._pt != 0xff) {
//pt不匹配
// pt不匹配
it = track._attr.erase(it);
continue;
}
@ -230,18 +237,18 @@ void SdpParser::load(const string &sdp) {
track._samplerate = samplerate;
}
if (!track._samplerate && track._type == TrackVideo) {
//未设置视频采样率时赋值为90000
// 未设置视频采样率时赋值为90000
track._samplerate = 90000;
}
++it;
}
for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) {
for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) {
auto &fmtp = it->second;
int pt;
sscanf(fmtp.data(), "%d", &pt);
if (track._pt != pt && track._pt != 0xff) {
//pt不匹配
// pt不匹配
it = track._attr.erase(it);
continue;
}
@ -315,8 +322,17 @@ string SdpParser::toString() const {
return title + video + audio;
}
template<int type>
class PortManager : public std::enable_shared_from_this<PortManager<type> > {
std::string SdpParser::getControlUrl(const std::string &url) const {
auto title_track = getTrack(TrackTitle);
if (title_track && title_track->_control.find("://") != string::npos) {
// 以rtsp://开头
return title_track->_control;
}
return url;
}
template <int type>
class PortManager : public std::enable_shared_from_this<PortManager<type>> {
public:
PortManager() {
static auto func = [](const string &str, int index) {
@ -326,11 +342,11 @@ public:
};
GET_CONFIG_FUNC(uint16_t, s_min_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 0); });
GET_CONFIG_FUNC(uint16_t, s_max_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 1); });
assert(s_max_port >= s_min_port + 36 -1);
assert(s_max_port >= s_min_port + 36 - 1);
setRange((s_min_port + 1) / 2, s_max_port / 2);
}
static PortManager& Instance() {
static PortManager &Instance() {
static auto instance = std::make_shared<PortManager>();
return *instance;
}
@ -344,12 +360,12 @@ public:
}
if (is_udp) {
if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) {
//分配端口失败
// 分配端口失败
throw runtime_error("open udp socket[0] failed");
}
if (!sock1->bindUdpSock(2 * *sock_pair + 1, local_ip.data(), re_use_port)) {
//分配端口失败
// 分配端口失败
throw runtime_error("open udp socket[1] failed");
}
@ -359,12 +375,12 @@ public:
sock1->setOnAccept(on_cycle);
} else {
if (!sock0->listen(2 * *sock_pair, local_ip.data())) {
//分配端口失败
// 分配端口失败
throw runtime_error("listen tcp socket[0] failed");
}
if (!sock1->listen(2 * *sock_pair + 1, local_ip.data())) {
//分配端口失败
// 分配端口失败
throw runtime_error("listen tcp socket[1] failed");
}
@ -377,9 +393,13 @@ public:
private:
void setRange(uint16_t start_pos, uint16_t end_pos) {
std::mt19937 rng(std::random_device {}());
lock_guard<recursive_mutex> lck(_pool_mtx);
auto it = _port_pair_pool.begin();
while (start_pos < end_pos) {
_port_pair_pool.emplace_back(start_pos++);
// 随机端口排序,防止重启后导致分配的端口重复
_port_pair_pool.insert(it, start_pos++);
it = _port_pair_pool.begin() + (rng() % (1 + _port_pair_pool.size()));
}
}
@ -400,7 +420,7 @@ private:
return;
}
InfoL << "return port to pool:" << 2 * pos << "-" << 2 * pos + 1;
//回收端口号
// 回收端口号
lock_guard<recursive_mutex> lck(strong_self->_pool_mtx);
strong_self->_port_pair_pool.emplace_back(pos);
});
@ -416,7 +436,7 @@ void makeSockPair(std::pair<Socket::Ptr, Socket::Ptr> &pair, const string &local
int try_count = 0;
while (true) {
try {
//udp和tcp端口池使用相同算法和范围分配但是互不相干
// udp和tcp端口池使用相同算法和范围分配但是互不相干
if (is_udp) {
PortManager<0>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp);
} else {
@ -433,9 +453,9 @@ void makeSockPair(std::pair<Socket::Ptr, Socket::Ptr> &pair, const string &local
}
string printSSRC(uint32_t ui32Ssrc) {
char tmp[9] = {0};
char tmp[9] = { 0 };
ui32Ssrc = htonl(ui32Ssrc);
uint8_t *pSsrc = (uint8_t *) &ui32Ssrc;
uint8_t *pSsrc = (uint8_t *)&ui32Ssrc;
for (int i = 0; i < 4; i++) {
sprintf(tmp + 2 * i, "%02X", pSsrc[i]);
}
@ -447,7 +467,7 @@ bool isRtp(const char *buf, size_t size) {
return false;
}
RtpHeader *header = (RtpHeader *)buf;
return ((header->pt < 64) || (header->pt >= 96));
return ((header->pt < 64) || (header->pt >= 96)) && header->version == RtpPacket::kRtpVersion;
}
bool isRtcp(const char *buf, size_t size) {
@ -470,12 +490,10 @@ Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved) {
return rtp_tcp;
}
#define AV_RB16(x) \
((((const uint8_t*)(x))[0] << 8) | \
((const uint8_t*)(x))[1])
#define AV_RB16(x) ((((const uint8_t *)(x))[0] << 8) | ((const uint8_t *)(x))[1])
size_t RtpHeader::getCsrcSize() const {
//每个csrc占用4字节
// 每个csrc占用4字节
return csrc << 2;
}
@ -487,18 +505,18 @@ uint8_t *RtpHeader::getCsrcData() {
}
size_t RtpHeader::getExtSize() const {
//rtp有ext
// rtp有ext
if (!ext) {
return 0;
}
auto ext_ptr = &payload + getCsrcSize();
//uint16_t reserved = AV_RB16(ext_ptr);
//每个ext占用4字节
// uint16_t reserved = AV_RB16(ext_ptr);
// 每个ext占用4字节
return AV_RB16(ext_ptr + 2) << 2;
}
uint16_t RtpHeader::getExtReserved() const {
//rtp有ext
// rtp有ext
if (!ext) {
return 0;
}
@ -511,12 +529,12 @@ uint8_t *RtpHeader::getExtData() {
return nullptr;
}
auto ext_ptr = &payload + getCsrcSize();
//多出的4个字节分别为reserved、ext_len
// 多出的4个字节分别为reserved、ext_len
return ext_ptr + 4;
}
size_t RtpHeader::getPayloadOffset() const {
//有ext时还需要忽略reserved、ext_len 4个字节
// 有ext时还需要忽略reserved、ext_len 4个字节
return getCsrcSize() + (ext ? (4 + getExtSize()) : 0);
}
@ -528,7 +546,7 @@ size_t RtpHeader::getPaddingSize(size_t rtp_size) const {
if (!padding) {
return 0;
}
auto end = (uint8_t *) this + rtp_size - 1;
auto end = (uint8_t *)this + rtp_size - 1;
return *end;
}
@ -539,12 +557,12 @@ ssize_t RtpHeader::getPayloadSize(size_t rtp_size) const {
string RtpHeader::dumpString(size_t rtp_size) const {
_StrPrinter printer;
printer << "version:" << (int) version << "\r\n";
printer << "version:" << (int)version << "\r\n";
printer << "padding:" << getPaddingSize(rtp_size) << "\r\n";
printer << "ext:" << getExtSize() << "\r\n";
printer << "csrc:" << getCsrcSize() << "\r\n";
printer << "mark:" << (int) mark << "\r\n";
printer << "pt:" << (int) pt << "\r\n";
printer << "mark:" << (int)mark << "\r\n";
printer << "pt:" << (int)pt << "\r\n";
printer << "seq:" << ntohs(seq) << "\r\n";
printer << "stamp:" << ntohl(stamp) << "\r\n";
printer << "ssrc:" << ntohl(ssrc) << "\r\n";
@ -557,16 +575,16 @@ string RtpHeader::dumpString(size_t rtp_size) const {
///////////////////////////////////////////////////////////////////////
RtpHeader *RtpPacket::getHeader() {
//需除去rtcp over tcp 4个字节长度
return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize);
// 需除去rtcp over tcp 4个字节长度
return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize);
}
const RtpHeader *RtpPacket::getHeader() const {
return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize);
return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize);
}
string RtpPacket::dumpString() const {
return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
return ((RtpPacket *)this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
}
uint16_t RtpPacket::getSeq() const {
@ -590,7 +608,7 @@ uint8_t *RtpPacket::getPayload() {
}
size_t RtpPacket::getPayloadSize() const {
//需除去rtcp over tcp 4个字节长度
// 需除去rtcp over tcp 4个字节长度
return getHeader()->getPayloadSize(size() - kRtpTcpHeaderSize);
}
@ -608,23 +626,22 @@ RtpPacket::Ptr RtpPacket::create() {
#endif
}
/**
* title类型sdp
* @param dur_sec rtsp点播时长0
* @param header sdp描述
* @param version sdp版本
*/
* title类型sdp
* @param dur_sec rtsp点播时长0
* @param header sdp描述
* @param version sdp版本
*/
TitleSdp::TitleSdp(float dur_sec, const std::map<std::string, std::string>& header, int version) : Sdp(0, 0) {
TitleSdp::TitleSdp(float dur_sec, const std::map<std::string, std::string> &header, int version)
: Sdp(0, 0) {
_printer << "v=" << version << "\r\n";
if (!header.empty()) {
for (auto &pr : header) {
_printer << pr.first << "=" << pr.second << "\r\n";
}
}
else {
} else {
_printer << "o=- 0 0 IN IP4 0.0.0.0\r\n";
_printer << "s=Streamed by " << kServerName << "\r\n";
_printer << "c=IN IP4 0.0.0.0\r\n";
@ -632,18 +649,17 @@ TitleSdp::TitleSdp(float dur_sec, const std::map<std::string, std::string>& head
}
if (dur_sec <= 0) {
//直播
// 直播
_printer << "a=range:npt=now-\r\n";
}
else {
//点播
} else {
// 点播
_dur_sec = dur_sec;
_printer << "a=range:npt=0-" << dur_sec << "\r\n";
}
_printer << "a=control:*\r\n";
}
}//namespace mediakit
} // namespace mediakit
namespace toolkit {
StatisticImp(mediakit::RtpPacket);

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