add ZLMediaKit code for learning.
All checks were successful
Deploy / PullDocker (push) Successful in 12s
Deploy / Build (push) Successful in 1m51s

This commit is contained in:
amass 2024-09-28 23:55:00 +08:00
parent 7a87c76543
commit 9de3af15eb
295 changed files with 65380 additions and 33 deletions

View File

@ -13,6 +13,8 @@ FetchContent_Declare(Kylin
)
FetchContent_MakeAvailable(Kylin)
add_subdirectory(MediaServer)
add_subdirectory(ToolKit)
add_subdirectory(Server)
add_subdirectory(ThirdParty)
add_subdirectory(UnitTest)

143
MediaServer/CMakeLists.txt Normal file
View File

@ -0,0 +1,143 @@
add_library(MediaServer
Common/config.h Common/config.cpp
Common/macros.h Common/macros.cpp
Common/MediaSink.h Common/MediaSink.cpp
Common/MediaSource.h Common/MediaSource.cpp
Common/MultiMediaSourceMuxer.h Common/MultiMediaSourceMuxer.cpp
Common/Parser.h Common/Parser.cpp
Common/Stamp.h Common/Stamp.cpp
Common/strCoding.h Common/strCoding.cpp
ext-codec/AAC.h ext-codec/AAC.cpp
ext-codec/AACRtmp.h ext-codec/AACRtmp.cpp
ext-codec/G711Rtp.h ext-codec/G711Rtp.cpp
ext-codec/H264.h ext-codec/H264.cpp
ext-codec/H264Rtp.h ext-codec/H264Rtp.cpp
ext-codec/H265.h ext-codec/H265.cpp
ext-codec/H265Rtp.h ext-codec/H265Rtp.cpp
ext-codec/JPEG.h ext-codec/JPEG.cpp
ext-codec/L16.h ext-codec/L16.cpp
ext-codec/Opus.h ext-codec/Opus.cpp
ext-codec/AACRtp.h ext-codec/AACRtp.cpp
ext-codec/G711.h ext-codec/G711.cpp
ext-codec/H264Rtmp.h ext-codec/H264Rtmp.cpp
ext-codec/H265Rtmp.h ext-codec/H265Rtmp.cpp
ext-codec/JPEGRtp.h ext-codec/JPEGRtp.cpp
ext-codec/SPSParser.h ext-codec/SPSParser.c
Extension/Frame.h Extension/Frame.cpp
Extension/CommonRtmp.h Extension/CommonRtmp.cpp
Extension/CommonRtp.h Extension/CommonRtp.cpp
Extension/Factory.h Extension/Factory.cpp
Http/HlsParser.h Http/HlsParser.cpp
Http/HttpBody.h Http/HttpBody.cpp
Http/HttpClientImp.h Http/HttpClientImp.cpp
Http/HttpCookie.h Http/HttpCookie.cpp
Http/HttpFileManager.h Http/HttpFileManager.cpp
Http/HttpRequester.h Http/HttpRequester.cpp
Http/TsPlayer.h Http/TsPlayer.cpp
Http/WebSocketSession.h
Http/HttpChunkedSplitter.h Http/HttpChunkedSplitter.cpp
Http/HttpCookieManager.h Http/HttpCookieManager.cpp
Http/HttpSession.h Http/HttpSession.cpp
Http/WebSocketSplitter.h Http/WebSocketSplitter.cpp
Http/HlsPlayer.h Http/HlsPlayer.cpp
Http/HttpConst.h Http/HttpConst.cpp
Http/HttpRequestSplitter.h Http/HttpRequestSplitter.cpp
Http/TsPlayerImp.h Http/TsplayerImp.cpp
Http/HttpClient.h Http/HttpClient.cpp
Http/HttpDownloader.h Http/HttpDownloader.cpp
Http/HttpTSPlayer.h Http/HttpTSPlayer.cpp
Http/WebSocketClient.h
Player/MediaPlayer.h Player/MediaPlayer.cpp
Player/PlayerBase.h Player/PlayerBase.cpp
Player/PlayerProxy.h Player/PlayerProxy.cpp
Pusher/MediaPusher.h Pusher/MediaPusher.cpp
Pusher/PusherBase.h Pusher/PusherBase.cpp
Pusher/PusherProxy.h Pusher/PusherProxy.cpp
Record/HlsMaker.h Record/HlsMaker.cpp
Record/HlsMakerImp.h Record/HlsMakerImp.cpp
Record/HlsMediaSource.h Record/HlsMediaSource.cpp
Record/HlsRecorder.h
Record/MP4.h Record/MP4.cpp
Record/MP4Demuxer.h Record/MP4Demuxer.cpp
Record/MP4Muxer.h Record/MP4Muxer.cpp
Record/MP4Reader.h Record/MP4Reader.cpp
Record/MP4Recorder.h Record/MP4Recorder.cpp
Record/MPEG.h Record/MPEG.cpp
Record/Recorder.h Record/Recorder.cpp
Rtcp/Rtcp.h Rtcp/Rtcp.cpp
Rtcp/RtcpContext.h Rtcp/RtcpContext.cpp
Rtcp/RtcpFCI.h Rtcp/RtcpFCI.cpp
Rtmp/FlvMuxer.h Rtmp/FlvMuxer.cpp
Rtmp/FlvPlayer.h Rtmp/FlvPlayer.cpp
Rtmp/Rtmp.h Rtmp/Rtmp.cpp
Rtmp/RtmpDemuxer.h Rtmp/RtmpDemuxer.cpp
Rtmp/RtmpMediaSourceImp.h Rtmp/RtmpMediaSourceImp.cpp
Rtmp/RtmpMuxer.h Rtmp/RtmpMuxer.cpp
Rtmp/RtmpPlayer.h Rtmp/RtmpPlayer.cpp
Rtmp/RtmpSession.h Rtmp/RtmpSession.cpp
Rtmp/amf.h Rtmp/amf.cpp
Rtmp/FlvSplitter.h Rtmp/FlvSplitter.cpp
Rtmp/RtmpPlayerImp.h
Rtmp/RtmpPusher.h Rtmp/RtmpPusher.cpp
Rtmp/utils.h Rtmp/utils.cpp
Rtmp/RtmpCodec.h
Rtmp/RtmpMediaSource.h
Rtmp/RtmpMediaSourceMuxer.h
Rtmp/RtmpProtocol.h Rtmp/RtmpProtocol.cpp
Rtp/Decoder.h Rtp/Decoder.cpp
Rtp/GB28181Process.h Rtp/GB28181Process.cpp
Rtp/PSEncoder.h Rtp/PSEncoder.cpp
Rtp/RawEncoder.h Rtp/RawEncoder.cpp
Rtp/RtpCache.h Rtp/RtpCache.cpp
Rtp/RtpSender.h Rtp/RtpSender.cpp
Rtp/RtpServer.h Rtp/RtpServer.cpp
Rtp/RtpSplitter.h Rtp/RtpSplitter.cpp
Rtp/TSDecoder.h Rtp/TSDecoder.cpp
Rtp/PSDecoder.h Rtp/PSDecoder.cpp
Rtp/RtpProcess.h Rtp/RtpProcess.cpp
Rtp/RtpSession.h Rtp/RtpSession.cpp
Rtp/ProcessInterface.h
Rtsp/RtpCodec.h Rtsp/RtpCodec.cpp
Rtsp/RtpMultiCaster.h Rtsp/RtpMultiCaster.cpp
Rtsp/RtpReceiver.h Rtsp/RtpReceiver.cpp
Rtsp/Rtsp.h Rtsp/Rtsp.cpp
Rtsp/RtspDemuxer.h Rtsp/RtspDemuxer.cpp
Rtsp/RtspMediaSourceImp.h Rtsp/RtspMediaSourceImp.cpp
Rtsp/RtspMuxer.h Rtsp/RtspMuxer.cpp
Rtsp/RtspPlayerImp.h
Rtsp/RtspSession.h Rtsp/RtspSession.cpp
Rtsp/RtspSplitter.h Rtsp/RtspSplitter.cpp
Rtsp/RtspMediaSource.h
Rtsp/RtspMediaSourceMuxer.h
Rtsp/RtspPlayer.h Rtsp/RtspPlayer.cpp
Rtsp/RtspPusher.h Rtsp/RtspPusher.cpp
Rtsp/UDPServer.h Rtsp/UDPServer.cpp
MediaServer.h MediaServer.cpp
)
target_include_directories(MediaServer
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE /opt/Libraries/ZLMediaKit/include
)
target_link_directories(MediaServer
PRIVATE /opt/Libraries/ZLMediaKit/lib
)
target_link_libraries(MediaServer
PUBLIC ToolKit
PUBLIC Universal
)

View File

@ -0,0 +1,424 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "MediaSink.h"
#include "Common/config.h"
#include "Extension/Factory.h"
#define MUTE_AUDIO_INDEX 0xFFFF
using namespace std;
namespace mediakit{
bool MediaSink::addTrack(const Track::Ptr &track_in) {
if (_only_audio && track_in->getTrackType() != TrackAudio) {
InfoL << "Only audio enabled, track ignored: " << track_in->getCodecName();
return false;
}
if (!_enable_audio) {
// 关闭音频时,加快单视频流注册速度 [AUTO-TRANSLATED:4d5a361d]
// Speed up single video stream registration when audio is off
if (track_in->getTrackType() == TrackAudio) {
// 音频被全局忽略 [AUTO-TRANSLATED:a8134a0b]
// Audio is globally ignored
InfoL << "Audio disabled, audio track ignored";
return false;
}
}
if (_all_track_ready) {
WarnL << "All track is ready, add track too late: " << track_in->getCodecName();
return false;
}
// 克隆Track只拷贝其数据不拷贝其数据转发关系 [AUTO-TRANSLATED:09edaa31]
// Clone Track, only copy its data, not its data forwarding relationship
auto track = track_in->clone();
CHECK(track, "Clone track failed: ", track_in->getCodecName());
auto index = track->getIndex();
if (!_track_map.emplace(index, std::make_pair(track, false)).second) {
WarnL << "Already add a same track: " << track->getIndex() << ", codec: " << track->getCodecName();
return false;
}
_ticker.resetTime();
_audio_add = track->getTrackType() == TrackAudio ? true : _audio_add;
_track_ready_callback[index] = [this, track]() { onTrackReady(track); };
track->addDelegate([this](const Frame::Ptr &frame) {
if (_all_track_ready) {
return onTrackFrame(frame);
}
auto &frame_unread = _frame_unread[frame->getIndex()];
GET_CONFIG(uint32_t, kMaxUnreadyFrame, General::kUnreadyFrameCache);
if (frame_unread.size() > kMaxUnreadyFrame) {
// 未就绪的的track不能缓存太多的帧否则可能内存溢出 [AUTO-TRANSLATED:23958376]
// Unready tracks cannot cache too many frames, otherwise memory may overflow
frame_unread.clear();
WarnL << "Cached frame of unready track(" << frame->getCodecName() << ") is too much, now cleared";
}
// 还有Track未就绪先缓存之 [AUTO-TRANSLATED:f96eadfa]
// There are still unready tracks, cache them first
frame_unread.emplace_back(Frame::getCacheAbleFrame(frame));
return true;
});
return true;
}
void MediaSink::resetTracks() {
_audio_add = false;
_have_video = false;
_all_track_ready = false;
_mute_audio_maker = nullptr;
_ticker.resetTime();
_track_map.clear();
_frame_unread.clear();
_track_ready_callback.clear();
}
bool MediaSink::inputFrame(const Frame::Ptr &frame) {
auto it = _track_map.find(frame->getIndex());
if (it == _track_map.end()) {
return false;
}
// got frame
it->second.second = true;
auto ret = it->second.first->inputFrame(frame);
if (_mute_audio_maker && frame->getTrackType() == TrackVideo) {
// 视频驱动产生静音音频 [AUTO-TRANSLATED:2a8c789c]
// Video driver generates silent audio
_mute_audio_maker->inputFrame(frame);
}
checkTrackIfReady();
return ret;
}
void MediaSink::checkTrackIfReady() {
if (!_all_track_ready && !_track_ready_callback.empty()) {
for (auto &pr : _track_map) {
if (pr.second.second && pr.second.first->ready()) {
// Track由未就绪状态转换成就绪状态我们就触发onTrackReady回调 [AUTO-TRANSLATED:f8975e53]
// When a Track transitions from an unready state to a ready state, we trigger the onTrackReady callback
auto it = _track_ready_callback.find(pr.first);
if (it != _track_ready_callback.end()) {
it->second();
_track_ready_callback.erase(it);
}
}
}
}
// 等待音频超时时间
GET_CONFIG(uint32_t, kWaitAudioTrackDataMS, General::kWaitAudioTrackDataMS);
if (_max_track_size > 1) {
for (auto it = _track_map.begin(); it != _track_map.end();) {
if (it->second.first->getTrackType() == TrackAudio && _ticker.elapsedTime() > kWaitAudioTrackDataMS && !it->second.second) {
// 音频超时且完全没收到音频数据,忽略音频
auto index = it->second.first->getIndex();
WarnL << "Audio track index " << index << " codec " << it->second.first->getCodecName() << " receive no data for long "
<< _ticker.elapsedTime() << "ms. Ignore it!";
it = _track_map.erase(it);
_max_track_size -= 1;
_track_ready_callback.erase(index);
} else {
++it;
}
}
}
if (!_all_track_ready) {
GET_CONFIG(uint32_t, kMaxWaitReadyMS, General::kWaitTrackReadyMS);
if (_ticker.elapsedTime() > kMaxWaitReadyMS) {
// 如果超过规定时间那么不再等待并忽略未准备好的Track [AUTO-TRANSLATED:fd089806]
// If it exceeds the specified time, then stop waiting and ignore unprepared Tracks
emitAllTrackReady();
return;
}
if (!_track_ready_callback.empty()) {
// 在超时时间内如果存在未准备好的Track那么继续等待 [AUTO-TRANSLATED:cfaf3b49]
// Within the timeout period, if there are unprepared Tracks, then continue waiting
return;
}
if (_only_audio && _audio_add) {
// 只开启音频 [AUTO-TRANSLATED:bac07e47]
// Only enable audio
emitAllTrackReady();
return;
}
if (_track_map.size() == _max_track_size) {
// 如果已经添加了音视频Track并且不存在未准备好的Track那么说明所有Track都准备好了 [AUTO-TRANSLATED:6fce8779]
// If audio and video Tracks have been added, and there are no unprepared Tracks, then all Tracks are ready
emitAllTrackReady();
return;
}
GET_CONFIG(uint32_t, kMaxAddTrackMS, General::kWaitAddTrackMS);
if (_track_map.size() == 1 && (_ticker.elapsedTime() > kMaxAddTrackMS || !_enable_audio)) {
// 如果只有一个Track那么在该Track添加后我们最多还等待若干时间(可能后面还会添加Track) [AUTO-TRANSLATED:5b4bd438]
// If there is only one Track, then after the Track is added, we wait for a certain amount of time at most (more Tracks may be added later)
emitAllTrackReady();
return;
}
}
}
void MediaSink::addTrackCompleted() {
setMaxTrackCount(_track_map.size());
}
void MediaSink::setMaxTrackCount(size_t i) {
if (_all_track_ready) {
WarnL << "All track is ready, set max track count ignored";
return;
}
_max_track_size = MAX(i, 1);
checkTrackIfReady();
}
void MediaSink::emitAllTrackReady() {
if (_all_track_ready) {
return;
}
DebugL << "All track ready use " << _ticker.elapsedTime() << "ms";
if (!_track_ready_callback.empty()) {
// 这是超时强制忽略未准备好的Track [AUTO-TRANSLATED:d4f57e00]
// This is a timeout forced ignore of unprepared Tracks
_track_ready_callback.clear();
// 移除未准备好的Track [AUTO-TRANSLATED:69965c62]
// Remove unprepared Tracks
for (auto it = _track_map.begin(); it != _track_map.end();) {
if (!it->second.second || !it->second.first->ready()) {
WarnL << "Track not ready for a long time, ignored: " << it->second.first->getCodecName();
it = _track_map.erase(it);
continue;
}
++it;
}
}
if (!_track_map.empty()) {
// 最少有一个有效的Track [AUTO-TRANSLATED:099adc94]
// There is at least one valid Track
onAllTrackReady_l();
// 全部Track就绪我们一次性把之前的帧输出 [AUTO-TRANSLATED:2431422b]
// All Tracks are ready, we output all the previous frames at once
for (auto &pr : _frame_unread) {
if (_track_map.find(pr.first) == _track_map.end()) {
// 该Track已经被移除 [AUTO-TRANSLATED:d44bf74e]
// The Track has been removed
continue;
}
pr.second.for_each([&](const Frame::Ptr &frame) { MediaSink::inputFrame(frame); });
}
_frame_unread.clear();
} else {
throw toolkit::SockException(toolkit::Err_shutdown, "no vaild track data");
}
}
void MediaSink::onAllTrackReady_l() {
// 是否添加静音音频 [AUTO-TRANSLATED:bbfbfe73]
// Whether to add silent audio
if (_add_mute_audio) {
addMuteAudioTrack();
}
onAllTrackReady();
_all_track_ready = true;
_have_video = (bool)getTrack(TrackVideo);
}
vector<Track::Ptr> MediaSink::getTracks(bool ready) const {
vector<Track::Ptr> ret;
for (auto &pr : _track_map) {
if (ready && !pr.second.first->ready()) {
continue;
}
ret.emplace_back(pr.second.first);
}
return ret;
}
static uint8_t s_mute_adts[] = {0xff, 0xf1, 0x6c, 0x40, 0x2d, 0x3f, 0xfc, 0x00, 0xe0, 0x34, 0x20, 0xad, 0xf2, 0x3f, 0xb5, 0xdd,
0x73, 0xac, 0xbd, 0xca, 0xd7, 0x7d, 0x4a, 0x13, 0x2d, 0x2e, 0xa2, 0x62, 0x02, 0x70, 0x3c, 0x1c,
0xc5, 0x63, 0x55, 0x69, 0x94, 0xb5, 0x8d, 0x70, 0xd7, 0x24, 0x6a, 0x9e, 0x2e, 0x86, 0x24, 0xea,
0x4f, 0xd4, 0xf8, 0x10, 0x53, 0xa5, 0x4a, 0xb2, 0x9a, 0xf0, 0xa1, 0x4f, 0x2f, 0x66, 0xf9, 0xd3,
0x8c, 0xa6, 0x97, 0xd5, 0x84, 0xac, 0x09, 0x25, 0x98, 0x0b, 0x1d, 0x77, 0x04, 0xb8, 0x55, 0x49,
0x85, 0x27, 0x06, 0x23, 0x58, 0xcb, 0x22, 0xc3, 0x20, 0x3a, 0x12, 0x09, 0x48, 0x24, 0x86, 0x76,
0x95, 0xe3, 0x45, 0x61, 0x43, 0x06, 0x6b, 0x4a, 0x61, 0x14, 0x24, 0xa9, 0x16, 0xe0, 0x97, 0x34,
0xb6, 0x58, 0xa4, 0x38, 0x34, 0x90, 0x19, 0x5d, 0x00, 0x19, 0x4a, 0xc2, 0x80, 0x4b, 0xdc, 0xb7,
0x00, 0x18, 0x12, 0x3d, 0xd9, 0x93, 0xee, 0x74, 0x13, 0x95, 0xad, 0x0b, 0x59, 0x51, 0x0e, 0x99,
0xdf, 0x49, 0x98, 0xde, 0xa9, 0x48, 0x4b, 0xa5, 0xfb, 0xe8, 0x79, 0xc9, 0xe2, 0xd9, 0x60, 0xa5,
0xbe, 0x74, 0xa6, 0x6b, 0x72, 0x0e, 0xe3, 0x7b, 0x28, 0xb3, 0x0e, 0x52, 0xcc, 0xf6, 0x3d, 0x39,
0xb7, 0x7e, 0xbb, 0xf0, 0xc8, 0xce, 0x5c, 0x72, 0xb2, 0x89, 0x60, 0x33, 0x7b, 0xc5, 0xda, 0x49,
0x1a, 0xda, 0x33, 0xba, 0x97, 0x9e, 0xa8, 0x1b, 0x6d, 0x5a, 0x77, 0xb6, 0xf1, 0x69, 0x5a, 0xd1,
0xbd, 0x84, 0xd5, 0x4e, 0x58, 0xa8, 0x5e, 0x8a, 0xa0, 0xc2, 0xc9, 0x22, 0xd9, 0xa5, 0x53, 0x11,
0x18, 0xc8, 0x3a, 0x39, 0xcf, 0x3f, 0x57, 0xb6, 0x45, 0x19, 0x1e, 0x8a, 0x71, 0xa4, 0x46, 0x27,
0x9e, 0xe9, 0xa4, 0x86, 0xdd, 0x14, 0xd9, 0x4d, 0xe3, 0x71, 0xe3, 0x26, 0xda, 0xaa, 0x17, 0xb4,
0xac, 0xe1, 0x09, 0xc1, 0x0d, 0x75, 0xba, 0x53, 0x0a, 0x37, 0x8b, 0xac, 0x37, 0x39, 0x41, 0x27,
0x6a, 0xf0, 0xe9, 0xb4, 0xc2, 0xac, 0xb0, 0x39, 0x73, 0x17, 0x64, 0x95, 0xf4, 0xdc, 0x33, 0xbb,
0x84, 0x94, 0x3e, 0xf8, 0x65, 0x71, 0x60, 0x7b, 0xd4, 0x5f, 0x27, 0x79, 0x95, 0x6a, 0xba, 0x76,
0xa6, 0xa5, 0x9a, 0xec, 0xae, 0x55, 0x3a, 0x27, 0x48, 0x23, 0xcf, 0x5c, 0x4d, 0xbc, 0x0b, 0x35,
0x5c, 0xa7, 0x17, 0xcf, 0x34, 0x57, 0xc9, 0x58, 0xc5, 0x20, 0x09, 0xee, 0xa5, 0xf2, 0x9c, 0x6c,
0x39, 0x1a, 0x77, 0x92, 0x9b, 0xff, 0xc6, 0xae, 0xf8, 0x36, 0xba, 0xa8, 0xaa, 0x6b, 0x1e, 0x8c,
0xc5, 0x97, 0x39, 0x6a, 0xb8, 0xa2, 0x55, 0xa8, 0xf8};
#define MUTE_ADTS_DATA s_mute_adts
#define MUTE_ADTS_DATA_MS 128
static uint8_t ADTS_CONFIG[2] = { 0x15, 0x88 };
bool MuteAudioMaker::inputFrame(const Frame::Ptr &frame) {
if (_track_index == -1) {
// 锁定track [AUTO-TRANSLATED:41aff35e]
// Lock track
_track_index = frame->getIndex();
}
if (frame->getIndex() != _track_index) {
// 不是锁定的track [AUTO-TRANSLATED:496bd08b]
// Not a locked track
return false;
}
auto audio_idx = frame->dts() / MUTE_ADTS_DATA_MS;
if (_audio_idx != audio_idx) {
_audio_idx = audio_idx;
auto aacFrame = std::make_shared<FrameToCache<FrameFromPtr>>(CodecAAC, (char *)MUTE_ADTS_DATA, sizeof(s_mute_adts), _audio_idx * MUTE_ADTS_DATA_MS, 0, 7);
aacFrame->setIndex(MUTE_AUDIO_INDEX);
return FrameDispatcher::inputFrame(aacFrame);
}
return false;
}
bool MediaSink::addMuteAudioTrack() {
if (!_enable_audio) {
return false;
}
for (auto &pr : _track_map) {
if (pr.second.first->getTrackType() == TrackAudio) {
return false;
}
}
auto audio = Factory::getTrackByCodecId(CodecAAC);
audio->setIndex(MUTE_AUDIO_INDEX);
audio->setExtraData(ADTS_CONFIG, 2);
_track_map[MUTE_AUDIO_INDEX] = std::make_pair(audio, true);
audio->addDelegate([this](const Frame::Ptr &frame) { return onTrackFrame(frame); });
_mute_audio_maker = std::make_shared<MuteAudioMaker>();
_mute_audio_maker->addDelegate([audio](const Frame::Ptr &frame) { return audio->inputFrame(frame); });
onTrackReady(audio);
TraceL << "Mute aac track added";
return true;
}
bool MediaSink::isAllTrackReady() const {
return _all_track_ready;
}
void MediaSink::enableAudio(bool flag) {
_enable_audio = flag;
}
void MediaSink::setOnlyAudio() {
_only_audio = true;
_enable_audio = true;
_add_mute_audio = false;
}
void MediaSink::enableMuteAudio(bool flag) {
_add_mute_audio = flag;
}
bool MediaSink::haveVideo() const {
return _have_video;
}
///////////////////////////DemuxerSink//////////////////////////////
void MediaSinkDelegate::setTrackListener(TrackListener *listener) {
_listener = listener;
}
bool MediaSinkDelegate::onTrackReady(const Track::Ptr &track) {
if (_listener) {
_listener->addTrack(track);
}
return true;
}
void MediaSinkDelegate::onAllTrackReady() {
if (_listener) {
_listener->addTrackCompleted();
}
}
void MediaSinkDelegate::resetTracks() {
MediaSink::resetTracks();
if (_listener) {
_listener->resetTracks();
}
}
///////////////////////////Demuxer//////////////////////////////
void Demuxer::setTrackListener(TrackListener *listener, bool wait_track_ready) {
if (wait_track_ready) {
auto sink = std::make_shared<MediaSinkDelegate>();
sink->setTrackListener(listener);
_sink = std::move(sink);
}
_listener = listener;
}
bool Demuxer::addTrack(const Track::Ptr &track) {
if (!_sink) {
_origin_track.emplace_back(track);
return _listener ? _listener->addTrack(track) : false;
}
if (_sink->addTrack(track)) {
track->addDelegate([this](const Frame::Ptr &frame) { return _sink->inputFrame(frame); });
return true;
}
return false;
}
void Demuxer::addTrackCompleted() {
if (_sink) {
_sink->addTrackCompleted();
} else if (_listener) {
_listener->addTrackCompleted();
}
}
void Demuxer::resetTracks() {
if (_sink) {
_sink->resetTracks();
} else if (_listener) {
_listener->resetTracks();
}
}
vector<Track::Ptr> Demuxer::getTracks(bool ready) const {
if (_sink) {
return _sink->getTracks(ready);
}
vector<Track::Ptr> ret;
for (auto &track : _origin_track) {
if (ready && !track->ready()) {
continue;
}
ret.emplace_back(track);
}
return ret;
}
} // namespace mediakit

View File

@ -0,0 +1,300 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MEDIASINK_H
#define ZLMEDIAKIT_MEDIASINK_H
#include <mutex>
#include <memory>
#include "Util/TimeTicker.h"
#include "Extension/Frame.h"
#include "Extension/Track.h"
namespace mediakit{
class TrackListener {
public:
virtual ~TrackListener() = default;
/**
* trackTrack的clone方法
* sps pps这些信息 Delegate相关关系
* @param track
* Add track, internally calls the clone method of Track
* Only clones sps pps information, not the Delegate relationship
* @param track
* [AUTO-TRANSLATED:ba6faf58]
*/
virtual bool addTrack(const Track::Ptr & track) = 0;
/**
* track完毕
* Track added
* [AUTO-TRANSLATED:dc70ddea]
*/
virtual void addTrackCompleted() {};
/**
* track
* Reset track
* [AUTO-TRANSLATED:95dc0b4f]
*/
virtual void resetTracks() {};
};
class MediaSinkInterface : public FrameWriterInterface, public TrackListener {
public:
using Ptr = std::shared_ptr<MediaSinkInterface>;
};
/**
* aac静音音频添加器
* AAC mute audio adder
* [AUTO-TRANSLATED:aa154f71]
*/
class MuteAudioMaker : public FrameDispatcher {
public:
using Ptr = std::shared_ptr<MuteAudioMaker>;
bool inputFrame(const Frame::Ptr &frame) override;
private:
int _track_index = -1;
uint64_t _audio_idx = 0;
};
/**
* Track ready()true也就是就绪后再通知派生类进行下一步的操作
* Frame前由Track截取处理下便sps pps aa_cfg
* The role of this class is to wait for Track ready() to return true, that is, ready, and then notify the derived class to perform the next operation.
* The purpose is to intercept and process the input Frame by Track before inputting the Frame, so as to obtain valid information (such as sps pps aa_cfg)
* [AUTO-TRANSLATED:9e4f096b]
*/
class MediaSink : public MediaSinkInterface, public TrackSource{
public:
using Ptr = std::shared_ptr<MediaSink>;
/**
* frame
* @param frame
* Input frame
* @param frame
* [AUTO-TRANSLATED:7aaa5bba]
*/
bool inputFrame(const Frame::Ptr &frame) override;
/**
* trackTrack的clone方法
* sps pps这些信息 Delegate相关关系
* @param track
* Add track, internally calls the clone method of Track
* Only clones sps pps information, not the Delegate relationship
* @param track
* [AUTO-TRANSLATED:ba6faf58]
*/
bool addTrack(const Track::Ptr & track) override;
/**
* Track完毕Track3onAllTrackReady
* Track
*
* Track added, if it is a single Track, it will wait for a maximum of 3 seconds before triggering onAllTrackReady
* This will increase the delay in generating the stream. If you add both audio and video tracks, you can skip this method.
* Otherwise, to reduce the stream registration delay, please call this method manually.
* [AUTO-TRANSLATED:580b6163]
*/
void addTrackCompleted() override;
/**
* track数>=1addTrackCompleted类型
* track时
* Set the maximum number of tracks, the value range is >=1; this method is of the addTrackCompleted type;
* When setting a single track, it can speed up media registration
* [AUTO-TRANSLATED:cd521c6f]
*/
void setMaxTrackCount(size_t i);
/**
* track
* Reset track
* [AUTO-TRANSLATED:95dc0b4f]
*/
void resetTracks() override;
/**
* Track
* @param trackReady Track
* Get all Tracks
* @param trackReady Whether to get the ready Track
* [AUTO-TRANSLATED:32032e47]
*/
std::vector<Track::Ptr> getTracks(bool trackReady = true) const override;
/**
* onAllTrackReady事件
* Determine whether the onAllTrackReady event has been triggered
* [AUTO-TRANSLATED:fb8b4c71]
*/
bool isAllTrackReady() const;
/**
*
* Set whether to enable audio
* [AUTO-TRANSLATED:0e9a3ef0]
*/
void enableAudio(bool flag);
/**
*
* Set single audio
* [AUTO-TRANSLATED:48fc734a]
*/
void setOnlyAudio();
/**
*
* Set whether to enable adding mute audio
* [AUTO-TRANSLATED:49efef10]
*/
void enableMuteAudio(bool flag);
/**
* track
* Whether there is a video track
* [AUTO-TRANSLATED:4c4d651d]
*/
bool haveVideo() const;
protected:
/**
* track已经准备好ready()true
* sps pps等相关信息了
* @param track
* A certain track is ready, its ready() status returns true,
* This means that you can get its related information such as sps pps
* @param track
* [AUTO-TRANSLATED:720dedc1]
*/
virtual bool onTrackReady(const Track::Ptr & track) { return false; };
/**
* Track已经准备好
* All Tracks are ready,
* [AUTO-TRANSLATED:c54d02e2]
*/
virtual void onAllTrackReady() {};
/**
* Track输出frameonAllTrackReady触发后才会调用此方法
* @param frame
* A certain Track outputs a frame, this method will be called only after onAllTrackReady is triggered
* @param frame
* [AUTO-TRANSLATED:debbd247]
*/
virtual bool onTrackFrame(const Frame::Ptr &frame) { return false; };
private:
/**
* onAllTrackReady事件
* Trigger the onAllTrackReady event
* [AUTO-TRANSLATED:068fdb61]
*/
void emitAllTrackReady();
/**
* track是否准备完毕
* Check if the track is ready
* [AUTO-TRANSLATED:12e7c3e6]
*/
void checkTrackIfReady();
void onAllTrackReady_l();
/**
* aac静音轨道
* Add AAC mute track
* [AUTO-TRANSLATED:9ba052b5]
*/
bool addMuteAudioTrack();
private:
bool _audio_add = false;
bool _have_video = false;
bool _enable_audio = true;
bool _only_audio = false;
bool _add_mute_audio = true;
bool _all_track_ready = false;
size_t _max_track_size = 2;
toolkit::Ticker _ticker;
MuteAudioMaker::Ptr _mute_audio_maker;
std::unordered_map<int, toolkit::List<Frame::Ptr> > _frame_unread;
std::unordered_map<int, std::function<void()> > _track_ready_callback;
std::unordered_map<int, std::pair<Track::Ptr, bool/*got frame*/> > _track_map;
};
class MediaSinkDelegate : public MediaSink {
public:
/**
* track监听器
* Set track listener
* [AUTO-TRANSLATED:cedc97d7]
*/
void setTrackListener(TrackListener *listener);
protected:
void resetTracks() override;
bool onTrackReady(const Track::Ptr & track) override;
void onAllTrackReady() override;
private:
TrackListener *_listener = nullptr;
};
class Demuxer : protected TrackListener, public TrackSource {
public:
void setTrackListener(TrackListener *listener, bool wait_track_ready = false);
std::vector<Track::Ptr> getTracks(bool trackReady = true) const override;
protected:
bool addTrack(const Track::Ptr &track) override;
void addTrackCompleted() override;
void resetTracks() override;
private:
MediaSink::Ptr _sink;
TrackListener *_listener = nullptr;
std::vector<Track::Ptr> _origin_track;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_MEDIASINK_H

View File

@ -0,0 +1,927 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <mutex>
#include "Util/util.h"
#include "Util/NoticeCenter.h"
#include "Network/sockutil.h"
#include "Network/Session.h"
#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;
namespace toolkit {
StatisticImp(mediakit::MediaSource);
}
namespace mediakit {
static recursive_mutex s_media_source_mtx;
using StreamMap = unordered_map<string/*strema_id*/, weak_ptr<MediaSource> >;
using AppStreamMap = unordered_map<string/*app*/, StreamMap>;
using VhostAppStreamMap = unordered_map<string/*vhost*/, AppStreamMap>;
using SchemaVhostAppStreamMap = unordered_map<string/*schema*/, VhostAppStreamMap>;
static SchemaVhostAppStreamMap s_media_source_map;
string getOriginTypeString(MediaOriginType type){
#define SWITCH_CASE(type) case MediaOriginType::type : return #type
switch (type) {
SWITCH_CASE(unknown);
SWITCH_CASE(rtmp_push);
SWITCH_CASE(rtsp_push);
SWITCH_CASE(rtp_push);
SWITCH_CASE(pull);
SWITCH_CASE(ffmpeg_pull);
SWITCH_CASE(mp4_vod);
SWITCH_CASE(device_chn);
SWITCH_CASE(rtc_push);
SWITCH_CASE(srt_push);
default : return "unknown";
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
ProtocolOption::ProtocolOption() {
mINI ini;
auto &config = mINI::Instance();
static auto sz = strlen(Protocol::kFieldName);
for (auto it = config.lower_bound(Protocol::kFieldName); it != config.end() && start_with(it->first, Protocol::kFieldName); ++it) {
ini.emplace(it->first.substr(sz), it->second);
}
load(ini);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct MediaSourceNull : public MediaSource {
MediaSourceNull() : MediaSource("schema", MediaTuple{"vhost", "app", "stream", ""}) {};
int readerCount() override { return 0; }
};
MediaSource &MediaSource::NullMediaSource() {
static std::shared_ptr<MediaSource> s_null = std::make_shared<MediaSourceNull>();
return *s_null;
}
MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple(tuple) {
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost || _tuple.vhost.empty()) {
_tuple.vhost = DEFAULT_VHOST;
}
_schema = schema;
_create_stamp = time(NULL);
}
MediaSource::~MediaSource() {
try {
unregist();
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}
}
std::shared_ptr<void> MediaSource::getOwnership() {
if (_owned.test_and_set()) {
// 已经被所有 [AUTO-TRANSLATED:bab937dc]
// Already owned by all
return nullptr;
}
weak_ptr<MediaSource> weak_self = shared_from_this();
// 确保返回的Ownership智能指针不为空0x01无实际意义 [AUTO-TRANSLATED:9a4cca08]
// Ensure that the returned Ownership smart pointer is not empty, 0x01 has no practical meaning
return std::shared_ptr<void>((void *) 0x01, [weak_self](void *ptr) {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->_owned.clear();
}
});
}
int MediaSource::getBytesSpeed(TrackType type){
if(type == TrackInvalid || type == TrackMax){
return _speed[TrackVideo].getSpeed() + _speed[TrackAudio].getSpeed();
}
return _speed[type].getSpeed();
}
uint64_t MediaSource::getAliveSecond() const {
// 使用Ticker对象获取存活时间的目的是防止修改系统时间导致回退 [AUTO-TRANSLATED:68474061]
// The purpose of using the Ticker object to obtain the survival time is to prevent the modification of the system time from causing a rollback
return _ticker.createdTime() / 1000;
}
vector<Track::Ptr> MediaSource::getTracks(bool ready) const {
auto listener = _listener.lock();
if(!listener){
return vector<Track::Ptr>();
}
return listener->getMediaTracks(const_cast<MediaSource &>(*this), ready);
}
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
_listener = listener;
}
std::weak_ptr<MediaSourceEvent> MediaSource::getListener() const {
return _listener;
}
int MediaSource::totalReaderCount(){
auto listener = _listener.lock();
if(!listener){
return readerCount();
}
return listener->totalReaderCount(*this);
}
MediaOriginType MediaSource::getOriginType() const {
auto listener = _listener.lock();
if (!listener) {
return MediaOriginType::unknown;
}
return listener->getOriginType(const_cast<MediaSource &>(*this));
}
string MediaSource::getOriginUrl() const {
auto listener = _listener.lock();
if (!listener) {
return getUrl();
}
auto ret = listener->getOriginUrl(const_cast<MediaSource &>(*this));
if (!ret.empty()) {
return ret;
}
return getUrl();
}
std::shared_ptr<SockInfo> MediaSource::getOriginSock() const {
auto listener = _listener.lock();
if (!listener) {
return nullptr;
}
return listener->getOriginSock(const_cast<MediaSource &>(*this));
}
bool MediaSource::seekTo(uint32_t stamp) {
auto listener = _listener.lock();
if(!listener){
return false;
}
return listener->seekTo(*this, stamp);
}
bool MediaSource::pause(bool pause) {
auto listener = _listener.lock();
if (!listener) {
return false;
}
return listener->pause(*this, pause);
}
bool MediaSource::speed(float speed) {
auto listener = _listener.lock();
if (!listener) {
return false;
}
return listener->speed(*this, speed);
}
bool MediaSource::close(bool force) {
auto listener = _listener.lock();
if (!listener) {
return false;
}
if (!force && totalReaderCount()) {
// 有人观看,不强制关闭 [AUTO-TRANSLATED:44b7e24d]
// Someone is watching, do not force close
return false;
}
return listener->close(*this);
}
float MediaSource::getLossRate(mediakit::TrackType type) {
auto listener = _listener.lock();
if (!listener) {
return -1;
}
return listener->getLossRate(*this, type);
}
toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() {
toolkit::EventPoller::Ptr ret;
auto listener = _listener.lock();
if (listener) {
return listener->getOwnerPoller(*this);
}
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl());
}
std::shared_ptr<MultiMediaSourceMuxer> MediaSource::getMuxer() const {
auto listener = _listener.lock();
return listener ? listener->getMuxer(const_cast<MediaSource&>(*this)) : nullptr;
}
std::shared_ptr<RtpProcess> MediaSource::getRtpProcess() const {
auto listener = _listener.lock();
return listener ? listener->getRtpProcess(const_cast<MediaSource&>(*this)) : nullptr;
}
void MediaSource::onReaderChanged(int size) {
try {
weak_ptr<MediaSource> weak_self = shared_from_this();
getOwnerPoller()->async([weak_self, size]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
auto listener = strong_self->_listener.lock();
if (listener) {
listener->onReaderChanged(*strong_self, size);
}
});
} catch (MediaSourceEvent::NotImplemented &ex) {
// 未实现接口,应该打印异常 [AUTO-TRANSLATED:84f28c9d]
// The interface is not implemented, an exception should be printed
WarnL << ex.what();
} catch (...) {
// getOwnerPoller()接口抛异常机制应该只对外不对内 [AUTO-TRANSLATED:ee2e2923]
// The getOwnerPoller() interface should only throw exceptions externally, not internally
// 所以listener已经销毁导致获取归属线程失败的异常直接忽略 [AUTO-TRANSLATED:26cb5521]
// Therefore, the exception that the listener has been destroyed and the ownership thread cannot be obtained is directly ignored
}
}
bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path, size_t max_second){
auto listener = _listener.lock();
if (!listener) {
WarnL << "未设置MediaSource的事件监听者setupRecord失败:" << getUrl();
return false;
}
return listener->setupRecord(*this, type, start, custom_path, max_second);
}
bool MediaSource::isRecording(Recorder::type type){
auto listener = _listener.lock();
if(!listener){
return false;
}
return listener->isRecording(*this, type);
}
void MediaSource::startSendRtp(const MediaSourceEvent::SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) {
auto listener = _listener.lock();
if (!listener) {
cb(0, SockException(Err_other, "尚未设置事件监听器"));
return;
}
return listener->startSendRtp(*this, args, cb);
}
bool MediaSource::stopSendRtp(const string &ssrc) {
auto listener = _listener.lock();
if (!listener) {
return false;
}
return listener->stopSendRtp(*this, ssrc);
}
template<typename MAP, typename LIST, typename First, typename ...KeyTypes>
static void for_each_media_l(const MAP &map, LIST &list, const First &first, const KeyTypes &...keys) {
if (first.empty()) {
for (auto &pr : map) {
for_each_media_l(pr.second, list, keys...);
}
return;
}
auto it = map.find(first);
if (it != map.end()) {
for_each_media_l(it->second, list, keys...);
}
}
template<typename LIST, typename Ptr>
static void emplace_back(LIST &list, const Ptr &ptr) {
auto src = ptr.lock();
if (src) {
list.emplace_back(std::move(src));
}
}
template<typename MAP, typename LIST, typename First>
static void for_each_media_l(const MAP &map, LIST &list, const First &first) {
if (first.empty()) {
for (auto &pr : map) {
emplace_back(list, pr.second);
}
return;
}
auto it = map.find(first);
if (it != map.end()) {
emplace_back(list, it->second);
}
}
void MediaSource::for_each_media(const function<void(const Ptr &src)> &cb,
const string &schema,
const string &vhost,
const string &app,
const string &stream) {
deque<Ptr> src_list;
{
lock_guard<recursive_mutex> lock(s_media_source_mtx);
for_each_media_l(s_media_source_map, src_list, schema, vhost, app, stream);
}
for (auto &src : src_list) {
cb(src);
}
}
static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, const string &app, const string &id, bool from_mp4) {
string vhost = vhost_in;
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if(vhost.empty() || !enableVhost){
vhost = DEFAULT_VHOST;
}
if (app.empty() || id.empty()) {
// 如果未指定app与stream id那么就是遍历而非查找所以应该返回查找失败 [AUTO-TRANSLATED:84976471]
// If no app and stream id are specified, then it is traversal instead of searching, so it should return search failure
return nullptr;
}
MediaSource::Ptr ret;
MediaSource::for_each_media([&](const MediaSource::Ptr &src) { ret = std::move(const_cast<MediaSource::Ptr &>(src)); }, schema, vhost, app, id);
if(!ret && from_mp4 && schema != HLS_SCHEMA){
// 未找到媒体源则读取mp4创建一个 [AUTO-TRANSLATED:e2e03a82]
// If the media source is not found, read mp4 to create one
// 播放hls不触发mp4点播(因为HLS也可以用于录像不是纯粹的直播) [AUTO-TRANSLATED:30b18b6d]
// Playing hls does not trigger mp4 on-demand (because HLS can also be used for recording, not purely live)
ret = MediaSource::createFromMP4(schema, vhost, app, id);
}
return ret;
}
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &session, bool retry,
const function<void(const MediaSource::Ptr &src)> &cb){
auto src = find_l(info.schema, info.vhost, info.app, info.stream, true);
if (src || !retry) {
cb(src);
return;
}
GET_CONFIG(int, maxWaitMS, General::kMaxStreamWaitTimeMS);
void *listener_tag = session.get();
auto poller = session->getPoller();
std::shared_ptr<atomic_flag> invoked(new atomic_flag{false});
auto cb_once = [cb, invoked](const MediaSource::Ptr &src) {
if (invoked->test_and_set()) {
// 回调已经执行过了 [AUTO-TRANSLATED:f034e2eb]
// The callback has already been executed
return;
}
cb(src);
};
auto on_timeout = poller->doDelayTask(maxWaitMS, [cb_once, listener_tag]() {
// 最多等待一定时间,如在这个时间内,流还未注册上,则返回空 [AUTO-TRANSLATED:e8851208]
// Wait for a certain amount of time at most, if the stream is not registered within this time, return empty
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
cb_once(nullptr);
return 0;
});
auto cancel_all = [on_timeout, listener_tag]() {
// 取消延时任务,防止多次回调 [AUTO-TRANSLATED:42988b9c]
// Cancel the delayed task to prevent multiple callbacks
on_timeout->cancel();
// 取消媒体注册事件监听 [AUTO-TRANSLATED:efb9aacb]
// Cancel the media registration event listener
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
};
weak_ptr<Session> weak_session = session;
auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) {
if (!bRegist ||
sender.getSchema() != info.schema ||
!equalMediaTuple(sender.getMediaTuple(), info)) {
// 不是自己感兴趣的事件,忽略之 [AUTO-TRANSLATED:b4e102d4]
// Not an event of interest, ignore it
return;
}
poller->async([weak_session, cancel_all, info, cb_once]() {
cancel_all();
if (auto strong_session = weak_session.lock()) {
// 播发器请求的流终于注册上了,切换到自己的线程再回复 [AUTO-TRANSLATED:7b79ad9b]
// The stream requested by the player is finally registered, switch to its own thread and reply
DebugL << "收到媒体注册事件,回复播放器:" << info.getUrl();
// 再找一遍媒体源,一般能找到 [AUTO-TRANSLATED:069de7f6]
// Find the media source again, usually it can be found
findAsync_l(info, strong_session, false, cb_once);
}
}, false);
};
// 监听媒体注册事件 [AUTO-TRANSLATED:9cf13779]
// Listen for media registration events
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, on_register);
function<void()> close_player = [cb_once, cancel_all, poller]() {
poller->async([cancel_all, cb_once]() {
cancel_all();
// 告诉播放器,流不存在,这样会立即断开播放器 [AUTO-TRANSLATED:b5b4eead]
// Tell the player that the stream does not exist, so it will immediately disconnect the player
cb_once(nullptr);
});
};
// 广播未找到流,此时可以立即去拉流,这样还来得及 [AUTO-TRANSLATED:794014f1]
// Broadcast that the stream is not found, at this time you can immediately pull the stream, so it is still in time
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) {
return findAsync_l(info, session, true, cb);
}
MediaSource::Ptr MediaSource::find(const string &schema, const string &vhost, const string &app, const string &id, bool from_mp4) {
return find_l(schema, vhost, app, id, from_mp4);
}
MediaSource::Ptr MediaSource::find(const string &vhost, const string &app, const string &stream_id, bool from_mp4) {
auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id, from_mp4);
if (src) {
return src;
}
src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id, from_mp4);
if (src) {
return src;
}
src = MediaSource::find(TS_SCHEMA, vhost, app, stream_id, from_mp4);
if (src) {
return src;
}
src = MediaSource::find(FMP4_SCHEMA, vhost, app, stream_id, from_mp4);
if (src) {
return src;
}
src = MediaSource::find(HLS_SCHEMA, vhost, app, stream_id, from_mp4);
if (src) {
return src;
}
return MediaSource::find(HLS_FMP4_SCHEMA, vhost, app, stream_id, from_mp4);
}
void MediaSource::emitEvent(bool regist){
auto listener = _listener.lock();
if (listener) {
// 触发回调 [AUTO-TRANSLATED:08ea452d]
// Trigger callback
listener->onRegist(*this, regist);
}
// 触发广播 [AUTO-TRANSLATED:a5b415a4]
// Trigger broadcast
NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this);
InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl();
}
void MediaSource::regist() {
{
// 减小互斥锁临界区 [AUTO-TRANSLATED:1309d309]
// Reduce mutex lock critical area
lock_guard<recursive_mutex> lock(s_media_source_mtx);
auto &ref = s_media_source_map[_schema][_tuple.vhost][_tuple.app][_tuple.stream];
auto src = ref.lock();
if (src) {
if (src.get() == this) {
return;
}
// 增加判断, 防止当前流已注册时再次注册 [AUTO-TRANSLATED:ccc5dcb1]
// Add judgment to prevent re-registration when the current stream is already registered
throw std::invalid_argument("media source already existed:" + getUrl());
}
ref = shared_from_this();
}
emitEvent(true);
}
template<typename MAP, typename First, typename ...KeyTypes>
static bool erase_media_source(bool &hit, const MediaSource *thiz, MAP &map, const First &first, const KeyTypes &...keys) {
auto it = map.find(first);
if (it != map.end() && erase_media_source(hit, thiz, it->second, keys...)) {
map.erase(it);
}
return map.empty();
}
template<typename MAP, typename First>
static bool erase_media_source(bool &hit, const MediaSource *thiz, MAP &map, const First &first) {
auto it = map.find(first);
if (it != map.end()) {
auto src = it->second.lock();
if (!src || src.get() == thiz) {
// 对象已经销毁或者对象就是自己,那么移除之 [AUTO-TRANSLATED:1b9a11d1]
// If the object has been destroyed or the object is itself, then remove it
map.erase(it);
hit = true;
}
}
return map.empty();
}
// 反注册该源 [AUTO-TRANSLATED:682c27ab]
// Unregister the source
bool MediaSource::unregist() {
bool ret = false;
{
// 减小互斥锁临界区 [AUTO-TRANSLATED:1309d309]
// Reduce mutex lock critical area
lock_guard<recursive_mutex> lock(s_media_source_mtx);
erase_media_source(ret, this, s_media_source_map, _schema, _tuple.vhost, _tuple.app, _tuple.stream);
}
if (ret) {
emitEvent(false);
}
return ret;
}
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b) {
return a.vhost == b.vhost && a.app == b.app && a.stream == b.stream;
}
/////////////////////////////////////MediaInfo//////////////////////////////////////
void MediaInfo::parse(const std::string &url_in){
full_url = url_in;
auto url = url_in;
auto pos = url.find("?");
if (pos != string::npos) {
params = url.substr(pos + 1);
url.erase(pos);
}
auto schema_pos = url.find("://");
if (schema_pos != string::npos) {
schema = url.substr(0, schema_pos);
} else {
schema_pos = -3;
}
auto split_vec = split(url.substr(schema_pos + 3), "/");
if (split_vec.size() > 0) {
splitUrl(split_vec[0], host, port);
vhost = host;
if (vhost == "localhost" || isIP(vhost.data())) {
// 如果访问的是localhost或ip那么则为默认虚拟主机 [AUTO-TRANSLATED:67291b7a]
// If the access is to localhost or ip, then it is the default virtual host
vhost = DEFAULT_VHOST;
}
}
if (split_vec.size() > 1) {
app = split_vec[1];
}
if (split_vec.size() > 2) {
string stream_id;
for (size_t i = 2; i < split_vec.size(); ++i) {
stream_id.append(split_vec[i] + "/");
}
if (stream_id.back() == '/') {
stream_id.pop_back();
}
stream = stream_id;
}
auto kv = Parser::parseArgs(params);
auto it = kv.find(VHOST_KEY);
if (it != kv.end()) {
vhost = it->second;
}
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost || vhost.empty()) {
// 如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认 [AUTO-TRANSLATED:9f76a112]
// If the virtual host is closed or the virtual host is empty, set the virtual host to the default
vhost = DEFAULT_VHOST;
}
}
MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &file_path , bool check_app){
GET_CONFIG(string, appName, Record::kAppName);
if (check_app && app != appName) {
return nullptr;
}
#ifdef ENABLE_MP4
try {
MediaTuple tuple = {vhost, app, stream, ""};
auto reader = std::make_shared<MP4Reader>(tuple, file_path);
reader->startReadMP4();
return MediaSource::find(schema, vhost, app, stream);
} catch (std::exception &ex) {
WarnL << ex.what();
return nullptr;
}
#else
WarnL << "创建MP4点播失败请编译时打开\"ENABLE_MP4\"选项";
return nullptr;
#endif //ENABLE_MP4
}
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
GET_CONFIG(bool, enable, General::kBroadcastPlayerCountChanged);
if (enable) {
NOTICE_EMIT(BroadcastPlayerCountChangedArgs, Broadcast::kBroadcastPlayerCountChanged, sender.getMediaTuple(), sender.totalReaderCount());
}
if (size || sender.totalReaderCount()) {
// 还有人观看该视频,不触发关闭事件 [AUTO-TRANSLATED:7f2f6ed3]
// Someone is still watching this video, do not trigger the close event
_async_close_timer = nullptr;
return;
}
// 没有任何人观看该视频源,表明该源可以关闭了 [AUTO-TRANSLATED:ea64bb8f]
// No one is watching this video source, indicating that the source can be closed.
GET_CONFIG(string, record_app, Record::kAppName);
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
// 如果mp4点播, 无人观看时我们强制关闭点播 [AUTO-TRANSLATED:9576e4b0]
// If it's an mp4 on-demand, we force close the on-demand when no one is watching.
bool is_mp4_vod = sender.getMediaTuple().app == record_app;
weak_ptr<MediaSource> weak_sender = sender.shared_from_this();
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() {
auto strong_sender = weak_sender.lock();
if (!strong_sender) {
// 对象已经销毁 [AUTO-TRANSLATED:130328af]
// The object has been destroyed.
return false;
}
if (strong_sender->totalReaderCount()) {
// 还有人观看该视频,不触发关闭事件 [AUTO-TRANSLATED:7f2f6ed3]
// Someone is still watching this video, so the close event is not triggered.
return false;
}
if (!is_mp4_vod) {
auto muxer = strong_sender->getMuxer();
if (muxer && muxer->getOption().auto_close) {
// 此流被标记为无人观看自动关闭流 [AUTO-TRANSLATED:64a0dac3]
// This stream is marked as an automatically closed stream with no viewers.
WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl();
strong_sender->close(false);
} else {
// 直播时触发无人观看事件,让开发者自行选择是否关闭 [AUTO-TRANSLATED:c6c75eaa]
// When live streaming, trigger the no-viewer event, allowing developers to choose whether to close it.
NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender);
}
} else {
// 这个是mp4点播我们自动关闭 [AUTO-TRANSLATED:8a7b9a90]
// This is an mp4 on-demand, we automatically close it.
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
strong_sender->close(false);
}
return false;
}, nullptr);
}
string MediaSourceEvent::getOriginUrl(MediaSource &sender) const {
return sender.getUrl();
}
MediaOriginType MediaSourceEventInterceptor::getOriginType(MediaSource &sender) const {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getOriginType(sender);
}
return listener->getOriginType(sender);
}
string MediaSourceEventInterceptor::getOriginUrl(MediaSource &sender) const {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getOriginUrl(sender);
}
auto ret = listener->getOriginUrl(sender);
if (!ret.empty()) {
return ret;
}
return MediaSourceEvent::getOriginUrl(sender);
}
std::shared_ptr<SockInfo> MediaSourceEventInterceptor::getOriginSock(MediaSource &sender) const {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getOriginSock(sender);
}
return listener->getOriginSock(sender);
}
bool MediaSourceEventInterceptor::seekTo(MediaSource &sender, uint32_t stamp) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::seekTo(sender, stamp);
}
return listener->seekTo(sender, stamp);
}
bool MediaSourceEventInterceptor::pause(MediaSource &sender, bool pause) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::pause(sender, pause);
}
return listener->pause(sender, pause);
}
bool MediaSourceEventInterceptor::speed(MediaSource &sender, float speed) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::speed(sender, speed);
}
return listener->speed(sender, speed);
}
bool MediaSourceEventInterceptor::close(MediaSource &sender) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::close(sender);
}
return listener->close(sender);
}
int MediaSourceEventInterceptor::totalReaderCount(MediaSource &sender) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::totalReaderCount(sender);
}
return listener->totalReaderCount(sender);
}
void MediaSourceEventInterceptor::onReaderChanged(MediaSource &sender, int size) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::onReaderChanged(sender, size);
}
listener->onReaderChanged(sender, size);
}
void MediaSourceEventInterceptor::onRegist(MediaSource &sender, bool regist) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::onRegist(sender, regist);
}
listener->onRegist(sender, regist);
}
float MediaSourceEventInterceptor::getLossRate(MediaSource &sender, TrackType type) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getLossRate(sender, type);
}
return listener->getLossRate(sender, type);
}
toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSource &sender) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getOwnerPoller(sender);
}
return listener->getOwnerPoller(sender);
}
std::shared_ptr<MultiMediaSourceMuxer> MediaSourceEventInterceptor::getMuxer(MediaSource &sender) const {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getMuxer(sender);
}
return listener->getMuxer(sender);
}
std::shared_ptr<RtpProcess> MediaSourceEventInterceptor::getRtpProcess(MediaSource &sender) const {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getRtpProcess(sender);
}
return listener->getRtpProcess(sender);
}
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::setupRecord(sender, type, start, custom_path, max_second);
}
return listener->setupRecord(sender, type, start, custom_path, max_second);
}
bool MediaSourceEventInterceptor::isRecording(MediaSource &sender, Recorder::type type) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::isRecording(sender, type);
}
return listener->isRecording(sender, type);
}
vector<Track::Ptr> MediaSourceEventInterceptor::getMediaTracks(MediaSource &sender, bool trackReady) const {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::getMediaTracks(sender, trackReady);
}
return listener->getMediaTracks(sender, trackReady);
}
void MediaSourceEventInterceptor::startSendRtp(MediaSource &sender, const MediaSourceEvent::SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::startSendRtp(sender, args, cb);
}
listener->startSendRtp(sender, args, cb);
}
bool MediaSourceEventInterceptor::stopSendRtp(MediaSource &sender, const string &ssrc) {
auto listener = _listener.lock();
if (!listener) {
return MediaSourceEvent::stopSendRtp(sender, ssrc);
}
return listener->stopSendRtp(sender, ssrc);
}
void MediaSourceEventInterceptor::setDelegate(const std::weak_ptr<MediaSourceEvent> &listener) {
if (listener.lock().get() == this) {
throw std::invalid_argument("can not set self as a delegate");
}
_listener = listener;
}
std::shared_ptr<MediaSourceEvent> MediaSourceEventInterceptor::getDelegate() const {
return _listener.lock();
}
/////////////////////////////////////FlushPolicy//////////////////////////////////////
static bool isFlushAble_default(bool is_video, uint64_t last_stamp, uint64_t new_stamp, size_t cache_size) {
if (new_stamp + 500 < last_stamp) {
// 时间戳回退比较大(可能seek中)由于rtp中时间戳是pts是可能存在一定程度的回退的 [AUTO-TRANSLATED:67158987]
// The timestamp rollback is relatively large (possibly during seek), because the timestamp in RTP is PTS, which may have a certain degree of rollback.
return true;
}
// 时间戳发送变化或者缓存超过1024个,sendmsg接口一般最多只能发送1024个数据包 [AUTO-TRANSLATED:f87d1da0]
// The timestamp sends changes or the cache exceeds 1024, the sendmsg interface generally can only send a maximum of 1024 data packets.
return last_stamp != new_stamp || cache_size >= 1024;
}
static bool isFlushAble_merge(bool is_video, uint64_t last_stamp, uint64_t new_stamp, size_t cache_size, int merge_ms) {
if (new_stamp + 500 < last_stamp) {
// 时间戳回退比较大(可能seek中)由于rtp中时间戳是pts是可能存在一定程度的回退的 [AUTO-TRANSLATED:67158987]
// The timestamp rollback is relatively large (possibly during seek), because the timestamp in RTP is PTS, which may have a certain degree of rollback.
return true;
}
if (new_stamp > last_stamp + merge_ms) {
// 时间戳增量超过合并写阈值 [AUTO-TRANSLATED:cbcf3ab0]
// The timestamp increment exceeds the merge write threshold.
return true;
}
// 缓存数超过1024个,这个逻辑用于避免时间戳异常的流导致的内存暴增问题 [AUTO-TRANSLATED:f27e11f8]
// The number of caches exceeds 1024, this logic is used to avoid memory explosion caused by streams with abnormal timestamps.
// 而且sendmsg接口一般最多只能发送1024个数据包 [AUTO-TRANSLATED:872436e2]
// Moreover, the sendmsg interface generally can only send a maximum of 1024 data packets.
return cache_size >= 1024;
}
bool FlushPolicy::isFlushAble(bool is_video, bool is_key, uint64_t new_stamp, size_t cache_size) {
bool flush_flag = false;
if (is_key && is_video) {
// 遇到关键帧flush掉前面的数据确保关键帧为该组数据的第一帧确保GOP缓存有效 [AUTO-TRANSLATED:e2ebbf9b]
// Encounter a key frame, flush the previous data, ensure that the key frame is the first frame of this group of data, and ensure the GOP cache is valid.
flush_flag = true;
} else {
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
if (mergeWriteMS <= 0) {
// 关闭了合并写或者合并写阈值小于等于0 [AUTO-TRANSLATED:2397b647]
// Merge writing is closed or the merge writing threshold is less than or equal to 0.
flush_flag = isFlushAble_default(is_video, _last_stamp[is_video], new_stamp, cache_size);
} else {
flush_flag = isFlushAble_merge(is_video, _last_stamp[is_video], new_stamp, cache_size, mergeWriteMS);
}
}
if (flush_flag) {
_last_stamp[is_video] = new_stamp;
}
return flush_flag;
}
} /* namespace mediakit */

View File

@ -0,0 +1,580 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MEDIASOURCE_H
#define ZLMEDIAKIT_MEDIASOURCE_H
#include <string>
#include <atomic>
#include <memory>
#include <functional>
#include "Util/mini.h"
#include "Network/Socket.h"
#include "Extension/Track.h"
#include "Record/Recorder.h"
namespace toolkit {
class Session;
} // namespace toolkit
namespace mediakit {
enum class MediaOriginType : uint8_t {
unknown = 0,
rtmp_push ,
rtsp_push,
rtp_push,
pull,
ffmpeg_pull,
mp4_vod,
device_chn,
rtc_push,
srt_push
};
std::string getOriginTypeString(MediaOriginType type);
class MediaSource;
class RtpProcess;
class MultiMediaSourceMuxer;
class MediaSourceEvent {
public:
friend class MediaSource;
class NotImplemented : public std::runtime_error {
public:
template<typename ...T>
NotImplemented(T && ...args) : std::runtime_error(std::forward<T>(args)...) {}
};
virtual ~MediaSourceEvent() = default;
// 获取媒体源类型 [AUTO-TRANSLATED:34290a69]
// Get media source type
virtual MediaOriginType getOriginType(MediaSource &sender) const { return MediaOriginType::unknown; }
// 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795]
// Get media source url or file path
virtual std::string getOriginUrl(MediaSource &sender) const;
// 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910]
// Get media source client related information
virtual std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const { return nullptr; }
// 通知拖动进度条 [AUTO-TRANSLATED:561b17f7]
// Notify drag progress bar
virtual bool seekTo(MediaSource &sender, uint32_t stamp) { return false; }
// 通知暂停或恢复 [AUTO-TRANSLATED:ee3c219f]
// Notify pause or resume
virtual bool pause(MediaSource &sender, bool pause) { return false; }
// 通知倍数 [AUTO-TRANSLATED:8f1dab15]
// Notify multiple times
virtual bool speed(MediaSource &sender, float speed) { return false; }
// 通知其停止产生流 [AUTO-TRANSLATED:62c9022c]
// Notify it to stop generating streams
virtual bool close(MediaSource &sender) { return false; }
// 获取观看总人数,此函数一般强制重载 [AUTO-TRANSLATED:1da20a10]
// Get the total number of viewers, this function is generally forced to overload
virtual int totalReaderCount(MediaSource &sender) { throw NotImplemented(toolkit::demangle(typeid(*this).name()) + "::totalReaderCount not implemented"); }
// 通知观看人数变化 [AUTO-TRANSLATED:bad89528]
// Notify the change in the number of viewers
virtual void onReaderChanged(MediaSource &sender, int size);
// 流注册或注销事件 [AUTO-TRANSLATED:2cac8178]
// Stream registration or deregistration event
virtual void onRegist(MediaSource &sender, bool regist) {}
// 获取丢包率 [AUTO-TRANSLATED:ec61b378]
// Get packet loss rate
virtual float getLossRate(MediaSource &sender, TrackType type) { return -1; }
// 获取所在线程, 此函数一般强制重载 [AUTO-TRANSLATED:71c99afb]
// Get the current thread, this function is generally forced to overload
virtual toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) { throw NotImplemented(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller not implemented"); }
// //////////////////////仅供MultiMediaSourceMuxer对象继承//////////////////////// [AUTO-TRANSLATED:6e810d1f]
// //////////////////////Only for MultiMediaSourceMuxer object inheritance////////////////////////
// 开启或关闭录制 [AUTO-TRANSLATED:3817e390]
// Start or stop recording
virtual bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const std::string &custom_path, size_t max_second) { return false; };
// 获取录制状态 [AUTO-TRANSLATED:a0499880]
// Get recording status
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }
// 获取所有track相关信息 [AUTO-TRANSLATED:2141be42]
// Get all track related information
virtual std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
// 获取MultiMediaSourceMuxer对象 [AUTO-TRANSLATED:2de96d44]
// Get MultiMediaSourceMuxer object
virtual std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) const { return nullptr; }
// 获取RtpProcess对象 [AUTO-TRANSLATED:c6b7da43]
// Get RtpProcess object
virtual std::shared_ptr<RtpProcess> getRtpProcess(MediaSource &sender) const { return nullptr; }
class SendRtpArgs {
public:
enum DataType {
kRtpES = 0, // 发送ES流
kRtpPS = 1, // 发送PS流
kRtpTS = 2 // 发送TS流
};
enum ConType {
kTcpActive = 0, // tcp主动模式tcp客户端主动连接对方并发送rtp
kUdpActive = 1, // udp主动模式主动发送数据给对方
kTcpPassive = 2, // tcp被动模式tcp服务器等待对方连接并回复rtp
kUdpPassive = 3 // udp被动方式等待对方发送nat打洞包然后回复rtp至打洞包源地址
};
// rtp类型 [AUTO-TRANSLATED:acca40ab]
// Rtp type
DataType data_type = kRtpPS;
// 连接类型 [AUTO-TRANSLATED:8ad5c881]
// Connection type
ConType con_type = kUdpActive;
// 发送es流时指定是否只发送纯音频流 [AUTO-TRANSLATED:470c761e]
// Specify whether to send only pure audio stream when sending es stream
bool only_audio = false;
// rtp payload type
uint8_t pt = 96;
// 是否支持同ssrc多服务器发送 [AUTO-TRANSLATED:9d817af2]
// Whether to support multiple servers sending with the same ssrc
bool ssrc_multi_send = false;
// 指定rtp ssrc [AUTO-TRANSLATED:7366c6f9]
// Specify rtp ssrc
std::string ssrc;
// 指定本地发送端口 [AUTO-TRANSLATED:f5d92f40]
// Specify local sending port
uint16_t src_port = 0;
// 发送目标端口 [AUTO-TRANSLATED:096b5574]
// Send target port
uint16_t dst_port;
// 发送目标主机地址可以是ip或域名 [AUTO-TRANSLATED:2c872f2e]
// Send target host address, can be ip or domain name
std::string dst_url;
// udp发送时是否开启rr rtcp接收超时判断 [AUTO-TRANSLATED:784982bd]
// When sending udp, whether to enable rr rtcp receive timeout judgment
bool udp_rtcp_timeout = false;
// passive被动、tcp主动模式超时时间 [AUTO-TRANSLATED:8886d475]
// Passive passive, tcp active mode timeout time
uint32_t close_delay_ms = 0;
// udp 发送时rr rtcp包接收超时时间单位毫秒 [AUTO-TRANSLATED:9f0d91d9]
// When sending udp, rr rtcp packet receive timeout time, in milliseconds
uint32_t rtcp_timeout_ms = 30 * 1000;
// udp 发送时发送sr rtcp包间隔单位毫秒 [AUTO-TRANSLATED:c87bfed4]
// When sending udp, send sr rtcp packet interval, in milliseconds
uint32_t rtcp_send_interval_ms = 5 * 1000;
// 发送rtp同时接收一般用于双向语言对讲, 如果不为空,说明开启接收 [AUTO-TRANSLATED:f4c18084]
// Send rtp while receiving, generally used for two-way language intercom, if not empty, it means receiving is enabled
std::string recv_stream_id;
std::string recv_stream_app;
std::string recv_stream_vhost;
};
// 开始发送ps-rtp [AUTO-TRANSLATED:a51796fa]
// Start sending ps-rtp
virtual void startSendRtp(MediaSource &sender, const SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) { cb(0, toolkit::SockException(toolkit::Err_other, "not implemented"));};
// 停止发送ps-rtp [AUTO-TRANSLATED:952d2b35]
// Stop sending ps-rtp
virtual bool stopSendRtp(MediaSource &sender, const std::string &ssrc) {return false; }
private:
toolkit::Timer::Ptr _async_close_timer;
};
template <typename MAP, typename KEY, typename TYPE>
static void getArgsValue(const MAP &allArgs, const KEY &key, TYPE &value) {
auto val = ((MAP &)allArgs)[key];
if (!val.empty()) {
value = (TYPE)val;
}
}
template <typename KEY, typename TYPE>
static void getArgsValue(const toolkit::mINI &allArgs, const KEY &key, TYPE &value) {
auto it = allArgs.find(key);
if (it != allArgs.end()) {
value = (TYPE)it->second;
}
}
class ProtocolOption {
public:
ProtocolOption();
enum {
kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变
kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理)
kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
};
// 时间戳类型 [AUTO-TRANSLATED:7d2779e1]
// Timestamp type
int modify_stamp;
// 转协议是否开启音频 [AUTO-TRANSLATED:220dddfa]
// Whether to enable audio for protocol conversion
bool enable_audio;
// 添加静音音频,在关闭音频时,此开关无效 [AUTO-TRANSLATED:47c0ec8e]
// Add mute audio, this switch is invalid when audio is closed
bool add_mute_audio;
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) [AUTO-TRANSLATED:dba7ab70]
// Whether to close directly when no one is watching (instead of returning close through the on_none_reader hook)
// 此配置置1时此流如果无人观看将不触发on_none_reader hook回调 [AUTO-TRANSLATED:a5ead314]
// When this configuration is set to 1, if no one is watching this stream, it will not trigger the on_none_reader hook callback,
// 而是将直接关闭流 [AUTO-TRANSLATED:06887d49]
// but will directly close the stream
bool auto_close;
// 断连续推延时,单位毫秒,默认采用配置文件 [AUTO-TRANSLATED:7a15b12f]
// Delay in milliseconds for continuous pushing, default is using the configuration file
uint32_t continue_push_ms;
// 平滑发送定时器间隔单位毫秒置0则关闭开启后影响cpu性能同时增加内存 [AUTO-TRANSLATED:ad4e306a]
// Smooth sending timer interval, in milliseconds, set to 0 to close; enabling it will affect cpu performance and increase memory at the same time
// 该配置开启后可以解决一些流发送不平滑导致zlmediakit转发也不平滑的问题 [AUTO-TRANSLATED:0f2b1657]
// This configuration can solve some problems where the stream is not sent smoothly, resulting in zlmediakit forwarding not being smooth
uint32_t paced_sender_ms;
// 是否开启转换为hls(mpegts) [AUTO-TRANSLATED:bfc1167a]
// Whether to enable conversion to hls(mpegts)
bool enable_hls;
// 是否开启转换为hls(fmp4) [AUTO-TRANSLATED:20548673]
// Whether to enable conversion to hls(fmp4)
bool enable_hls_fmp4;
// 是否开启MP4录制 [AUTO-TRANSLATED:0157b014]
// Whether to enable MP4 recording
bool enable_mp4;
// 是否开启转换为rtsp/webrtc [AUTO-TRANSLATED:0711cb18]
// Whether to enable conversion to rtsp/webrtc
bool enable_rtsp;
// 是否开启转换为rtmp/flv [AUTO-TRANSLATED:d4774119]
// Whether to enable conversion to rtmp/flv
bool enable_rtmp;
// 是否开启转换为http-ts/ws-ts [AUTO-TRANSLATED:51acc798]
// Whether to enable conversion to http-ts/ws-ts
bool enable_ts;
// 是否开启转换为http-fmp4/ws-fmp4 [AUTO-TRANSLATED:8c96e1e4]
// Whether to enable conversion to http-fmp4/ws-fmp4
bool enable_fmp4;
// hls协议是否按需生成如果hls.segNum配置为0(意味着hls录制)那么hls将一直生成(不管此开关) [AUTO-TRANSLATED:4653b411]
// Whether to generate hls protocol on demand, if hls.segNum is configured to 0 (meaning hls recording), then hls will always be generated (regardless of this switch)
bool hls_demand;
// rtsp[s]协议是否按需生成 [AUTO-TRANSLATED:1c3237b0]
// Whether to generate rtsp[s] protocol on demand
bool rtsp_demand;
// rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成 [AUTO-TRANSLATED:09ed2c30]
// Whether to generate rtmp[s]、http[s]-flv、ws[s]-flv protocol on demand
bool rtmp_demand;
// http[s]-ts协议是否按需生成 [AUTO-TRANSLATED:a0129db3]
// Whether to generate http[s]-ts protocol on demand
bool ts_demand;
// http[s]-fmp4、ws[s]-fmp4协议是否按需生成 [AUTO-TRANSLATED:828d25c7]
// Whether to generate http[s]-fmp4、ws[s]-fmp4 protocol on demand
bool fmp4_demand;
// 是否将mp4录制当做观看者 [AUTO-TRANSLATED:ba351230]
// Whether to treat mp4 recording as a viewer
bool mp4_as_player;
// mp4切片大小单位秒 [AUTO-TRANSLATED:c3fb8ec1]
// MP4 slice size, in seconds
size_t mp4_max_second;
// mp4录制保存路径 [AUTO-TRANSLATED:6d860f27]
// MP4 recording save path
std::string mp4_save_path;
// hls录制保存路径 [AUTO-TRANSLATED:cfa90719]
// HLS recording save path
std::string hls_save_path;
// 支持通过on_publish返回值替换stream_id [AUTO-TRANSLATED:2c4e4997]
// Support replacing stream_id through the return value of on_publish
std::string stream_replace;
// 最大track数 [AUTO-TRANSLATED:2565fd37]
// Maximum number of tracks
size_t max_track = 2;
template <typename MAP>
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
load(allArgs);
}
template <typename MAP>
void load(const MAP &allArgs) {
#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(paced_sender_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);
GET_OPT_VALUE(enable_ts);
GET_OPT_VALUE(enable_fmp4);
GET_OPT_VALUE(hls_demand);
GET_OPT_VALUE(rtsp_demand);
GET_OPT_VALUE(rtmp_demand);
GET_OPT_VALUE(ts_demand);
GET_OPT_VALUE(fmp4_demand);
GET_OPT_VALUE(mp4_max_second);
GET_OPT_VALUE(mp4_as_player);
GET_OPT_VALUE(mp4_save_path);
GET_OPT_VALUE(hls_save_path);
GET_OPT_VALUE(stream_replace);
GET_OPT_VALUE(max_track);
}
};
// 该对象用于拦截感兴趣的MediaSourceEvent事件 [AUTO-TRANSLATED:fd6d0559]
// This object is used to intercept interesting MediaSourceEvent events
class MediaSourceEventInterceptor : public MediaSourceEvent {
public:
void setDelegate(const std::weak_ptr<MediaSourceEvent> &listener);
std::shared_ptr<MediaSourceEvent> getDelegate() const;
MediaOriginType getOriginType(MediaSource &sender) const override;
std::string getOriginUrl(MediaSource &sender) const override;
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
bool seekTo(MediaSource &sender, uint32_t stamp) override;
bool pause(MediaSource &sender, bool pause) override;
bool speed(MediaSource &sender, float speed) override;
bool close(MediaSource &sender) override;
int totalReaderCount(MediaSource &sender) override;
void onReaderChanged(MediaSource &sender, int size) override;
void onRegist(MediaSource &sender, bool regist) override;
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const std::string &custom_path, size_t max_second) override;
bool isRecording(MediaSource &sender, Recorder::type type) override;
std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const override;
void startSendRtp(MediaSource &sender, const SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) override;
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) const override;
std::shared_ptr<RtpProcess> getRtpProcess(MediaSource &sender) const override;
private:
std::weak_ptr<MediaSourceEvent> _listener;
};
/**
* url获取媒体相关信息
* Parse the url to get media information
* [AUTO-TRANSLATED:3b3cfde7]
*/
class MediaInfo: public MediaTuple {
public:
MediaInfo() = default;
MediaInfo(const std::string &url) { parse(url); }
void parse(const std::string &url);
std::string getUrl() const { return schema + "://" + shortUrl(); }
public:
uint16_t port = 0;
std::string full_url;
std::string schema;
std::string host;
};
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b);
/**
* rtsp/rtmp的直播流都源自该对象
* Media source, any rtsp/rtmp live stream originates from this object
* [AUTO-TRANSLATED:658077ad]
*/
class MediaSource: public TrackSource, public std::enable_shared_from_this<MediaSource> {
public:
static MediaSource& NullMediaSource();
using Ptr = std::shared_ptr<MediaSource>;
MediaSource(const std::string &schema, const MediaTuple& tuple);
virtual ~MediaSource();
// //////////////获取MediaSource相关信息//////////////// [AUTO-TRANSLATED:4a520f1f]
// //////////////Get MediaSource information////////////////
// 获取协议类型 [AUTO-TRANSLATED:d6b50c14]
// Get protocol type
const std::string& getSchema() const {
return _schema;
}
const MediaTuple& getMediaTuple() const {
return _tuple;
}
std::string getUrl() const { return _schema + "://" + _tuple.shortUrl(); }
// 获取对象所有权 [AUTO-TRANSLATED:84fb43cd]
// Get object ownership
std::shared_ptr<void> getOwnership();
// 获取所有Track [AUTO-TRANSLATED:59f1c570]
// Get all Tracks
std::vector<Track::Ptr> getTracks(bool ready = true) const override;
// 获取流当前时间戳 [AUTO-TRANSLATED:f65f560a]
// Get the current timestamp of the stream
virtual uint32_t getTimeStamp(TrackType type) { return 0; };
// 设置时间戳 [AUTO-TRANSLATED:2bfce32f]
// Set timestamp
virtual void setTimeStamp(uint32_t stamp) {};
// 获取数据速率单位bytes/s [AUTO-TRANSLATED:c70465c1]
// Get data rate, unit bytes/s
int getBytesSpeed(TrackType type = TrackInvalid);
// 获取流创建GMT unix时间戳单位秒 [AUTO-TRANSLATED:0bbe145e]
// Get the stream creation GMT unix timestamp, unit seconds
uint64_t getCreateStamp() const { return _create_stamp; }
// 获取流上线时间,单位秒 [AUTO-TRANSLATED:a087d56a]
// Get the stream online time, unit seconds
uint64_t getAliveSecond() const;
// //////////////MediaSourceEvent相关接口实现//////////////// [AUTO-TRANSLATED:aa63d949]
// //////////////MediaSourceEvent related interface implementation////////////////
// 设置监听者 [AUTO-TRANSLATED:b9b90b57]
// Set listener
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
// 获取监听者 [AUTO-TRANSLATED:5c9cbb82]
// Get listener
std::weak_ptr<MediaSourceEvent> getListener() const;
// 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数 [AUTO-TRANSLATED:0874fa7c]
// This protocol gets the number of viewers, it may return the number of viewers of this protocol, or it may return the total number of viewers
virtual int readerCount() = 0;
// 观看者个数,包括(hls/rtsp/rtmp) [AUTO-TRANSLATED:6604020f]
// Number of viewers, including (hls/rtsp/rtmp)
virtual int totalReaderCount();
// 获取播放器列表 [AUTO-TRANSLATED:e7691d2b]
// Get the player list
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<toolkit::Any>());
}
virtual bool broadcastMessage(const toolkit::Any &data) { return false; }
// 获取媒体源类型 [AUTO-TRANSLATED:34290a69]
// Get the media source type
MediaOriginType getOriginType() const;
// 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795]
// Get the media source url or file path
std::string getOriginUrl() const;
// 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910]
// Get the media source client information
std::shared_ptr<toolkit::SockInfo> getOriginSock() const;
// 拖动进度条 [AUTO-TRANSLATED:65ee8a83]
// Drag the progress bar
bool seekTo(uint32_t stamp);
// 暂停 [AUTO-TRANSLATED:ffd21ae7]
// Pause
bool pause(bool pause);
// 倍数播放 [AUTO-TRANSLATED:a5e3c1c9]
// Playback speed
bool speed(float speed);
// 关闭该流 [AUTO-TRANSLATED:b3867b98]
// Close the stream
bool close(bool force);
// 该流观看人数变化 [AUTO-TRANSLATED:8e583993]
// The number of viewers of this stream changes
void onReaderChanged(int size);
// 开启或关闭录制 [AUTO-TRANSLATED:3817e390]
// Turn recording on or off
bool setupRecord(Recorder::type type, bool start, const std::string &custom_path, size_t max_second);
// 获取录制状态 [AUTO-TRANSLATED:a0499880]
// Get recording status
bool isRecording(Recorder::type type);
// 开始发送ps-rtp [AUTO-TRANSLATED:a51796fa]
// Start sending ps-rtp
void startSendRtp(const MediaSourceEvent::SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb);
// 停止发送ps-rtp [AUTO-TRANSLATED:952d2b35]
// Stop sending ps-rtp
bool stopSendRtp(const std::string &ssrc);
// 获取丢包率 [AUTO-TRANSLATED:ec61b378]
// Get packet loss rate
float getLossRate(mediakit::TrackType type);
// 获取所在线程 [AUTO-TRANSLATED:75662eb8]
// Get the thread where it is running
toolkit::EventPoller::Ptr getOwnerPoller();
// 获取MultiMediaSourceMuxer对象 [AUTO-TRANSLATED:2de96d44]
// Get the MultiMediaSourceMuxer object
std::shared_ptr<MultiMediaSourceMuxer> getMuxer() const;
// 获取RtpProcess对象 [AUTO-TRANSLATED:c6b7da43]
// Get the RtpProcess object
std::shared_ptr<RtpProcess> getRtpProcess() const;
// //////////////static方法查找或生成MediaSource//////////////// [AUTO-TRANSLATED:c3950036]
// //////////////static methods, find or generate MediaSource////////////////
// 同步查找流 [AUTO-TRANSLATED:97048f1e]
// Synchronously find the stream
static Ptr find(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &id, bool from_mp4 = false);
static Ptr find(const MediaInfo &info, bool from_mp4 = false) {
return find(info.schema, info.vhost, info.app, info.stream, from_mp4);
}
// 忽略schema同步查找流可能返回rtmp/rtsp/hls类型 [AUTO-TRANSLATED:8c061cac]
// Ignore schema, synchronously find the stream, may return rtmp/rtsp/hls type
static Ptr find(const std::string &vhost, const std::string &app, const std::string &stream_id, bool from_mp4 = false);
// 异步查找流 [AUTO-TRANSLATED:4decf738]
// Asynchronously find the stream
static void findAsync(const MediaInfo &info, const std::shared_ptr<toolkit::Session> &session, const std::function<void(const Ptr &src)> &cb);
// 遍历所有流 [AUTO-TRANSLATED:a39b2399]
// Traverse all streams
static void for_each_media(const std::function<void(const Ptr &src)> &cb, const std::string &schema = "", const std::string &vhost = "", const std::string &app = "", const std::string &stream = "");
// 从mp4文件生成MediaSource [AUTO-TRANSLATED:7df9762e]
// Generate MediaSource from mp4 file
static MediaSource::Ptr createFromMP4(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &stream, const std::string &file_path = "", bool check_app = true);
protected:
// 媒体注册 [AUTO-TRANSLATED:dbf5c730]
// Media registration
void regist();
private:
// 媒体注销 [AUTO-TRANSLATED:06a0630a]
// Media unregistration
bool unregist();
// 触发媒体事件 [AUTO-TRANSLATED:0c2f9ae6]
// Trigger media events
void emitEvent(bool regist);
protected:
toolkit::BytesSpeed _speed[TrackMax];
MediaTuple _tuple;
private:
std::atomic_flag _owned { false };
time_t _create_stamp;
toolkit::Ticker _ticker;
std::string _schema;
std::weak_ptr<MediaSourceEvent> _listener;
// 对象个数统计 [AUTO-TRANSLATED:f4a012d0]
// Object count statistics
toolkit::ObjectStatistic<MediaSource> _statistic;
};
} /* namespace mediakit */
#endif //ZLMEDIAKIT_MEDIASOURCE_H

View File

@ -0,0 +1,717 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <math.h>
#include "Common/config.h"
#include "MultiMediaSourceMuxer.h"
using namespace std;
using namespace toolkit;
namespace toolkit {
StatisticImp(mediakit::MultiMediaSourceMuxer);
}
namespace mediakit {
namespace {
class MediaSourceForMuxer : public MediaSource {
public:
MediaSourceForMuxer(const MultiMediaSourceMuxer::Ptr &muxer)
: MediaSource("muxer", muxer->getMediaTuple()) {
MediaSource::setListener(muxer);
}
int readerCount() override { return 0; }
};
} // namespace
class FramePacedSender : public FrameWriterInterface, public std::enable_shared_from_this<FramePacedSender> {
public:
using OnFrame = std::function<void(const Frame::Ptr &frame)>;
// 最小缓存100ms数据 [AUTO-TRANSLATED:7b2fcb0d]
// Minimum cache 100ms data
static constexpr auto kMinCacheMS = 100;
FramePacedSender(uint32_t paced_sender_ms, OnFrame cb) {
_paced_sender_ms = paced_sender_ms;
_cb = std::move(cb);
}
void resetTimer(const EventPoller::Ptr &poller) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
std::weak_ptr<FramePacedSender> weak_self = shared_from_this();
_timer = std::make_shared<Timer>(_paced_sender_ms / 1000.0f, [weak_self]() {
if (auto strong_self = weak_self.lock()) {
strong_self->onTick();
return true;
}
return false;
}, poller);
}
bool inputFrame(const Frame::Ptr &frame) override {
std::lock_guard<std::recursive_mutex> lck(_mtx);
if (!_timer) {
setCurrentStamp(frame->dts());
resetTimer(EventPoller::getCurrentPoller());
}
_cache.emplace_back(frame->dts() + _cache_ms, Frame::getCacheAbleFrame(frame));
return true;
}
private:
void onTick() {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto dst = _cache.empty() ? 0 : _cache.back().first;
while (!_cache.empty()) {
auto &front = _cache.front();
if (getCurrentStamp() < front.first) {
// 还没到消费时间 [AUTO-TRANSLATED:09fb4c3d]
// Not yet time to consume
break;
}
// 时间到了该消费frame了 [AUTO-TRANSLATED:2f007931]
// Time is up, it's time to consume the frame
_cb(front.second);
_cache.pop_front();
}
if (_cache.empty() && dst) {
// 消费太快,需要增加缓存大小 [AUTO-TRANSLATED:c05bfbcd]
// Consumption is too fast, need to increase cache size
setCurrentStamp(dst);
_cache_ms += kMinCacheMS;
}
// 消费太慢需要强制flush数据 [AUTO-TRANSLATED:5613625e]
// Consumption is too slow, need to force flush data
if (_cache.size() > 25 * 5) {
WarnL << "Flush frame paced sender cache: " << _cache.size();
while (!_cache.empty()) {
auto &front = _cache.front();
_cb(front.second);
_cache.pop_front();
}
setCurrentStamp(dst);
}
}
uint64_t getCurrentStamp() { return _ticker.elapsedTime() + _stamp_offset; }
void setCurrentStamp(uint64_t stamp) {
_stamp_offset = stamp;
_ticker.resetTime();
}
private:
uint32_t _paced_sender_ms;
uint32_t _cache_ms = kMinCacheMS;
uint64_t _stamp_offset = 0;
OnFrame _cb;
Ticker _ticker;
Timer::Ptr _timer;
std::recursive_mutex _mtx;
std::list<std::pair<uint64_t, Frame::Ptr>> _cache;
};
static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, const vector<Track::Ptr> &tracks, Recorder::type type, const ProtocolOption &option){
auto recorder = Recorder::createRecorder(type, sender.getMediaTuple(), option);
for (auto &track : tracks) {
recorder->addTrack(track);
}
recorder->addTrackCompleted();
return recorder;
}
static string getTrackInfoStr(const TrackSource *track_src){
_StrPrinter codec_info;
auto tracks = track_src->getTracks(true);
for (auto &track : tracks) {
track->update();
auto codec_type = track->getTrackType();
codec_info << track->getCodecName();
switch (codec_type) {
case TrackAudio : {
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
codec_info << "["
<< audio_track->getAudioSampleRate() << "/"
<< audio_track->getAudioChannel() << "/"
<< audio_track->getAudioSampleBit() << "] ";
break;
}
case TrackVideo : {
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
codec_info << "["
<< video_track->getVideoWidth() << "/"
<< video_track->getVideoHeight() << "/"
<< round(video_track->getVideoFps()) << "] ";
break;
}
default:
break;
}
}
return 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()) {
return ret;
}
return _tuple.shortUrl();
}
void MultiMediaSourceMuxer::forEachRtpSender(const std::function<void(const std::string &ssrc)> &cb) const {
for (auto &pr : _rtp_sender) {
cb(pr.first);
}
}
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) {
if (!option.stream_replace.empty()) {
// 支持在on_publish hook中替换stream_id [AUTO-TRANSLATED:375eb2ff]
// Support replacing stream_id in on_publish hook
_tuple.stream = option.stream_replace;
}
_poller = EventPollerPool::Instance().getPoller();
_create_in_poller = _poller->isCurrentThread();
_option = option;
_dur_sec = dur_sec;
setMaxTrackCount(option.max_track);
if (option.enable_rtmp) {
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(_tuple, option, std::make_shared<TitleMeta>(dur_sec));
}
if (option.enable_rtsp) {
_rtsp = std::make_shared<RtspMediaSourceMuxer>(_tuple, option, std::make_shared<TitleSdp>(dur_sec));
}
if (option.enable_hls) {
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _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 = dynamic_pointer_cast<TSMediaSourceMuxer>(Recorder::createRecorder(Recorder::type_ts, _tuple, option));
}
if (option.enable_fmp4) {
_fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option));
}
// 音频相关设置 [AUTO-TRANSLATED:6ee58d57]
// Audio related settings
enableAudio(option.enable_audio);
enableMuteAudio(option.add_mute_audio);
}
void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
setDelegate(listener);
auto self = shared_from_this();
// 拦截事件 [AUTO-TRANSLATED:100ca068]
// Intercept events
if (_rtmp) {
_rtmp->setListener(self);
}
if (_rtsp) {
_rtsp->setListener(self);
}
if (_ts) {
_ts->setListener(self);
}
if (_fmp4) {
_fmp4->setListener(self);
}
if (_hls_fmp4) {
_hls_fmp4->setListener(self);
}
if (_hls) {
_hls->setListener(self);
}
}
void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &listener) {
_track_listener = listener;
}
int MultiMediaSourceMuxer::totalReaderCount() const {
return (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) +
(_fmp4 ? _fmp4->readerCount() : 0) +
(_mp4 ? _option.mp4_as_player : 0) +
(_hls ? _hls->readerCount() : 0) +
(_hls_fmp4 ? _hls_fmp4->readerCount() : 0) +
(_ring ? _ring->readerCount() : 0);
}
void MultiMediaSourceMuxer::setTimeStamp(uint32_t stamp) {
if (_rtmp) {
_rtmp->setTimeStamp(stamp);
}
if (_rtsp) {
_rtsp->setTimeStamp(stamp);
}
}
int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
auto listener = getDelegate();
if (!listener) {
return totalReaderCount();
}
try {
return listener->totalReaderCount(sender);
} catch (MediaSourceEvent::NotImplemented &) {
// listener未重载totalReaderCount [AUTO-TRANSLATED:f098007e]
// Listener did not reload totalReaderCount
return totalReaderCount();
}
}
// 此函数可能跨线程调用 [AUTO-TRANSLATED:e8c5f74d]
// This function may be called across threads
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录制触发观看人数变化相关事件 [AUTO-TRANSLATED:b63a8deb]
// Turn on/off mp4 recording, trigger events related to changes in the number of viewers
onReaderChanged(sender, totalReaderCount());
}
});
switch (type) {
case Recorder::type_hls : {
if (start && !_hls) {
// 开始录制 [AUTO-TRANSLATED:36d99250]
// Start recording
_option.hls_save_path = custom_path;
auto hls = dynamic_pointer_cast<HlsRecorder>(makeRecorder(sender, getTracks(), type, _option));
if (hls) {
// 设置HlsMediaSource的事件监听器 [AUTO-TRANSLATED:69990c92]
// Set the event listener for HlsMediaSource
hls->setListener(shared_from_this());
}
_hls = hls;
} else if (!start && _hls) {
// 停止录制 [AUTO-TRANSLATED:3dee9292]
// Stop recording
_hls = nullptr;
}
return true;
}
case Recorder::type_mp4 : {
if (start && !_mp4) {
// 开始录制 [AUTO-TRANSLATED:36d99250]
// Start recording
_option.mp4_save_path = custom_path;
_option.mp4_max_second = max_second;
_mp4 = makeRecorder(sender, getTracks(), type, _option);
} else if (!start && _mp4) {
// 停止录制 [AUTO-TRANSLATED:3dee9292]
// Stop recording
_mp4 = nullptr;
}
return true;
}
case Recorder::type_hls_fmp4: {
if (start && !_hls_fmp4) {
// 开始录制 [AUTO-TRANSLATED:36d99250]
// Start recording
_option.hls_save_path = custom_path;
auto hls = dynamic_pointer_cast<HlsFMP4Recorder>(makeRecorder(sender, getTracks(), type, _option));
if (hls) {
// 设置HlsMediaSource的事件监听器 [AUTO-TRANSLATED:69990c92]
// Set the event listener for HlsMediaSource
hls->setListener(shared_from_this());
}
_hls_fmp4 = hls;
} else if (!start && _hls_fmp4) {
// 停止录制 [AUTO-TRANSLATED:3dee9292]
// Stop recording
_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;
}
}
// 此函数可能跨线程调用 [AUTO-TRANSLATED:e8c5f74d]
// This function may be called across threads
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
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;
}
}
void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceEvent::SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) {
#if defined(ENABLE_RTPPROXY)
createGopCacheIfNeed();
auto ring = _ring;
auto ssrc = args.ssrc;
auto ssrc_multi_send = args.ssrc_multi_send;
auto tracks = getTracks(false);
auto poller = getOwnerPoller(sender);
auto rtp_sender = std::make_shared<RtpSender>(poller);
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
rtp_sender->setOnClose([weak_self, ssrc](const toolkit::SockException &ex) {
if (auto strong_self = weak_self.lock()) {
// 可能归属线程发生变更 [AUTO-TRANSLATED:2b379e30]
// The owning thread may change
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex;
strong_self->_rtp_sender.erase(ssrc);
NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
});
}
});
rtp_sender->startSend(args, [ssrc,ssrc_multi_send, weak_self, rtp_sender, cb, tracks, ring, poller](uint16_t local_port, const SockException &ex) mutable {
cb(local_port, ex);
auto strong_self = weak_self.lock();
if (!strong_self || ex) {
return;
}
for (auto &track : tracks) {
rtp_sender->addTrack(track);
}
rtp_sender->addTrackCompleted();
auto reader = ring->attach(poller);
reader->setReadCB([rtp_sender](const Frame::Ptr &frame) {
rtp_sender->inputFrame(frame);
});
// 可能归属线程发生变更 [AUTO-TRANSLATED:2b379e30]
// The owning thread may change
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
if(!ssrc_multi_send) {
strong_self->_rtp_sender.erase(ssrc);
}
strong_self->_rtp_sender.emplace(ssrc,reader);
});
});
#else
cb(0, SockException(Err_other, "该功能未启用编译时请打开ENABLE_RTPPROXY宏"));
#endif//ENABLE_RTPPROXY
}
bool MultiMediaSourceMuxer::stopSendRtp(MediaSource &sender, const string &ssrc) {
#if defined(ENABLE_RTPPROXY)
if (ssrc.empty()) {
// 关闭全部 [AUTO-TRANSLATED:ffaadfda]
// Close all
auto size = _rtp_sender.size();
_rtp_sender.clear();
return size;
}
// 关闭特定的 [AUTO-TRANSLATED:2286322a]
// Close specific
return _rtp_sender.erase(ssrc);
#else
return false;
#endif//ENABLE_RTPPROXY
}
vector<Track::Ptr> MultiMediaSourceMuxer::getMediaTracks(MediaSource &sender, bool trackReady) const {
return getTracks(trackReady);
}
EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
auto listener = getDelegate();
if (!listener) {
return _poller;
}
try {
auto ret = listener->getOwnerPoller(sender);
if (ret != _poller) {
WarnL << "OwnerPoller changed " << _poller->getThreadName() << " -> " << ret->getThreadName() << " : " << shortUrl();
_poller = ret;
if (_paced_sender) {
_paced_sender->resetTimer(_poller);
}
}
return ret;
} catch (MediaSourceEvent::NotImplemented &) {
// listener未重载getOwnerPoller [AUTO-TRANSLATED:0ebf2e53]
// Listener did not reload getOwnerPoller
return _poller;
}
}
std::shared_ptr<MultiMediaSourceMuxer> MultiMediaSourceMuxer::getMuxer(MediaSource &sender) const {
return const_cast<MultiMediaSourceMuxer*>(this)->shared_from_this();
}
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
auto &stamp = _stamps[track->getIndex()];
if (_dur_sec > 0.01) {
// 点播 [AUTO-TRANSLATED:f0b0f74a]
// On-demand
stamp.setPlayBack();
}
bool ret = false;
if (_rtmp) {
ret = _rtmp->addTrack(track) ? true : ret;
}
if (_rtsp) {
ret = _rtsp->addTrack(track) ? true : ret;
}
if (_ts) {
ret = _ts->addTrack(track) ? true : ret;
}
if (_fmp4) {
ret = _fmp4->addTrack(track) ? true : ret;
}
if (_hls) {
ret = _hls->addTrack(track) ? true : ret;
}
if (_hls_fmp4) {
ret = _hls_fmp4->addTrack(track) ? true : ret;
}
if (_mp4) {
ret = _mp4->addTrack(track) ? true : ret;
}
return ret;
}
void MultiMediaSourceMuxer::onAllTrackReady() {
CHECK(!_create_in_poller || getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread());
if (_option.paced_sender_ms) {
std::weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
_paced_sender = std::make_shared<FramePacedSender>(_option.paced_sender_ms, [weak_self](const Frame::Ptr &frame) {
if (auto strong_self = weak_self.lock()) {
strong_self->onTrackFrame_l(frame);
}
});
}
setMediaListener(getDelegate());
if (_rtmp) {
_rtmp->addTrackCompleted();
}
if (_rtsp) {
_rtsp->addTrackCompleted();
}
if (_ts) {
_ts->addTrackCompleted();
}
if (_mp4) {
_mp4->addTrackCompleted();
}
if (_fmp4) {
_fmp4->addTrackCompleted();
}
if (_hls) {
_hls->addTrackCompleted();
}
if (_hls_fmp4) {
_hls_fmp4->addTrackCompleted();
}
auto listener = _track_listener.lock();
if (listener) {
listener->onAllTrackReady();
}
#if defined(ENABLE_RTPPROXY)
GET_CONFIG(bool, gop_cache, RtpProxy::kGopCache);
if (gop_cache) {
createGopCacheIfNeed();
}
#endif
Stamp *first = nullptr;
for (auto &pr : _stamps) {
if (!first) {
first = &pr.second;
} else {
pr.second.syncTo(*first);
}
}
InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this);
}
void MultiMediaSourceMuxer::createGopCacheIfNeed() {
if (_ring) {
return;
}
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
auto src = std::make_shared<MediaSourceForMuxer>(weak_self.lock());
_ring = std::make_shared<RingType>(1024, [weak_self, src](int size) {
if (auto strong_self = weak_self.lock()) {
// 切换到归属线程 [AUTO-TRANSLATED:abcf859b]
// Switch to the owning thread
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
strong_self->onReaderChanged(*src, strong_self->totalReaderCount());
});
}
});
}
void MultiMediaSourceMuxer::resetTracks() {
MediaSink::resetTracks();
if (_rtmp) {
_rtmp->resetTracks();
}
if (_rtsp) {
_rtsp->resetTracks();
}
if (_ts) {
_ts->resetTracks();
}
if (_fmp4) {
_fmp4->resetTracks();
}
if (_hls_fmp4) {
_hls_fmp4->resetTracks();
}
if (_hls) {
_hls->resetTracks();
}
if (_mp4) {
_mp4->resetTracks();
}
}
bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
auto frame = frame_in;
if (_option.modify_stamp != ProtocolOption::kModifyStampOff) {
// 时间戳不采用原始的绝对时间戳 [AUTO-TRANSLATED:8beb3bf7]
// Timestamp does not use the original absolute timestamp
frame = std::make_shared<FrameStamp>(frame, _stamps[frame->getIndex()], _option.modify_stamp);
}
return _paced_sender ? _paced_sender->inputFrame(frame) : onTrackFrame_l(frame);
}
bool MultiMediaSourceMuxer::onTrackFrame_l(const Frame::Ptr &frame_in) {
auto frame = frame_in;
bool ret = false;
if (_rtmp) {
ret = _rtmp->inputFrame(frame) ? true : ret;
}
if (_rtsp) {
ret = _rtsp->inputFrame(frame) ? true : ret;
}
if (_ts) {
ret = _ts->inputFrame(frame) ? true : ret;
}
if (_hls) {
ret = _hls->inputFrame(frame) ? true : ret;
}
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;
}
if (_ring) {
// 此场景由于直接转发可能存在切换线程引起的数据被缓存在管道所以需要CacheAbleFrame [AUTO-TRANSLATED:528afbb7]
// In this scenario, due to direct forwarding, there may be data cached in the pipeline due to thread switching, so CacheAbleFrame is needed
frame = Frame::getCacheAbleFrame(frame);
if (frame->getTrackType() == TrackVideo) {
// 视频时遇到第一帧配置帧或关键帧则标记为gop开始处 [AUTO-TRANSLATED:66247aa8]
// When it is a video, if the first frame configuration frame or key frame is encountered, it is marked as the beginning of the GOP
auto video_key_pos = frame->keyFrame() || frame->configFrame();
_ring->write(frame, video_key_pos && !_video_key_pos);
if (!frame->dropAble()) {
_video_key_pos = video_key_pos;
}
} else {
// 没有视频时设置is_key为true目的是关闭gop缓存 [AUTO-TRANSLATED:f3223755]
// When there is no video, set is_key to true to disable gop caching
_ring->write(frame, !haveVideo());
}
}
return ret;
}
bool MultiMediaSourceMuxer::isEnabled(){
GET_CONFIG(uint32_t, stream_none_reader_delay_ms, General::kStreamNoneReaderDelayMS);
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
// 无人观看时,每次检查是否真的无人观看 [AUTO-TRANSLATED:48bc59c6]
// When no one is watching, check each time if there is really no one watching
// 有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能) [AUTO-TRANSLATED:a7dfddc4]
// When someone is watching, check again after a certain delay to see if no one is watching (save performance)
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
(_rtsp ? _rtsp->isEnabled() : false) ||
(_ts ? _ts->isEnabled() : false) ||
(_fmp4 ? _fmp4->isEnabled() : false) ||
(_ring ? (bool)_ring->readerCount() : false) ||
(_hls ? _hls->isEnabled() : false) ||
(_hls_fmp4 ? _hls_fmp4->isEnabled() : false) ||
_mp4;
if (_is_enable) {
// 无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍所以刷新计数器无意义且浪费cpu [AUTO-TRANSLATED:03ab47cf]
// When no one is watching, do not refresh the timer, because each time no one is watching, it will be checked, so refreshing the counter is meaningless and wastes cpu
_last_check.resetTime();
}
}
return _is_enable;
}
}//namespace mediakit

View File

@ -0,0 +1,264 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
#define ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
#include "Common/Stamp.h"
#include "Common/MediaSource.h"
#include "Common/MediaSink.h"
#include "Record/Recorder.h"
#include "Rtp/RtpSender.h"
#include "Record/HlsRecorder.h"
#include "Record/HlsMediaSource.h"
#include "Rtsp/RtspMediaSourceMuxer.h"
#include "Rtmp/RtmpMediaSourceMuxer.h"
#include "TS/TSMediaSourceMuxer.h"
#include "FMP4/FMP4MediaSourceMuxer.h"
namespace mediakit {
class MultiMediaSourceMuxer : public MediaSourceEventInterceptor, public MediaSink, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
public:
using Ptr = std::shared_ptr<MultiMediaSourceMuxer>;
using RingType = toolkit::RingBuffer<Frame::Ptr>;
class Listener {
public:
virtual ~Listener() = default;
virtual void onAllTrackReady() = 0;
};
MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption());
/**
*
* @param listener
* Set event listener
* @param listener Listener
* [AUTO-TRANSLATED:d829419b]
*/
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
/**
* Track就绪事件监听器
* @param listener
* Set Track ready event listener
* @param listener Event listener
* [AUTO-TRANSLATED:64262ac5]
*/
void setTrackListener(const std::weak_ptr<Listener> &listener);
/**
*
* Return the total number of consumers
* [AUTO-TRANSLATED:5eaac131]
*/
int totalReaderCount() const;
/**
* ()
* Determine whether it is effective (whether it is being converted to another protocol)
* [AUTO-TRANSLATED:ca92165c]
*/
bool isEnabled();
/**
* MediaSource时间戳
* @param stamp
* Set MediaSource timestamp
* @param stamp Timestamp
* [AUTO-TRANSLATED:a75cc2fa]
*/
void setTimeStamp(uint32_t stamp);
/**
* track
* Reset track
* [AUTO-TRANSLATED:95dc0b4f]
*/
void resetTracks() override;
/////////////////////////////////MediaSourceEvent override/////////////////////////////////
/**
*
* @param sender
* @return
* Total number of viewers
* @param sender Event sender
* @return Total number of viewers
* [AUTO-TRANSLATED:f4d7146c]
*/
int totalReaderCount(MediaSource &sender) override;
/**
*
* @param type
* @param start
* @param custom_path
* @return
* Set recording status
* @param type Recording type
* @param start Start or stop
* @param custom_path Specify a custom path when recording is enabled
* @return Whether the setting is successful
* [AUTO-TRANSLATED:cb1fd8a9]
*/
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const std::string &custom_path, size_t max_second) override;
/**
*
* @param type
* @return
* Get recording status
* @param type Recording type
* @return Recording status
* [AUTO-TRANSLATED:798afa71]
*/
bool isRecording(MediaSource &sender, Recorder::type type) override;
/**
* ps-rtp流
* @param dst_url ip或域名
* @param dst_port
* @param ssrc rtp的ssrc
* @param is_udp udp
* @param cb
* Start sending ps-rtp stream
* @param dst_url Target ip or domain name
* @param dst_port Target port
* @param ssrc rtp's ssrc
* @param is_udp Whether it is udp
* @param cb Start success or failure callback
* [AUTO-TRANSLATED:620416c2]
*/
void startSendRtp(MediaSource &sender, const MediaSourceEvent::SendRtpArgs &args, const std::function<void(uint16_t, const toolkit::SockException &)> cb) override;
/**
* ps-rtp发送
* @return
* Stop ps-rtp sending
* @return Whether it is successful
* [AUTO-TRANSLATED:b91e2055]
*/
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
/**
* Track
* @param trackReady track
* @return Track
* Get all Tracks
* @param trackReady Whether to filter out unready tracks
* @return All Tracks
* [AUTO-TRANSLATED:53755f5d]
*/
std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const override;
/**
* 线
* Get the thread it belongs to
* [AUTO-TRANSLATED:a4dc847e]
*/
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
/**
*
* Get this object
* [AUTO-TRANSLATED:5e119bb3]
*/
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) const override;
const ProtocolOption &getOption() const;
const MediaTuple &getMediaTuple() const;
std::string shortUrl() const;
void forEachRtpSender(const std::function<void(const std::string &ssrc)> &cb) const;
protected:
/////////////////////////////////MediaSink override/////////////////////////////////
/**
* track已经准备好ready()true
* sps pps等相关信息了
* @param track
* A certain track is ready, its ready() status returns true,
* This means that you can get information such as sps pps, etc.
* @param track
* [AUTO-TRANSLATED:05659d48]
*/
bool onTrackReady(const Track::Ptr & track) override;
/**
* Track已经准备好
* All Tracks are ready,
* [AUTO-TRANSLATED:c54d02e2]
*/
void onAllTrackReady() override;
/**
* Track输出frameonAllTrackReady触发后才会调用此方法
* @param frame
* A certain Track outputs a frame, this method will be called after onAllTrackReady is triggered
* @param frame
* [AUTO-TRANSLATED:debbd247]
*/
bool onTrackFrame(const Frame::Ptr &frame) override;
bool onTrackFrame_l(const Frame::Ptr &frame);
private:
void createGopCacheIfNeed();
private:
bool _is_enable = false;
bool _create_in_poller = false;
bool _video_key_pos = false;
float _dur_sec;
std::shared_ptr<class FramePacedSender> _paced_sender;
MediaTuple _tuple;
ProtocolOption _option;
toolkit::Ticker _last_check;
std::unordered_map<int, Stamp> _stamps;
std::weak_ptr<Listener> _track_listener;
std::unordered_multimap<std::string, RingType::RingReader::Ptr> _rtp_sender;
FMP4MediaSourceMuxer::Ptr _fmp4;
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;
// 对象个数统计 [AUTO-TRANSLATED:3b43e8c2]
// Object count statistics
toolkit::ObjectStatistic<MultiMediaSourceMuxer> _statistic;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_PACKET_CACHE_H_
#define ZLMEDIAKIT_PACKET_CACHE_H_
#include "Common/config.h"
#include "Util/List.h"
namespace mediakit {
// / 缓存刷新策略类 [AUTO-TRANSLATED:bd941d15]
// / Cache refresh strategy class
class FlushPolicy {
public:
bool isFlushAble(bool is_video, bool is_key, uint64_t new_stamp, size_t cache_size);
private:
// 音视频的最后时间戳 [AUTO-TRANSLATED:957d18ed]
// Last timestamp of audio and video
uint64_t _last_stamp[2] = { 0, 0 };
};
// / 合并写缓存模板 [AUTO-TRANSLATED:25cde944]
// / Merge write cache template
// / \tparam packet 包类型 [AUTO-TRANSLATED:43085d9b]
// / \tparam packet Packet type
// / \tparam policy 刷新缓存策略 [AUTO-TRANSLATED:c5ac29a7]
// / \tparam policy Refresh cache strategy
// / \tparam packet_list 包缓存类型 [AUTO-TRANSLATED:a434e7fe]
// / \tparam packet_list Packet cache type
template<typename packet, typename policy = FlushPolicy, typename packet_list = toolkit::List<std::shared_ptr<packet> > >
class PacketCache {
public:
PacketCache() { _cache = std::make_shared<packet_list>(); }
virtual ~PacketCache() = default;
void inputPacket(uint64_t stamp, bool is_video, std::shared_ptr<packet> pkt, bool key_pos) {
bool flag = flushImmediatelyWhenCloseMerge();
if (!flag && _policy.isFlushAble(is_video, key_pos, stamp, _cache->size())) {
flush();
}
// 追加数据到最后 [AUTO-TRANSLATED:e24ccfb6]
// Append data to the end
_cache->emplace_back(std::move(pkt));
if (key_pos) {
_key_pos = key_pos;
}
if (flag) {
flush();
}
}
void flush() {
if (_cache->empty()) {
return;
}
onFlush(std::move(_cache), _key_pos);
_cache = std::make_shared<packet_list>();
_key_pos = false;
}
virtual void clearCache() {
_cache->clear();
}
virtual void onFlush(std::shared_ptr<packet_list>, bool key_pos) = 0;
private:
bool flushImmediatelyWhenCloseMerge() {
// 一般的协议关闭合并写时立即刷新缓存这样可以减少一帧的延时但是rtp例外 [AUTO-TRANSLATED:54eba701]
// Generally, when the protocol closes the merge write, the cache is refreshed immediately, which can reduce the delay of one frame, but RTP is an exception.
// 因为rtp的包很小一个RtpPacket包中也不是完整的一帧图像所以在关闭合并写时 [AUTO-TRANSLATED:b219082d]
// Because the RTP packet is very small, and a RtpPacket does not contain a complete frame of image, so when closing the merge write,
// 还是有必要缓冲一帧的rtp(也就是时间戳相同的rtp)再输出,这样虽然会增加一帧的延时 [AUTO-TRANSLATED:27c7ee8b]
// It is still necessary to buffer one frame of RTP (that is, RTP with the same timestamp) before outputting. Although this will increase the delay of one frame,
// 但是却对性能提升很大,这样做还是比较划算的 [AUTO-TRANSLATED:80eab719]
// But it greatly improves performance, so it is still worthwhile to do so.
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
GET_CONFIG(int, rtspLowLatency, Rtsp::kLowLatency);
return std::is_same<packet, RtpPacket>::value ? rtspLowLatency : (mergeWriteMS <= 0);
}
private:
bool _key_pos = false;
policy _policy;
std::shared_ptr<packet_list> _cache;
};
}
#endif //ZLMEDIAKIT_PACKET_CACHE_H_

View File

@ -0,0 +1,358 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <cinttypes>
#include "Parser.h"
#include "strCoding.h"
#include "Util/base64.h"
#include "Network/sockutil.h"
#include "Common/macros.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
string findSubString(const char *buf, const char *start, const char *end, size_t buf_size) {
if (buf_size <= 0) {
buf_size = strlen(buf);
}
auto msg_start = buf;
auto msg_end = buf + buf_size;
size_t len = 0;
if (start != NULL) {
len = strlen(start);
msg_start = strstr(buf, start);
}
if (msg_start == NULL) {
return "";
}
msg_start += len;
if (end != NULL) {
msg_end = strstr(msg_start, end);
if (msg_end == NULL) {
return "";
}
}
return string(msg_start, msg_end);
}
void Parser::parse(const char *buf, size_t size) {
clear();
auto ptr = buf;
while (true) {
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 - ptr);
auto next_blank = strchr(blank + 1, ' ');
CHECK(next_blank && next_blank < next_line);
_url.assign(blank + 1, next_blank);
auto pos = _url.find('?');
if (pos != string::npos) {
_params = _url.substr(pos + 1);
_url_args = parseArgs(_params);
_url = _url.substr(0, pos);
}
_protocol = std::string(next_blank + 1, next_line);
} else {
auto pos = strchr(ptr, ':');
CHECK(pos > ptr && pos < next_line);
std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
std::string value;
if (pos[1] == ' ') {
value.assign(pos + 2, next_line);
} else {
value.assign(pos + 1, next_line);
}
_headers.emplace_force(trim(std::move(key)), trim(std::move(value)));
}
ptr = next_line + offset;
if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕
_content.assign(ptr + 2, buf + size);
break;
}
}
}
const string &Parser::method() const {
return _method;
}
const string &Parser::url() const {
return _url;
}
const std::string &Parser::status() const {
return url();
}
string Parser::fullUrl() const {
if (_params.empty()) {
return _url;
}
return _url + "?" + _params;
}
const string &Parser::protocol() const {
return _protocol;
}
const std::string &Parser::statusStr() const {
return protocol();
}
static std::string kNull;
const string &Parser::operator[](const char *name) const {
auto it = _headers.find(name);
if (it == _headers.end()) {
return kNull;
}
return it->second;
}
const string &Parser::content() const {
return _content;
}
void Parser::clear() {
_method.clear();
_url.clear();
_params.clear();
_protocol.clear();
_content.clear();
_headers.clear();
_url_args.clear();
}
const string &Parser::params() const {
return _params;
}
void Parser::setUrl(string url) {
_url = std::move(url);
}
void Parser::setContent(string content) {
_content = std::move(content);
}
StrCaseMap &Parser::getHeader() const {
return _headers;
}
StrCaseMap &Parser::getUrlArgs() const {
return _url_args;
}
StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) {
StrCaseMap ret;
auto arg_vec = split(str, pair_delim);
for (auto &key_val : arg_vec) {
if (key_val.empty()) {
// 忽略
continue;
}
auto pos = key_val.find(key_delim);
if (pos != string::npos) {
auto key = trim(std::string(key_val, 0, pos));
auto val = trim(key_val.substr(pos + strlen(key_delim)));
ret.emplace_force(std::move(key), std::move(val));
} else {
trim(key_val);
if (!key_val.empty()) {
ret.emplace_force(std::move(key_val), "");
}
}
}
return ret;
}
std::string Parser::mergeUrl(const string &base_url, const string &path) {
// 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径
if (base_url.empty()) {
return path;
}
if (path.empty()) {
return base_url;
}
// 如果包含协议,则直接返回
if (path.find("://") != string::npos) {
return path;
}
string protocol = "http://";
size_t protocol_end = base_url.find("://");
if (protocol_end != string::npos) {
protocol = base_url.substr(0, protocol_end + 3);
}
// 如果path以"//"开头,则直接拼接协议
if (path.find("//") == 0) {
return protocol + path.substr(2);
}
string host;
size_t pos = 0;
if (protocol_end != string::npos) {
pos = base_url.find('/', protocol_end + 3);
host = base_url.substr(0, pos);
if (pos == string::npos) {
pos = base_url.size();
} else {
pos++;
}
}
// 如果path以"/"开头,则直接拼接协议和主机
if (path[0] == '/') {
return host + path;
}
vector<string> path_parts;
size_t next_pos = 0;
if (!host.empty()) {
path_parts.emplace_back(host);
}
while ((next_pos = base_url.find('/', pos)) != string::npos) {
path_parts.emplace_back(base_url.substr(pos, next_pos - pos));
pos = next_pos + 1;
}
pos = 0;
while ((next_pos = path.find('/', pos)) != string::npos) {
string part = path.substr(pos, next_pos - pos);
if (part == "..") {
if (!path_parts.empty() && !path_parts.back().empty()) {
if (path_parts.size() > 1 || protocol_end == string::npos) {
path_parts.pop_back();
}
}
} else if (part != "." && !part.empty()) {
path_parts.emplace_back(part);
}
pos = next_pos + 1;
}
string part = path.substr(pos);
if (part != ".." && part != "." && !part.empty()) {
path_parts.emplace_back(part);
}
stringstream final_url;
for (size_t i = 0; i < path_parts.size(); ++i) {
if (i == 0) {
final_url << path_parts[i];
} else {
final_url << '/' << path_parts[i];
}
}
return final_url.str();
}
void RtspUrl::parse(const string &strUrl) {
auto schema = findSubString(strUrl.data(), nullptr, "://");
bool is_ssl = strcasecmp(schema.data(), "rtsps") == 0;
// 查找"://"与"/"之间的字符串,用于提取用户名密码
auto middle_url = findSubString(strUrl.data(), "://", "/");
if (middle_url.empty()) {
middle_url = findSubString(strUrl.data(), "://", nullptr);
}
auto pos = middle_url.rfind('@');
if (pos == string::npos) {
// 并没有用户名密码
return setup(is_ssl, strUrl, "", "");
}
// 包含用户名密码
auto user_pwd = middle_url.substr(0, pos);
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
auto url = StrPrinter << "rtsp://" << suffix << endl;
if (user_pwd.find(":") == string::npos) {
return setup(is_ssl, url, user_pwd, "");
}
auto user = findSubString(user_pwd.data(), nullptr, ":");
auto pwd = findSubString(user_pwd.data(), ":", nullptr);
return setup(is_ssl, url, user, pwd);
}
void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const string &passwd) {
auto ip = findSubString(url.data(), "://", "/");
if (ip.empty()) {
ip = split(findSubString(url.data(), "://", NULL), "?")[0];
}
uint16_t port = is_ssl ? 322 : 554;
splitUrl(ip, ip, port);
_url = std::move(url);
_user = strCoding::UrlDecodeUserOrPass(user);
_passwd = strCoding::UrlDecodeUserOrPass(passwd);
_host = std::move(ip);
_port = port;
_is_ssl = is_ssl;
}
static void inline checkHost(std::string &host) {
if (host.back() == ']' && host.front() == '[') {
// ipv6去除方括号
host.pop_back();
host.erase(0, 1);
CHECK(SockUtil::is_ipv6(host.data()), "not a ipv6 address:", host);
}
}
void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
CHECK(!url.empty(), "empty url");
auto pos = url.rfind(':');
if (pos == string::npos || url.back() == ']') {
// 没有冒号,未指定端口;或者是纯粹的ipv6地址
host = url;
checkHost(host);
return;
}
CHECK(pos > 0, "invalid url:", url);
CHECK(sscanf(url.data() + pos + 1, "%" SCNu16, &port) == 1, "parse port from url failed:", url);
host = url.substr(0, pos);
checkHost(host);
}
void parseProxyUrl(const std::string &proxy_url, std::string &proxy_host, uint16_t &proxy_port, std::string &proxy_auth) {
// 判断是否包含http://, 如果是则去掉
std::string host;
auto pos = proxy_url.find("://");
if (pos != string::npos) {
host = proxy_url.substr(pos + 3);
} else {
host = proxy_url;
}
// 判断是否包含用户名和密码
pos = host.rfind('@');
if (pos != string::npos) {
proxy_auth = encodeBase64(host.substr(0, pos));
host = host.substr(pos + 1, host.size());
}
splitUrl(host, proxy_host, proxy_port);
}
#if 0
//测试代码
static onceToken token([](){
string host;
uint16_t port;
splitUrl("www.baidu.com:8880", host, port);
splitUrl("192.168.1.1:8880", host, port);
splitUrl("[::]:8880", host, port);
splitUrl("[fe80::604d:4173:76e9:1009]:8880", host, port);
});
#endif
} // namespace mediakit

159
MediaServer/Common/Parser.h Normal file
View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_PARSER_H
#define ZLMEDIAKIT_PARSER_H
#include <map>
#include <string>
#include "Util/util.h"
namespace mediakit {
// 从字符串中提取子字符串 [AUTO-TRANSLATED:8493b6a5]
// Extract substring from string
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns [AUTO-TRANSLATED:0cfa4a6c]
// Parse url to host address and port number, compatible with ipv4/ipv6/dns
void splitUrl(const std::string &url, std::string &host, uint16_t &port);
// 解析proxy url,仅支持http [AUTO-TRANSLATED:194b49d7]
// Parse proxy url, only supports http
void parseProxyUrl(const std::string &proxy_url, std::string &proxy_host, uint16_t &proxy_port, std::string &proxy_auth);
struct StrCaseCompare {
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }
};
class StrCaseMap : public std::multimap<std::string, std::string, StrCaseCompare> {
public:
using Super = std::multimap<std::string, std::string, StrCaseCompare>;
std::string &operator[](const std::string &k) {
auto it = find(k);
if (it == end()) {
it = Super::emplace(k, "");
}
return it->second;
}
template <typename K, typename V>
void emplace(K &&k, V &&v) {
auto it = find(k);
if (it != end()) {
return;
}
Super::emplace(std::forward<K>(k), std::forward<V>(v));
}
template <typename K, typename V>
void emplace_force(K &&k, V &&v) {
Super::emplace(std::forward<K>(k), std::forward<V>(v));
}
};
// rtsp/http/sip解析类 [AUTO-TRANSLATED:188ca500]
// rtsp/http/sip parsing class
class Parser {
public:
// 解析http/rtsp/sip请求需要确保buf以\0结尾 [AUTO-TRANSLATED:552953af]
// Parse http/rtsp/sip request, ensure buf ends with \0
void parse(const char *buf, size_t size);
// 获取命令字如GET/POST [AUTO-TRANSLATED:34750f3d]
// Get command word, such as GET/POST
const std::string &method() const;
// 请求时获取中间url不包含?后面的参数 [AUTO-TRANSLATED:c259f1ed]
// When requesting, get the middle url, excluding the parameters after ?
const std::string &url() const;
// 回复时获取状态码如200/404 [AUTO-TRANSLATED:ac3f8ed4]
// When replying, get the status code, such as 200/404
const std::string &status() const;
// 获取中间url包含?后面的参数 [AUTO-TRANSLATED:ca1fec1a]
// Get the middle url, including the parameters after ?
std::string fullUrl() const;
// 请求时获取协议名如HTTP/1.1 [AUTO-TRANSLATED:7410fed6]
// When requesting, get the protocol name, such as HTTP/1.1
const std::string &protocol() const;
// 回复时,获取状态字符串,如 OK/Not Found [AUTO-TRANSLATED:d245247a]
// When replying, get the status string, such as OK/Not Found
const std::string &statusStr() const;
// 根据header key名获取请求header value值 [AUTO-TRANSLATED:5cbc9ac7]
// Get the request header value according to the header key name
const std::string &operator[](const char *name) const;
// 获取http body或sdp [AUTO-TRANSLATED:d6fd1803]
// Get http body or sdp
const std::string &content() const;
// 清空,为了重用 [AUTO-TRANSLATED:cb7a16dd]
// Clear, for reuse
void clear();
// 获取?后面的参数 [AUTO-TRANSLATED:4ada90e1]
// Get the parameters after ?
const std::string &params() const;
// 重新设置url [AUTO-TRANSLATED:4829ba8e]
// Reset url
void setUrl(std::string url);
// 重新设置content [AUTO-TRANSLATED:ac8fc8c0]
// Reset content
void setContent(std::string content);
// 获取header列表 [AUTO-TRANSLATED:90d90b03]
// Get header list
StrCaseMap &getHeader() const;
// 获取url参数列表 [AUTO-TRANSLATED:da1df48a]
// Get url parameter list
StrCaseMap &getUrlArgs() const;
// 解析?后面的参数 [AUTO-TRANSLATED:38692051]
// Parse the parameters after ?
static StrCaseMap parseArgs(const std::string &str, const char *pair_delim = "&", const char *key_delim = "=");
static std::string mergeUrl(const std::string &base_url, const std::string &path);
private:
std::string _method;
std::string _url;
std::string _protocol;
std::string _content;
std::string _params;
mutable StrCaseMap _headers;
mutable StrCaseMap _url_args;
};
// 解析rtsp url的工具类 [AUTO-TRANSLATED:0d31ae01]
// Utility class for parsing rtsp url
class RtspUrl {
public:
bool _is_ssl;
uint16_t _port;
std::string _url;
std::string _user;
std::string _passwd;
std::string _host;
public:
void parse(const std::string &url);
private:
void setup(bool, const std::string &, const std::string &, const std::string &);
};
} // namespace mediakit
#endif // ZLMEDIAKIT_PARSER_H

View File

@ -0,0 +1,401 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Stamp.h"
// 时间戳最大允许跳变3秒主要是防止网络抖动导致的跳变 [AUTO-TRANSLATED:144154de]
// Timestamp maximum allowable jump is 3 seconds, mainly to prevent network jitter caused by the jump
#define MAX_DELTA_STAMP (3 * 1000)
#define STAMP_LOOP_DELTA (60 * 1000)
#define MAX_CTS 500
#define ABS(x) ((x) > 0 ? (x) : (-x))
using namespace toolkit;
namespace mediakit {
DeltaStamp::DeltaStamp() {
// 时间戳最大允许跳跃300ms [AUTO-TRANSLATED:2458e61f]
// Timestamp maximum allowable jump is 300ms
_max_delta = 300;
}
int64_t DeltaStamp::relativeStamp(int64_t stamp, bool enable_rollback) {
_relative_stamp += deltaStamp(stamp, enable_rollback);
return _relative_stamp;
}
int64_t DeltaStamp::relativeStamp() {
return _relative_stamp;
}
int64_t DeltaStamp::deltaStamp(int64_t stamp, bool enable_rollback) {
if (!_last_stamp) {
// 第一次计算时间戳增量,时间戳增量为0 [AUTO-TRANSLATED:32944bd3]
// Calculate the timestamp increment for the first time, the timestamp increment is 0
if (stamp) {
_last_stamp = stamp;
}
return 0;
}
int64_t ret = stamp - _last_stamp;
if (ret >= 0) {
// 时间戳增量为正,返回之 [AUTO-TRANSLATED:308dfb22]
// The timestamp increment is positive, return it
_last_stamp = stamp;
// 在直播情况下时间戳增量不得大于MAX_DELTA_STAMP否则强制相对时间戳加1 [AUTO-TRANSLATED:c78c40d3]
// In the live broadcast case, the timestamp increment must not be greater than MAX_DELTA_STAMP, otherwise the relative timestamp is forced to add 1
if (ret > _max_delta) {
needSync();
return 1;
}
return ret;
}
// 时间戳增量为负,说明时间戳回环了或回退了 [AUTO-TRANSLATED:fd825d50]
// The timestamp increment is negative, indicating that the timestamp has looped or retreated
_last_stamp = stamp;
if (!enable_rollback || -ret > _max_delta) {
// 不允许回退或者回退太多了, 强制时间戳加1 [AUTO-TRANSLATED:152f5ffa]
// Not allowed to retreat or retreat too much, force the timestamp to add 1
needSync();
return 1;
}
return ret;
}
void DeltaStamp::setMaxDelta(size_t max_delta) {
_max_delta = max_delta;
}
void Stamp::setPlayBack(bool playback) {
_playback = playback;
}
void Stamp::syncTo(Stamp &other) {
_need_sync = true;
_sync_master = &other;
}
void Stamp::needSync() {
_need_sync = true;
}
void Stamp::enableRollback(bool flag) {
_enable_rollback = flag;
}
// 限制dts回退 [AUTO-TRANSLATED:6bc53b31]
// Limit dts retreat
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
if (_playback) {
// 回放允许时间戳回退 [AUTO-TRANSLATED:5d822118]
// Playback allows timestamp rollback
return;
}
if (dts_out < _last_dts_out) {
// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out; [AUTO-TRANSLATED:c36316f5]
// WarnL << "dts rollback:" << dts_out << " < " << _last_dts_out;
dts_out = _last_dts_out;
pts_out = _last_pts_out;
return;
}
_last_dts_out = dts_out;
_last_pts_out = pts_out;
}
// 音视频时间戳同步 [AUTO-TRANSLATED:58f1e95c]
// Audio and video timestamp synchronization
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
revise_l2(dts, pts, dts_out, pts_out, modifyStamp);
if (!_sync_master || modifyStamp || _playback) {
// 自动生成时间戳或回放或同步完毕 [AUTO-TRANSLATED:a0b8f8bd]
// Automatically generate timestamps or playback or synchronization is complete
return;
}
// 需要同步时间戳 [AUTO-TRANSLATED:af93e8f8]
// Need to synchronize timestamps
if (_sync_master && _sync_master->_last_dts_in && (_need_sync || _sync_master->_need_sync)) {
// 音视频dts当前时间差 [AUTO-TRANSLATED:716468a6]
// Audio and video dts current time difference
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
if (ABS(dts_diff) < 5000) {
// 如果绝对时间戳小于5秒那么说明他们的起始时间戳是一致的那么强制同步 [AUTO-TRANSLATED:5d11ef6a]
// If the absolute timestamp is less than 5 seconds, then it means that their starting timestamps are consistent, then force synchronization
auto target_stamp = _sync_master->_relative_stamp + dts_diff;
if (target_stamp > _relative_stamp || _enable_rollback) {
// 强制同步后,时间戳增加跳跃了,或允许回退 [AUTO-TRANSLATED:805424a9]
// After forced synchronization, the timestamp increases jump, or allows rollback
TraceL << "Relative stamp changed: " << _relative_stamp << " -> " << target_stamp;
_relative_stamp = target_stamp;
} else {
// 不允许回退, 则让另外一个Track的时间戳增长 [AUTO-TRANSLATED:428e8ce2]
// Not allowed to rollback, then let the timestamp of the other Track increase
target_stamp = _relative_stamp - dts_diff;
TraceL << "Relative stamp changed: " << _sync_master->_relative_stamp << " -> " << target_stamp;
_sync_master->_relative_stamp = target_stamp;
}
}
_need_sync = false;
_sync_master->_need_sync = false;
}
}
// 求取相对时间戳 [AUTO-TRANSLATED:122da805]
// Obtain the relative timestamp
void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
if (!pts) {
// 没有播放时间戳,使其赋值为解码时间戳 [AUTO-TRANSLATED:9ee71899]
// There is no playback timestamp, set it to the decoding timestamp
pts = dts;
}
if (_playback) {
// 这是点播 [AUTO-TRANSLATED:f11fd173]
// This is on-demand
dts_out = dts;
pts_out = pts;
_relative_stamp = dts_out;
_last_dts_in = dts;
return;
}
// pts和dts的差值 [AUTO-TRANSLATED:3b145073]
// The difference between pts and dts
int64_t pts_dts_diff = pts - dts;
if (_last_dts_in != dts) {
// 时间戳发生变更 [AUTO-TRANSLATED:7344315c]
// Timestamp changed
if (modifyStamp) {
// 内部自己生产时间戳 [AUTO-TRANSLATED:fae889e0]
// Internal production of timestamps
_relative_stamp = _ticker.elapsedTime();
} else {
_relative_stamp += deltaStamp(dts, _enable_rollback);
}
_last_dts_in = dts;
}
dts_out = _relative_stamp;
// ////////////以下是播放时间戳的计算////////////////// [AUTO-TRANSLATED:6c4a56a7]
// ////////////The following is the calculation of the playback timestamp//////////////////
if (ABS(pts_dts_diff) > MAX_CTS) {
// 如果差值太大,则认为由于回环导致时间戳错乱了 [AUTO-TRANSLATED:1a11b5f3]
// If the difference is too large, it is considered that the timestamp is messed up due to looping
pts_dts_diff = 0;
}
pts_out = dts_out + pts_dts_diff;
}
void Stamp::setRelativeStamp(int64_t relativeStamp) {
_relative_stamp = relativeStamp;
}
int64_t Stamp::getRelativeStamp() const {
return _relative_stamp;
}
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts) {
bool ret = false;
if (pts == _last_pts) {
// pts未变说明dts也不会变返回上次dts [AUTO-TRANSLATED:dc0972e0]
// pts does not change, indicating that dts will not change, return the last dts
if (_last_dts) {
dts = _last_dts;
ret = true;
}
} else {
// pts变了尝试计算dts [AUTO-TRANSLATED:f527d0f6]
// pts changed, try to calculate dts
ret = getDts_l(pts, dts);
if (ret) {
// 获取到了dts保存本次结果 [AUTO-TRANSLATED:d6a5ce6d]
// Get the dts, save the current result
_last_dts = dts;
}
}
if (!ret) {
// pts排序列队长度还不知道也就是不知道有没有B帧 [AUTO-TRANSLATED:e5ad4327]
// The pts sorting queue length is not yet known, that is, it is not known whether there is a B frame,
// 那么先强制dts == pts这样可能导致有B帧的情况下起始画面有几帧回退 [AUTO-TRANSLATED:74c97de1]
// Then force dts == pts first, which may cause the starting picture to have a few frames rollback in the case of B frames
dts = pts;
}
// 记录上次pts [AUTO-TRANSLATED:4ecd474b]
// Record the last pts
_last_pts = pts;
return ret;
}
// 该算法核心思想是对pts进行排序排序好的pts就是dts。 [AUTO-TRANSLATED:efb36e04]
// The core idea of this algorithm is to sort the pts, and the sorted pts is the dts.
// 排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量 [AUTO-TRANSLATED:5ada843a]
// Sorting has a certain lag, so it is necessary to add the timestamp offset caused by sorting
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts) {
if (_sorter_max_size == 1) {
// 没有B帧dts就等于pts [AUTO-TRANSLATED:9cfae4ea]
// There is no B frame, dts is equal to pts
dts = pts;
return true;
}
if (!_sorter_max_size) {
// 尚未计算出pts排序列队长度(也就是P帧间B帧个数) [AUTO-TRANSLATED:8bedb754]
// The length of the pts sorting queue (that is, the number of B frames between P frames) has not been calculated yet
if (pts > _last_max_pts) {
// pts时间戳增加了那么说明这帧画面不是B帧(说明是P帧或关键帧) [AUTO-TRANSLATED:4c5ef2b8]
// The pts timestamp has increased, which means that this frame is not a B frame (it means it is a P frame or a key frame)
if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) {
// 已经出现多次非B帧的情况那么我们就能知道P帧间B帧的个数 [AUTO-TRANSLATED:fd747b3c]
// There have been multiple non-B frames, so we can know the number of B frames between P frames
_sorter_max_size = _frames_since_last_max_pts;
// 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计) [AUTO-TRANSLATED:66c0cc14]
// We record the time interval between P frames (that is, the cumulative increment of multiple B frame timestamps)
_dts_pts_offset = (pts - _last_max_pts);
// 除以2防止dts大于pts [AUTO-TRANSLATED:52b5b3ab]
// Divide by 2 to prevent dts from being greater than pts
_dts_pts_offset /= 2;
}
// 遇到P帧或关键帧连续B帧计数清零 [AUTO-TRANSLATED:537bb54d]
// When encountering a P frame or a key frame, the continuous B frame count is cleared
_frames_since_last_max_pts = 0;
// 记录上次非B帧的pts时间戳(同时也是dts)用于统计连续B帧时间戳增量 [AUTO-TRANSLATED:194f8cdb]
// Record the pts timestamp of the last non-B frame (which is also dts), used to count the continuous B frame timestamp increment
_last_max_pts = pts;
}
// 如果pts时间戳小于上一个P帧那么断定这个是B帧,我们记录B帧连续个数 [AUTO-TRANSLATED:1a7e33e2]
// If the pts timestamp is less than the previous P frame, then it is determined that this is a B frame, and we record the number of consecutive B frames
++_frames_since_last_max_pts;
}
// pts放入排序缓存列队缓存列队最大等于连续B帧个数 [AUTO-TRANSLATED:ff598a97]
// Put pts into the sorting cache queue, the maximum cache queue is equal to the number of consecutive B frames
_pts_sorter.emplace(pts);
if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) {
// 如果启用了pts排序(意味着存在B帧)并且pts排序缓存列队长度大于连续B帧个数 [AUTO-TRANSLATED:002c0d03]
// If pts sorting is enabled (meaning there are B frames), and the length of the pts sorting cache queue is greater than the number of consecutive B frames,
// 意味着后续的pts都会比最早的pts大那么说明可以取出最早的pts了这个pts将当做该帧的dts基准 [AUTO-TRANSLATED:86b8f679]
// It means that the subsequent pts will be larger than the earliest pts, which means that the earliest pts can be taken out, and this pts will be used as the dts baseline for this frame
auto it = _pts_sorter.begin();
// 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts), [AUTO-TRANSLATED:eb3657aa]
// Since this pts is the pts of the previous _sorter_max_size frames (that is, the dts of that frame),
// 那么我们加上时间戳偏移量基本等于该帧的dts [AUTO-TRANSLATED:245aac6e]
// Then we add the timestamp offset, which is basically equal to the dts of this frame
dts = *it + _dts_pts_offset;
if (dts > pts) {
// dts不能大于pts(基本不可能到达这个逻辑) [AUTO-TRANSLATED:847c4531]
// dts cannot be greater than pts (it is basically impossible to reach this logic)
dts = pts;
}
// pts排序缓存出列 [AUTO-TRANSLATED:8b580191]
// pts sorting cache dequeue
_pts_sorter.erase(it);
return true;
}
// 排序缓存尚未满 [AUTO-TRANSLATED:3f502460]
// The sorting cache is not full yet
return false;
}
void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
if (!ntp_stamp_ms || !rtp_stamp) {
// 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0 [AUTO-TRANSLATED:d3c200fc]
// It has been found that some rtsp servers send rtp timestamps and ntp timestamps that are always 0
WarnL << "Invalid sender report rtcp, ntp_stamp_ms = " << ntp_stamp_ms << ", rtp_stamp = " << rtp_stamp;
return;
}
update(rtp_stamp, ntp_stamp_ms * 1000);
}
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_us) {
_last_rtp_stamp = rtp_stamp;
_last_ntp_stamp_us = ntp_stamp_us;
}
uint64_t NtpStamp::getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate) {
if (rtp_stamp == _last_rtp_stamp) {
return _last_ntp_stamp_us / 1000;
}
return getNtpStampUS(rtp_stamp, sample_rate) / 1000;
}
uint64_t NtpStamp::getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate) {
if (!_last_ntp_stamp_us) {
// 尚未收到sender report rtcp包那么赋值为本地系统时间戳吧 [AUTO-TRANSLATED:c9056069]
// The sender report rtcp packet has not been received yet, so assign it to the local system timestamp
update(rtp_stamp, getCurrentMicrosecond(true));
}
// rtp时间戳正增长 [AUTO-TRANSLATED:4d3c87d1]
// The rtp timestamp is increasing
if (rtp_stamp >= _last_rtp_stamp) {
auto diff_us = static_cast<int64_t>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000000.0f));
if (diff_us < MAX_DELTA_STAMP * 1000) {
// 时间戳正常增长 [AUTO-TRANSLATED:db60e84a]
// The timestamp is increasing normally
update(rtp_stamp, _last_ntp_stamp_us + diff_us);
return _last_ntp_stamp_us;
}
// 时间戳大幅跳跃 [AUTO-TRANSLATED:c8585a51]
// The timestamp jumps significantly
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
if (_last_rtp_stamp < loop_delta_hz && rtp_stamp > UINT32_MAX - loop_delta_hz) {
// 应该是rtp时间戳溢出+乱序 [AUTO-TRANSLATED:13529fd6]
// It should be rtp timestamp overflow + out of order
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
return _last_ntp_stamp_us + diff_us - max_rtp_us;
}
// 不明原因的时间戳大幅跳跃,直接返回上次值 [AUTO-TRANSLATED:952b769c]
// The timestamp jumps significantly for unknown reasons, directly return the last value
WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp;
update(rtp_stamp, _last_ntp_stamp_us);
return _last_ntp_stamp_us;
}
// rtp时间戳负增长 [AUTO-TRANSLATED:54a7f797]
// The rtp timestamp is decreasing
auto diff_us = static_cast<int64_t>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000000.0f));
if (diff_us < MAX_DELTA_STAMP * 1000) {
// 正常范围的时间戳回退说明收到rtp乱序了 [AUTO-TRANSLATED:f691d5bf]
// The timestamp retreats within the normal range, indicating that the rtp is out of order
return _last_ntp_stamp_us - diff_us;
}
// 时间戳大幅度回退 [AUTO-TRANSLATED:0ad69100]
// The timestamp retreats significantly
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
if (rtp_stamp < loop_delta_hz && _last_rtp_stamp > UINT32_MAX - loop_delta_hz) {
// 确定是时间戳溢出 [AUTO-TRANSLATED:322274c3]
// Determine if it is a timestamp overflow
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
update(rtp_stamp, _last_ntp_stamp_us + (max_rtp_us - diff_us));
return _last_ntp_stamp_us;
}
// 不明原因的时间戳回退,直接返回上次值 [AUTO-TRANSLATED:c5105c14]
// Timestamp rollback for unknown reasons, return the last value directly
WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp;
update(rtp_stamp, _last_ntp_stamp_us);
return _last_ntp_stamp_us;
}
} // namespace mediakit

189
MediaServer/Common/Stamp.h Normal file
View File

@ -0,0 +1,189 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_STAMP_H
#define ZLMEDIAKIT_STAMP_H
#include <set>
#include <cstdint>
#include "Util/TimeTicker.h"
namespace mediakit {
class DeltaStamp {
public:
DeltaStamp();
virtual ~DeltaStamp() = default;
/**
*
* @param stamp
* @param enable_rollback 退
* @return
* Calculate the timestamp increment
* @param stamp Absolute timestamp
* @param enable_rollback Whether to allow the timestamp to roll back
* @return Timestamp increment
* [AUTO-TRANSLATED:e8d21dcd]
*/
int64_t deltaStamp(int64_t stamp, bool enable_rollback = true);
int64_t relativeStamp(int64_t stamp, bool enable_rollback = true);
int64_t relativeStamp();
// 设置最大允许回退或跳跃幅度 [AUTO-TRANSLATED:e5b44ede]
// Set the maximum allowed rollback or jump amplitude
void setMaxDelta(size_t max_delta);
protected:
virtual void needSync() {}
protected:
int _max_delta;
int64_t _last_stamp = 0;
int64_t _relative_stamp = 0;
};
// 该类解决时间戳回环、回退问题 [AUTO-TRANSLATED:b442692c]
// This class solves the problem of timestamp loopback and rollback
// 计算相对时间戳或者产生平滑时间戳 [AUTO-TRANSLATED:0deabd6e]
// Calculate the relative timestamp or generate a smooth timestamp
class Stamp : public DeltaStamp{
public:
/**
* ,dts回退等功能
* @param dts dts0
* @param pts pts0dts
* @param dts_out dts
* @param pts_out pts
* @param modifyStamp
* Get the relative timestamp, which also implements audio and video synchronization, limits dts rollback, etc.
* @param dts Input dts, if it is 0, it will be generated according to the system timestamp
* @param pts Input pts, if it is 0, it is equal to dts
* @param dts_out Output dts
* @param pts_out Output pts
* @param modifyStamp Whether to overwrite with the system timestamp
* [AUTO-TRANSLATED:0b939dc5]
*/
void revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
/**
* seek用
* @param relativeStamp
* Set the relative timestamp again, used for seek
* @param relativeStamp Relative timestamp
* [AUTO-TRANSLATED:fc087399]
*/
void setRelativeStamp(int64_t relativeStamp);
/**
*
* @return
* Get the current relative timestamp
* @return
* [AUTO-TRANSLATED:7ca29fde]
*/
int64_t getRelativeStamp() const ;
/**
* 退
* @param playback
* Set whether it is playback mode, playback mode allows timestamp rollback
* @param playback Whether it is playback mode
* [AUTO-TRANSLATED:ffe5e40b]
*/
void setPlayBack(bool playback = true);
/**
* ()
*
* Used for audio and video synchronization, audio should be synchronized with video (only modify audio timestamp)
* Because modifying the audio timestamp does not affect the playback speed
* [AUTO-TRANSLATED:7ac41a76]
*/
void syncTo(Stamp &other);
/**
* 退
* Whether to allow timestamp rollback
* [AUTO-TRANSLATED:1d32f7e3]
*/
void enableRollback(bool flag);
private:
// 主要实现音视频时间戳同步功能 [AUTO-TRANSLATED:45863fce]
// Mainly implements audio and video timestamp synchronization function
void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
// 主要实现获取相对时间戳功能 [AUTO-TRANSLATED:4e042942]
// Mainly implements the function of obtaining the relative timestamp
void revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
void needSync() override;
private:
bool _playback = false;
bool _need_sync = false;
// 默认不允许时间戳回滚 [AUTO-TRANSLATED:0163ff03]
// Default does not allow timestamp rollback
bool _enable_rollback = false;
int64_t _relative_stamp = 0;
int64_t _last_dts_in = 0;
int64_t _last_dts_out = 0;
int64_t _last_pts_out = 0;
toolkit::SmoothTicker _ticker;
Stamp *_sync_master = nullptr;
};
// dts生成器 [AUTO-TRANSLATED:d8a794a2]
// dts generator,
// pts排序后就是dts [AUTO-TRANSLATED:439ac368]
// pts after sorting is dts
class DtsGenerator{
public:
bool getDts(uint64_t pts, uint64_t &dts);
private:
bool getDts_l(uint64_t pts, uint64_t &dts);
private:
uint64_t _dts_pts_offset = 0;
uint64_t _last_dts = 0;
uint64_t _last_pts = 0;
uint64_t _last_max_pts = 0;
size_t _frames_since_last_max_pts = 0;
size_t _sorter_max_size = 0;
size_t _count_sorter_max_size = 0;
std::set<uint64_t> _pts_sorter;
};
class NtpStamp {
public:
void setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms);
uint64_t getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate);
private:
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_us);
uint64_t getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate);
private:
uint32_t _last_rtp_stamp = 0;
uint64_t _last_ntp_stamp_us = 0;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_STAMP_H

View File

@ -0,0 +1,675 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Common/config.h"
#include "MediaSource.h"
#include "Util/NoticeCenter.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/util.h"
#include <assert.h>
#include <stdio.h>
using namespace std;
using namespace toolkit;
namespace mediakit {
bool loadIniConfig(const char *ini_path) {
string ini;
if (ini_path && ini_path[0] != '\0') {
ini = ini_path;
} else {
ini = exePath() + ".ini";
}
try {
mINI::Instance().parseFile(ini);
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
return true;
} catch (std::exception &) {
InfoL << "dump ini file to:" << ini;
mINI::Instance().dumpFile(ini);
return false;
}
}
// //////////广播名称/////////// [AUTO-TRANSLATED:439b2d74]
// //////////Broadcast Name///////////
namespace Broadcast {
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
const string kBroadcastRecordTs = "kBroadcastRecordTs";
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth";
const string kBroadcastMediaPlayed = "kBroadcastMediaPlayed";
const string kBroadcastMediaPublish = "kBroadcastMediaPublish";
const string kBroadcastFlowReport = "kBroadcastFlowReport";
const string kBroadcastReloadConfig = "kBroadcastReloadConfig";
const string kBroadcastShellLogin = "kBroadcastShellLogin";
const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream";
const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader";
const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess";
const string kBroadcastSendRtpStopped = "kBroadcastSendRtpStopped";
const string kBroadcastRtpServerTimeout = "kBroadcastRtpServerTimeout";
const string kBroadcastRtcSctpConnecting = "kBroadcastRtcSctpConnecting";
const string kBroadcastRtcSctpConnected = "kBroadcastRtcSctpConnected";
const string kBroadcastRtcSctpFailed = "kBroadcastRtcSctpFailed";
const string kBroadcastRtcSctpClosed = "kBroadcastRtcSctpClosed";
const string kBroadcastRtcSctpSend = "kBroadcastRtcSctpSend";
const string kBroadcastRtcSctpReceived = "kBroadcastRtcSctpReceived";
const string kBroadcastPlayerCountChanged = "kBroadcastPlayerCountChanged";
} // namespace Broadcast
// 通用配置项目 [AUTO-TRANSLATED:ca344202]
// General Configuration Items
namespace General {
#define GENERAL_FIELD "general."
const string kMediaServerId = GENERAL_FIELD "mediaServerId";
const string kFlowThreshold = GENERAL_FIELD "flowThreshold";
const string kStreamNoneReaderDelayMS = GENERAL_FIELD "streamNoneReaderDelayMS";
const string kMaxStreamWaitTimeMS = GENERAL_FIELD "maxStreamWaitMS";
const string kEnableVhost = GENERAL_FIELD "enableVhost";
const string kResetWhenRePlay = GENERAL_FIELD "resetWhenRePlay";
const string kMergeWriteMS = GENERAL_FIELD "mergeWriteMS";
const string kCheckNvidiaDev = GENERAL_FIELD "check_nvidia_dev";
const string kEnableFFmpegLog = GENERAL_FIELD "enable_ffmpeg_log";
const string kWaitTrackReadyMS = GENERAL_FIELD "wait_track_ready_ms";
const string kWaitAudioTrackDataMS = GENERAL_FIELD "wait_audio_track_data_ms";
const string kWaitAddTrackMS = GENERAL_FIELD "wait_add_track_ms";
const string kUnreadyFrameCache = GENERAL_FIELD "unready_frame_cache";
const string kBroadcastPlayerCountChanged = GENERAL_FIELD "broadcast_player_count_changed";
const string kListenIP = GENERAL_FIELD "listen_ip";
static onceToken token([]() {
mINI::Instance()[kFlowThreshold] = 1024;
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
mINI::Instance()[kEnableVhost] = 0;
mINI::Instance()[kResetWhenRePlay] = 1;
mINI::Instance()[kMergeWriteMS] = 0;
mINI::Instance()[kMediaServerId] = makeRandStr(16);
mINI::Instance()[kCheckNvidiaDev] = 1;
mINI::Instance()[kEnableFFmpegLog] = 0;
mINI::Instance()[kWaitTrackReadyMS] = 10000;
mINI::Instance()[kWaitAudioTrackDataMS] = 1000;
mINI::Instance()[kWaitAddTrackMS] = 3000;
mINI::Instance()[kUnreadyFrameCache] = 100;
mINI::Instance()[kBroadcastPlayerCountChanged] = 0;
mINI::Instance()[kListenIP] = "::";
});
} // namespace General
namespace Protocol {
const string kModifyStamp = string(kFieldName) + "modify_stamp";
const string kEnableAudio = string(kFieldName) + "enable_audio";
const string kAddMuteAudio = string(kFieldName) + "add_mute_audio";
const string kAutoClose = string(kFieldName) + "auto_close";
const string kContinuePushMS = string(kFieldName) + "continue_push_ms";
const string kPacedSenderMS = string(kFieldName) + "paced_sender_ms";
const string kEnableHls = string(kFieldName) + "enable_hls";
const string kEnableHlsFmp4 = string(kFieldName) + "enable_hls_fmp4";
const string kEnableMP4 = string(kFieldName) + "enable_mp4";
const string kEnableRtsp = string(kFieldName) + "enable_rtsp";
const string kEnableRtmp = string(kFieldName) + "enable_rtmp";
const string kEnableTS = string(kFieldName) + "enable_ts";
const string kEnableFMP4 = string(kFieldName) + "enable_fmp4";
const string kMP4AsPlayer = string(kFieldName) + "mp4_as_player";
const string kMP4MaxSecond = string(kFieldName) + "mp4_max_second";
const string kMP4SavePath = string(kFieldName) + "mp4_save_path";
const string kHlsSavePath = string(kFieldName) + "hls_save_path";
const string kHlsDemand = string(kFieldName) + "hls_demand";
const string kRtspDemand = string(kFieldName) + "rtsp_demand";
const string kRtmpDemand = string(kFieldName) + "rtmp_demand";
const string kTSDemand = string(kFieldName) + "ts_demand";
const string kFMP4Demand = string(kFieldName) + "fmp4_demand";
static onceToken token([]() {
mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative;
mINI::Instance()[kEnableAudio] = 1;
mINI::Instance()[kAddMuteAudio] = 1;
mINI::Instance()[kContinuePushMS] = 15000;
mINI::Instance()[kPacedSenderMS] = 0;
mINI::Instance()[kAutoClose] = 0;
mINI::Instance()[kEnableHls] = 1;
mINI::Instance()[kEnableHlsFmp4] = 0;
mINI::Instance()[kEnableMP4] = 0;
mINI::Instance()[kEnableRtsp] = 1;
mINI::Instance()[kEnableRtmp] = 1;
mINI::Instance()[kEnableTS] = 1;
mINI::Instance()[kEnableFMP4] = 1;
mINI::Instance()[kMP4AsPlayer] = 0;
mINI::Instance()[kMP4MaxSecond] = 3600;
mINI::Instance()[kMP4SavePath] = "./www";
mINI::Instance()[kHlsSavePath] = "./www";
mINI::Instance()[kHlsDemand] = 0;
mINI::Instance()[kRtspDemand] = 0;
mINI::Instance()[kRtmpDemand] = 0;
mINI::Instance()[kTSDemand] = 0;
mINI::Instance()[kFMP4Demand] = 0;
});
} // !Protocol
// //////////HTTP配置/////////// [AUTO-TRANSLATED:a281d694]
// //////////HTTP Configuration///////////
namespace Http {
#define HTTP_FIELD "http."
const string kSendBufSize = HTTP_FIELD "sendBufSize";
const string kMaxReqSize = HTTP_FIELD "maxReqSize";
const string kKeepAliveSecond = HTTP_FIELD "keepAliveSecond";
const string kCharSet = HTTP_FIELD "charSet";
const string kRootPath = HTTP_FIELD "rootPath";
const string kVirtualPath = HTTP_FIELD "virtualPath";
const string kNotFound = HTTP_FIELD "notFound";
const string kDirMenu = HTTP_FIELD "dirMenu";
const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix";
const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header";
const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains";
const string kAllowIPRange = HTTP_FIELD "allow_ip_range";
static onceToken token([]() {
mINI::Instance()[kSendBufSize] = 64 * 1024;
mINI::Instance()[kMaxReqSize] = 4 * 10240;
mINI::Instance()[kKeepAliveSecond] = 15;
mINI::Instance()[kDirMenu] = true;
mINI::Instance()[kVirtualPath] = "";
mINI::Instance()[kCharSet] = "utf-8";
mINI::Instance()[kRootPath] = "./www";
mINI::Instance()[kNotFound] = StrPrinter << "<html>"
"<head><title>404 Not Found</title></head>"
"<body bgcolor=\"white\">"
"<center><h1>您访问的资源不存在!</h1></center>"
"<hr><center>"
<< kServerName
<< "</center>"
"</body>"
"</html>"
<< endl;
mINI::Instance()[kForbidCacheSuffix] = "";
mINI::Instance()[kForwardedIpHeader] = "";
mINI::Instance()[kAllowCrossDomains] = 1;
mINI::Instance()[kAllowIPRange] = "::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255";
});
} // namespace Http
// //////////SHELL配置/////////// [AUTO-TRANSLATED:f023ec45]
// //////////SHELL Configuration///////////
namespace Shell {
#define SHELL_FIELD "shell."
const string kMaxReqSize = SHELL_FIELD "maxReqSize";
static onceToken token([]() { mINI::Instance()[kMaxReqSize] = 1024; });
} // namespace Shell
// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
// //////////RTSP Server Configuration///////////
namespace Rtsp {
#define RTSP_FIELD "rtsp."
const string kAuthBasic = RTSP_FIELD "authBasic";
const string kHandshakeSecond = RTSP_FIELD "handshakeSecond";
const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond";
const string kDirectProxy = RTSP_FIELD "directProxy";
const string kLowLatency = RTSP_FIELD"lowLatency";
const string kRtpTransportType = RTSP_FIELD"rtpTransportType";
static onceToken token([]() {
// 默认Md5方式认证 [AUTO-TRANSLATED:6155d989]
// Default Md5 authentication
mINI::Instance()[kAuthBasic] = 0;
mINI::Instance()[kHandshakeSecond] = 15;
mINI::Instance()[kKeepAliveSecond] = 15;
mINI::Instance()[kDirectProxy] = 1;
mINI::Instance()[kLowLatency] = 0;
mINI::Instance()[kRtpTransportType] = -1;
});
} // namespace Rtsp
// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
// //////////RTMP Server Configuration///////////
namespace Rtmp {
#define RTMP_FIELD "rtmp."
const string kHandshakeSecond = RTMP_FIELD "handshakeSecond";
const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond";
const string kDirectProxy = RTMP_FIELD "directProxy";
const string kEnhanced = RTMP_FIELD "enhanced";
static onceToken token([]() {
mINI::Instance()[kHandshakeSecond] = 15;
mINI::Instance()[kKeepAliveSecond] = 15;
mINI::Instance()[kDirectProxy] = 1;
mINI::Instance()[kEnhanced] = 0;
});
} // namespace Rtmp
// //////////RTP配置/////////// [AUTO-TRANSLATED:23cbcb86]
// //////////RTP Configuration///////////
namespace Rtp {
#define RTP_FIELD "rtp."
// RTP打包最大MTU,公网情况下更小 [AUTO-TRANSLATED:869f5c4b]
// Maximum RTP packet MTU, smaller for public networks
const string kVideoMtuSize = RTP_FIELD "videoMtuSize";
const string kAudioMtuSize = RTP_FIELD "audioMtuSize";
// rtp包最大长度限制单位是KB [AUTO-TRANSLATED:aee4bffc]
// Maximum RTP packet length limit, in KB
const string kRtpMaxSize = RTP_FIELD "rtpMaxSize";
const string kLowLatency = RTP_FIELD "lowLatency";
const string kH264StapA = RTP_FIELD "h264_stap_a";
static onceToken token([]() {
mINI::Instance()[kVideoMtuSize] = 1400;
mINI::Instance()[kAudioMtuSize] = 600;
mINI::Instance()[kRtpMaxSize] = 10;
mINI::Instance()[kLowLatency] = 0;
mINI::Instance()[kH264StapA] = 1;
});
} // namespace Rtp
// //////////组播配置/////////// [AUTO-TRANSLATED:dc39b9d6]
// //////////Multicast Configuration///////////
namespace MultiCast {
#define MULTI_FIELD "multicast."
// 组播分配起始地址 [AUTO-TRANSLATED:069db91d]
// Multicast allocation starting address
const string kAddrMin = MULTI_FIELD "addrMin";
// 组播分配截止地址 [AUTO-TRANSLATED:6d3fc54c]
// Multicast allocation ending address
const string kAddrMax = MULTI_FIELD "addrMax";
// 组播TTL [AUTO-TRANSLATED:c7c5339c]
// Multicast TTL
const string kUdpTTL = MULTI_FIELD "udpTTL";
static onceToken token([]() {
mINI::Instance()[kAddrMin] = "239.0.0.0";
mINI::Instance()[kAddrMax] = "239.255.255.255";
mINI::Instance()[kUdpTTL] = 64;
});
} // namespace MultiCast
// //////////录像配置/////////// [AUTO-TRANSLATED:19de3e96]
// //////////Recording Configuration///////////
namespace Record {
#define RECORD_FIELD "record."
const string kAppName = RECORD_FIELD "appName";
const string kSampleMS = RECORD_FIELD "sampleMS";
const string kFileBufSize = RECORD_FIELD "fileBufSize";
const string kFastStart = RECORD_FIELD "fastStart";
const string kFileRepeat = RECORD_FIELD "fileRepeat";
const string kEnableFmp4 = RECORD_FIELD "enableFmp4";
static onceToken token([]() {
mINI::Instance()[kAppName] = "record";
mINI::Instance()[kSampleMS] = 500;
mINI::Instance()[kFileBufSize] = 64 * 1024;
mINI::Instance()[kFastStart] = false;
mINI::Instance()[kFileRepeat] = false;
mINI::Instance()[kEnableFmp4] = false;
});
} // namespace Record
// //////////HLS相关配置/////////// [AUTO-TRANSLATED:873cc84c]
// //////////HLS Related Configuration///////////
namespace Hls {
#define HLS_FIELD "hls."
const string kSegmentDuration = HLS_FIELD "segDur";
const string kSegmentNum = HLS_FIELD "segNum";
const string kSegmentKeep = HLS_FIELD "segKeep";
const string kSegmentDelay = HLS_FIELD "segDelay";
const string kSegmentRetain = HLS_FIELD "segRetain";
const string kFileBufSize = HLS_FIELD "fileBufSize";
const string kBroadcastRecordTs = HLS_FIELD "broadcastRecordTs";
const string kDeleteDelaySec = HLS_FIELD "deleteDelaySec";
const string kFastRegister = HLS_FIELD "fastRegister";
static onceToken token([]() {
mINI::Instance()[kSegmentDuration] = 2;
mINI::Instance()[kSegmentNum] = 3;
mINI::Instance()[kSegmentKeep] = false;
mINI::Instance()[kSegmentDelay] = 0;
mINI::Instance()[kSegmentRetain] = 5;
mINI::Instance()[kFileBufSize] = 64 * 1024;
mINI::Instance()[kBroadcastRecordTs] = false;
mINI::Instance()[kDeleteDelaySec] = 10;
mINI::Instance()[kFastRegister] = false;
});
} // namespace Hls
// //////////Rtp代理相关配置/////////// [AUTO-TRANSLATED:7b285587]
// //////////Rtp Proxy Related Configuration///////////
namespace RtpProxy {
#define RTP_PROXY_FIELD "rtp_proxy."
const string kDumpDir = RTP_PROXY_FIELD "dumpDir";
const string kTimeoutSec = RTP_PROXY_FIELD "timeoutSec";
const string kPortRange = RTP_PROXY_FIELD "port_range";
const string kH264PT = RTP_PROXY_FIELD "h264_pt";
const string kH265PT = RTP_PROXY_FIELD "h265_pt";
const string kPSPT = RTP_PROXY_FIELD "ps_pt";
const string kOpusPT = RTP_PROXY_FIELD "opus_pt";
const string kGopCache = RTP_PROXY_FIELD "gop_cache";
const string kRtpG711DurMs = RTP_PROXY_FIELD "rtp_g711_dur_ms";
const string kUdpRecvSocketBuffer = RTP_PROXY_FIELD "udp_recv_socket_buffer";
static onceToken token([]() {
mINI::Instance()[kDumpDir] = "";
mINI::Instance()[kTimeoutSec] = 15;
mINI::Instance()[kPortRange] = "30000-35000";
mINI::Instance()[kH264PT] = 98;
mINI::Instance()[kH265PT] = 99;
mINI::Instance()[kPSPT] = 96;
mINI::Instance()[kOpusPT] = 100;
mINI::Instance()[kGopCache] = 1;
mINI::Instance()[kRtpG711DurMs] = 100;
mINI::Instance()[kUdpRecvSocketBuffer] = 4 * 1024 * 1024;
});
} // namespace RtpProxy
namespace Client {
const string kNetAdapter = "net_adapter";
const string kRtpType = "rtp_type";
const string kRtspBeatType = "rtsp_beat_type";
const string kRtspUser = "rtsp_user";
const string kRtspPwd = "rtsp_pwd";
const string kRtspPwdIsMD5 = "rtsp_pwd_md5";
const string kTimeoutMS = "protocol_timeout_ms";
const string kMediaTimeoutMS = "media_timeout_ms";
const string kBeatIntervalMS = "beat_interval_ms";
const string kBenchmarkMode = "benchmark_mode";
const string kWaitTrackReady = "wait_track_ready";
const string kPlayTrack = "play_track";
const string kProxyUrl = "proxy_url";
const string kRtspSpeed = "rtsp_speed";
} // namespace Client
} // namespace mediakit
#ifdef ENABLE_MEM_DEBUG
extern "C" {
extern void *__real_malloc(size_t);
extern void __real_free(void *);
extern void *__real_realloc(void *ptr, size_t c);
void *__wrap_malloc(size_t c);
void __wrap_free(void *ptr);
void *__wrap_calloc(size_t __nmemb, size_t __size);
void *__wrap_realloc(void *ptr, size_t c);
}
#define BLOCK_TYPES 16
#define MIN_BLOCK_SIZE 128
static int get_mem_block_type(size_t c) {
int ret = 0;
while (c > MIN_BLOCK_SIZE && ret + 1 < BLOCK_TYPES) {
c >>= 1;
++ret;
}
return ret;
}
std::vector<size_t> getBlockTypeSize() {
std::vector<size_t> ret;
ret.resize(BLOCK_TYPES);
size_t block_size = MIN_BLOCK_SIZE;
for (auto i = 0; i < BLOCK_TYPES; ++i) {
ret[i] = block_size;
block_size <<= 1;
}
return ret;
}
class MemThreadInfo {
public:
using Ptr = std::shared_ptr<MemThreadInfo>;
atomic<uint64_t> mem_usage { 0 };
atomic<uint64_t> mem_block { 0 };
atomic<uint64_t> mem_block_map[BLOCK_TYPES];
static MemThreadInfo *Instance(bool is_thread_local) {
if (!is_thread_local) {
static auto instance = new MemThreadInfo(is_thread_local);
return instance;
}
static auto thread_local instance = new MemThreadInfo(is_thread_local);
return instance;
}
~MemThreadInfo() {
// printf("%s %d\r\n", __FUNCTION__, (int) _is_thread_local);
}
MemThreadInfo(bool is_thread_local) {
_is_thread_local = is_thread_local;
if (_is_thread_local) {
// 确保所有线程退出后才能释放全局内存统计器 [AUTO-TRANSLATED:edb51704]
// Ensure that all threads exit before releasing the global memory statistics
total_mem = Instance(false);
}
// printf("%s %d\r\n", __FUNCTION__, (int) _is_thread_local);
}
void *operator new(size_t sz) { return __real_malloc(sz); }
void operator delete(void *ptr) { __real_free(ptr); }
void addBlock(size_t c) {
if (total_mem) {
total_mem->addBlock(c);
}
mem_usage += c;
++mem_block_map[get_mem_block_type(c)];
++mem_block;
}
void delBlock(size_t c) {
if (total_mem) {
total_mem->delBlock(c);
}
mem_usage -= c;
--mem_block_map[get_mem_block_type(c)];
if (0 == --mem_block) {
delete this;
}
}
private:
bool _is_thread_local;
MemThreadInfo *total_mem = nullptr;
};
class MemThreadInfoLocal {
public:
MemThreadInfoLocal() {
ptr = MemThreadInfo::Instance(true);
ptr->addBlock(1);
}
~MemThreadInfoLocal() { ptr->delBlock(1); }
MemThreadInfo *get() const { return ptr; }
private:
MemThreadInfo *ptr;
};
// 该变量主要确保线程退出后才能释放MemThreadInfo变量 [AUTO-TRANSLATED:a72494b0]
// This variable mainly ensures that the MemThreadInfo variable can be released only after the thread exits
static thread_local MemThreadInfoLocal s_thread_mem_info;
uint64_t getTotalMemUsage() {
return MemThreadInfo::Instance(false)->mem_usage.load();
}
uint64_t getTotalMemBlock() {
return MemThreadInfo::Instance(false)->mem_block.load();
}
uint64_t getTotalMemBlockByType(int type) {
assert(type < BLOCK_TYPES);
return MemThreadInfo::Instance(false)->mem_block_map[type].load();
}
uint64_t getThisThreadMemUsage() {
return MemThreadInfo::Instance(true)->mem_usage.load();
}
uint64_t getThisThreadMemBlock() {
return MemThreadInfo::Instance(true)->mem_block.load();
}
uint64_t getThisThreadMemBlockByType(int type) {
assert(type < BLOCK_TYPES);
return MemThreadInfo::Instance(true)->mem_block_map[type].load();
}
class MemCookie {
public:
static constexpr uint32_t kMagic = 0xFEFDFCFB;
uint32_t magic;
uint32_t size;
MemThreadInfo *alloc_info;
char ptr;
};
#define MEM_OFFSET offsetof(MemCookie, ptr)
#if (defined(__linux__) && !defined(ANDROID)) || defined(__MACH__)
#define MAX_STACK_FRAMES 128
#define MEM_WARING
#include <execinfo.h>
#include <limits.h>
#include <sys/resource.h>
#include <sys/wait.h>
static void print_mem_waring(size_t c) {
void *array[MAX_STACK_FRAMES];
int size = backtrace(array, MAX_STACK_FRAMES);
char **strings = backtrace_symbols(array, size);
printf("malloc big memory:%d, back trace:\r\n", (int)c);
for (int i = 0; i < size; ++i) {
printf("[%d]: %s\r\n", i, strings[i]);
}
__real_free(strings);
}
#endif
static void init_cookie(MemCookie *cookie, size_t c) {
cookie->magic = MemCookie::kMagic;
cookie->size = c;
cookie->alloc_info = s_thread_mem_info.get();
cookie->alloc_info->addBlock(c);
#if defined(MEM_WARING)
static auto env = getenv("MEM_WARN_SIZE");
static size_t s_mem_waring_size = atoll(env ? env : "0");
if (s_mem_waring_size > 1024 && c >= s_mem_waring_size) {
print_mem_waring(c);
}
#endif
}
static void un_init_cookie(MemCookie *cookie) {
cookie->alloc_info->delBlock(cookie->size);
}
void *__wrap_malloc(size_t c) {
c += MEM_OFFSET;
auto cookie = (MemCookie *)__real_malloc(c);
if (cookie) {
init_cookie(cookie, c);
return &cookie->ptr;
}
return nullptr;
}
void __wrap_free(void *ptr) {
if (!ptr) {
return;
}
auto cookie = (MemCookie *)((char *)ptr - MEM_OFFSET);
if (cookie->magic != MemCookie::kMagic) {
__real_free(ptr);
return;
}
un_init_cookie(cookie);
__real_free(cookie);
}
void *__wrap_calloc(size_t __nmemb, size_t __size) {
auto size = __nmemb * __size;
auto ret = malloc(size);
if (ret) {
memset(ret, 0, size);
}
return ret;
}
void *__wrap_realloc(void *ptr, size_t c) {
if (!ptr) {
return malloc(c);
}
auto cookie = (MemCookie *)((char *)ptr - MEM_OFFSET);
if (cookie->magic != MemCookie::kMagic) {
return __real_realloc(ptr, c);
}
un_init_cookie(cookie);
c += MEM_OFFSET;
cookie = (MemCookie *)__real_realloc(cookie, c);
if (cookie) {
init_cookie(cookie, c);
return &cookie->ptr;
}
return nullptr;
}
void *operator new(std::size_t size) {
auto ret = malloc(size);
if (ret) {
return ret;
}
throw std::bad_alloc();
}
void operator delete(void *ptr) noexcept {
free(ptr);
}
void operator delete(void *ptr, std::size_t) noexcept {
free(ptr);
}
void *operator new[](std::size_t size) {
auto ret = malloc(size);
if (ret) {
return ret;
}
throw std::bad_alloc();
}
void operator delete[](void *ptr) noexcept {
free(ptr);
}
void operator delete[](void *ptr, std::size_t) noexcept {
free(ptr);
}
#endif

630
MediaServer/Common/config.h Normal file
View File

@ -0,0 +1,630 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef COMMON_CONFIG_H
#define COMMON_CONFIG_H
#include "Util/NoticeCenter.h"
#include "Util/mini.h"
#include "Util/onceToken.h"
#include "macros.h"
#include <functional>
namespace mediakit {
class ProtocolOption;
// 加载配置文件,如果配置文件不存在,那么会导出默认配置并生成配置文件 [AUTO-TRANSLATED:16d0b898]
// Load the configuration file. If the configuration file does not exist, the default configuration will be exported and the configuration file will be generated.
// 加载配置文件成功后会触发kBroadcastUpdateConfig广播 [AUTO-TRANSLATED:327e5be2]
// After the configuration file is loaded successfully, the kBroadcastUpdateConfig broadcast will be triggered.
// 如果指定的文件名(ini_path)为空,那么会加载默认配置文件 [AUTO-TRANSLATED:e241a2b7]
// If the specified file name (ini_path) is empty, the default configuration file will be loaded.
// 默认配置文件名为 /path/to/your/exe.ini [AUTO-TRANSLATED:2d1acfcb]
// The default configuration file name is /path/to/your/exe.ini
// 加载配置文件成功后返回true否则返回false [AUTO-TRANSLATED:cba43e43]
// Returns true if the configuration file is loaded successfully, otherwise returns false.
bool loadIniConfig(const char *ini_path = nullptr);
// //////////广播名称/////////// [AUTO-TRANSLATED:439b2d74]
// //////////Broadcast Name///////////
namespace Broadcast {
// 注册或反注册MediaSource事件广播 [AUTO-TRANSLATED:ec55c1cf]
// Register or unregister MediaSource event broadcast
extern const std::string kBroadcastMediaChanged;
#define BroadcastMediaChangedArgs const bool &bRegist, MediaSource &sender
// 录制mp4文件成功后广播 [AUTO-TRANSLATED:479ec954]
// Broadcast after recording mp4 file successfully
extern const std::string kBroadcastRecordMP4;
#define BroadcastRecordMP4Args const RecordInfo &info
// 录制 ts 文件后广播 [AUTO-TRANSLATED:63a8868c]
// Broadcast after recording ts file
extern const std::string kBroadcastRecordTs;
#define BroadcastRecordTsArgs const RecordInfo &info
// 收到http api请求广播 [AUTO-TRANSLATED:c72e7c3f]
// Broadcast for receiving http api request
extern const std::string kBroadcastHttpRequest;
#define BroadcastHttpRequestArgs const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, SockInfo &sender
// 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 [AUTO-TRANSLATED:2de426b4]
// In the http file server, broadcast for receiving http access to files or directories. Control access permissions to the http directory through this event.
extern const std::string kBroadcastHttpAccess;
#define BroadcastHttpAccessArgs const Parser &parser, const std::string &path, const bool &is_dir, const HttpSession::HttpAccessPathInvoker &invoker, SockInfo &sender
// 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 [AUTO-TRANSLATED:0294d0c5]
// In the http file server, broadcast before receiving http access to files or directories. Control the mapping from http url to file path through this event.
// 在该事件中通过自行覆盖path参数可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 [AUTO-TRANSLATED:1bea3efb]
// By overriding the path parameter in this event, you can achieve the purpose of selecting different http root directories based on virtual hosts or apps.
extern const std::string kBroadcastHttpBeforeAccess;
#define BroadcastHttpBeforeAccessArgs const Parser &parser, std::string &path, SockInfo &sender
// 该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 [AUTO-TRANSLATED:5f436d8f]
// Does this stream need authentication? If yes, call invoker and pass in realm, otherwise pass in an empty realm. If this event is not listened to, no authentication will be performed.
extern const std::string kBroadcastOnGetRtspRealm;
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args, const RtspSession::onGetRealm &invoker, SockInfo &sender
// 请求认证用户密码事件user_name为用户名must_no_encrypt如果为true则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 [AUTO-TRANSLATED:22b6dfcc]
// Request authentication user password event, user_name is the username, must_no_encrypt if true, then the plaintext password must be provided (because it is base64 authentication method at this time), otherwise it will lead to authentication failure.
// 获取到密码后请调用invoker并输入对应类型的密码和密码类型invoker执行时会匹配密码 [AUTO-TRANSLATED:8c57fd43]
// After getting the password, please call invoker and input the corresponding type of password and password type. The invoker will match the password when executing.
extern const std::string kBroadcastOnRtspAuth;
#define BroadcastOnRtspAuthArgs const MediaInfo &args, const std::string &realm, const std::string &user_name, const bool &must_no_encrypt, const RtspSession::onAuth &invoker, SockInfo &sender
// 推流鉴权结果回调对象 [AUTO-TRANSLATED:7e508ed1]
// Push stream authentication result callback object
// 如果err为空则代表鉴权成功 [AUTO-TRANSLATED:d49b0544]
// If err is empty, it means authentication is successful.
using PublishAuthInvoker = std::function<void(const std::string &err, const ProtocolOption &option)>;
// 收到rtsp/rtmp推流事件广播通过该事件控制推流鉴权 [AUTO-TRANSLATED:72417373]
// Broadcast for receiving rtsp/rtmp push stream event. Control push stream authentication through this event.
extern const std::string kBroadcastMediaPublish;
#define BroadcastMediaPublishArgs const MediaOriginType &type, const MediaInfo &args, const Broadcast::PublishAuthInvoker &invoker, SockInfo &sender
// 播放鉴权结果回调对象 [AUTO-TRANSLATED:c980162b]
// Playback authentication result callback object
// 如果err为空则代表鉴权成功 [AUTO-TRANSLATED:d49b0544]
// If err is empty, it means authentication is successful.
using AuthInvoker = std::function<void(const std::string &err)>;
// 播放rtsp/rtmp/http-flv事件广播通过该事件控制播放鉴权 [AUTO-TRANSLATED:eddd7014]
// Broadcast for playing rtsp/rtmp/http-flv events. Control playback authentication through this event.
extern const std::string kBroadcastMediaPlayed;
#define BroadcastMediaPlayedArgs const MediaInfo &args, const Broadcast::AuthInvoker &invoker, SockInfo &sender
// shell登录鉴权 [AUTO-TRANSLATED:26b135d4]
// Shell login authentication
extern const std::string kBroadcastShellLogin;
#define BroadcastShellLoginArgs const std::string &user_name, const std::string &passwd, const Broadcast::AuthInvoker &invoker, SockInfo &sender
// 停止rtsp/rtmp/http-flv会话后流量汇报事件广播 [AUTO-TRANSLATED:69df61d8]
// Broadcast for traffic reporting event after stopping rtsp/rtmp/http-flv session
extern const std::string kBroadcastFlowReport;
#define BroadcastFlowReportArgs const MediaInfo &args, const uint64_t &totalBytes, const uint64_t &totalDuration, const bool &isPlayer, SockInfo &sender
// 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 [AUTO-TRANSLATED:0c00171d]
// This event will be broadcast after the stream is not found. Please pull the stream or other methods to generate the stream after listening to this event, so that you can pull the stream on demand.
extern const std::string kBroadcastNotFoundStream;
#define BroadcastNotFoundStreamArgs const MediaInfo &args, SockInfo &sender, const std::function<void()> &closePlayer
// 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑 [AUTO-TRANSLATED:3c45f002]
// Triggered when a stream is not consumed by anyone. The purpose is to achieve business logic such as actively disconnecting the pull stream when no one is watching.
extern const std::string kBroadcastStreamNoneReader;
#define BroadcastStreamNoneReaderArgs MediaSource &sender
// rtp推流被动停止时触发 [AUTO-TRANSLATED:43881965]
// Triggered when rtp push stream is passively stopped.
extern const std::string kBroadcastSendRtpStopped;
#define BroadcastSendRtpStoppedArgs MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
// 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播 [AUTO-TRANSLATED:ad4e167d]
// Update configuration file event broadcast. This broadcast will be triggered after the loadIniConfig function loads the configuration file successfully.
extern const std::string kBroadcastReloadConfig;
#define BroadcastReloadConfigArgs void
// rtp server 超时 [AUTO-TRANSLATED:a65573fd]
// Rtp server timeout
extern const std::string kBroadcastRtpServerTimeout;
#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const MediaTuple &tuple, int &tcp_mode, bool &re_use_port, uint32_t &ssrc
// rtc transport sctp 连接状态 [AUTO-TRANSLATED:f00284da]
// Rtc transport sctp connection status
extern const std::string kBroadcastRtcSctpConnecting;
extern const std::string kBroadcastRtcSctpConnected;
extern const std::string kBroadcastRtcSctpFailed;
extern const std::string kBroadcastRtcSctpClosed;
#define BroadcastRtcSctpConnectArgs WebRtcTransport& sender
// rtc transport sctp 发送数据 [AUTO-TRANSLATED:258f1ba8]
// rtc transport sctp send data
extern const std::string kBroadcastRtcSctpSend;
#define BroadcastRtcSctpSendArgs WebRtcTransport& sender, const uint8_t *&data, size_t& len
// rtc transport sctp 接收数据 [AUTO-TRANSLATED:ce4cb57e]
// rtc transport sctp receive data
extern const std::string kBroadcastRtcSctpReceived;
#define BroadcastRtcSctpReceivedArgs WebRtcTransport& sender, uint16_t &streamId, uint32_t &ppid, const uint8_t *&msg, size_t &len
// 观看人数变化广播 [AUTO-TRANSLATED:5b246b54]
// broadcast viewer count changes
extern const std::string kBroadcastPlayerCountChanged;
#define BroadcastPlayerCountChangedArgs const MediaTuple& args, const int& count
#define ReloadConfigTag ((void *)(0xFF))
#define RELOAD_KEY(arg, key) \
do { \
decltype(arg) arg##_tmp = ::toolkit::mINI::Instance()[key]; \
if (arg == arg##_tmp) { \
return; \
} \
arg = arg##_tmp; \
InfoL << "reload config:" << key << "=" << arg; \
} while (0)
// 监听某个配置发送变更 [AUTO-TRANSLATED:7f46b5b1]
// listen for configuration changes
#define LISTEN_RELOAD_KEY(arg, key, ...) \
do { \
static ::toolkit::onceToken s_token_listen([]() { \
::toolkit::NoticeCenter::Instance().addListener( \
ReloadConfigTag, Broadcast::kBroadcastReloadConfig, [](BroadcastReloadConfigArgs) { __VA_ARGS__; }); \
}); \
} while (0)
#define GET_CONFIG(type, arg, key) \
static type arg = ::toolkit::mINI::Instance()[key]; \
LISTEN_RELOAD_KEY(arg, key, { RELOAD_KEY(arg, key); });
#define GET_CONFIG_FUNC(type, arg, key, ...) \
static type arg; \
do { \
static ::toolkit::onceToken s_token_set([]() { \
static auto lam = __VA_ARGS__; \
static auto arg##_str = ::toolkit::mINI::Instance()[key]; \
arg = lam(arg##_str); \
LISTEN_RELOAD_KEY(arg, key, { \
RELOAD_KEY(arg##_str, key); \
arg = lam(arg##_str); \
}); \
}); \
} while (0)
} // namespace Broadcast
// //////////通用配置/////////// [AUTO-TRANSLATED:b09b9640]
// //////////General Configuration///////////
namespace General {
// 每个流媒体服务器的IDGUID [AUTO-TRANSLATED:c6ac6e56]
// ID (GUID) of each media server
extern const std::string kMediaServerId;
// 流量汇报事件流量阈值,单位KB默认1MB [AUTO-TRANSLATED:fd036326]
// Traffic reporting event traffic threshold, unit KB, default 1MB
extern const std::string kFlowThreshold;
// 流无人观看并且超过若干时间后才触发kBroadcastStreamNoneReader事件 [AUTO-TRANSLATED:baeea387]
// Trigger kBroadcastStreamNoneReader event only after the stream has been unwatched for a certain period of time
// 默认连续5秒无人观看然后触发kBroadcastStreamNoneReader事件 [AUTO-TRANSLATED:477bf488]
// Default to trigger kBroadcastStreamNoneReader event after 5 seconds of no viewers
extern const std::string kStreamNoneReaderDelayMS;
// 等待流注册超时时间,收到播放器后请求后,如果未找到相关流,服务器会等待一定时间, [AUTO-TRANSLATED:7ccd518d]
// Stream registration timeout, after receiving the player's request, if the related stream is not found, the server will wait for a certain period of time,
// 如果在这个时间内,相关流注册上了,那么服务器会立即响应播放器播放成功, [AUTO-TRANSLATED:93ef0249]
// If the related stream is registered within this time, the server will immediately respond to the player that the playback is successful,
// 否则会最多等待kMaxStreamWaitTimeMS毫秒然后响应播放器播放失败 [AUTO-TRANSLATED:f8c9f19e]
// Otherwise, it will wait for a maximum of kMaxStreamWaitTimeMS milliseconds and then respond to the player that the playback failed
extern const std::string kMaxStreamWaitTimeMS;
// 是否启动虚拟主机 [AUTO-TRANSLATED:e1cea728]
// Whether to enable virtual host
extern const std::string kEnableVhost;
// 拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, [AUTO-TRANSLATED:d150ffaa]
// When pulling stream proxy, whether to delete the previous media stream data if the stream is disconnected and reconnected successfully, if deleted, it will start again,
// 如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) [AUTO-TRANSLATED:21a5be7e]
// If not deleted, it will continue to write from the previous data (when recording hls/mp4, it will continue to write after the previous file)
extern const std::string kResetWhenRePlay;
// 合并写缓存大小(单位毫秒)合并写指服务器缓存一定的数据后才会一次性写入socket这样能提高性能但是会提高延时 [AUTO-TRANSLATED:6cc6fcf7]
// Merge write cache size (unit milliseconds), merge write refers to the server caching a certain amount of data before writing to the socket at once, which can improve performance but increase latency
// 开启后会同时关闭TCP_NODELAY并开启MSG_MORE [AUTO-TRANSLATED:953b82cf]
// When enabled, TCP_NODELAY will be closed and MSG_MORE will be enabled at the same time
extern const std::string kMergeWriteMS;
// 在docker环境下不能通过英伟达驱动是否存在来判断是否支持硬件转码 [AUTO-TRANSLATED:de678431]
// In the docker environment, the existence of the NVIDIA driver cannot be used to determine whether hardware transcoding is supported
extern const std::string kCheckNvidiaDev;
// 是否开启ffmpeg日志 [AUTO-TRANSLATED:038b471e]
// Whether to enable ffmpeg log
extern const std::string kEnableFFmpegLog;
// 最多等待未初始化的Track 10秒超时之后会忽略未初始化的Track [AUTO-TRANSLATED:826cd533]
// Maximum wait time for uninitialized Track is 10 seconds, after timeout, uninitialized Track will be ignored
extern const std::string kWaitTrackReadyMS;
//最多等待音频Track收到数据时间单位毫秒超时且完全没收到音频数据忽略音频Track
//加快某些带封装的流metadata说明有音频但是实际上没有的流ready时间比如很多厂商的GB28181 PS
extern const std::string kWaitAudioTrackDataMS;
// 如果直播流只有单Track最多等待3秒超时后未收到其他Track的数据则认为是单Track [AUTO-TRANSLATED:0e7a364d]
// If the live stream has only one Track, wait for a maximum of 3 seconds, if no data from other Tracks is received after timeout, it is considered a single Track
// 如果协议元数据有声明特定track数那么无此等待时间 [AUTO-TRANSLATED:76606846]
// If the protocol metadata declares a specific number of tracks, there is no such waiting time
extern const std::string kWaitAddTrackMS;
// 如果track未就绪我们先缓存帧数据但是有最大个数限制(100帧时大约4秒),防止内存溢出 [AUTO-TRANSLATED:c520054f]
// If the track is not ready, we will cache the frame data first, but there is a maximum number limit (100 frames is about 4 seconds) to prevent memory overflow
extern const std::string kUnreadyFrameCache;
// 是否启用观看人数变化事件广播置1则启用置0则关闭 [AUTO-TRANSLATED:3b7f0748]
// Whether to enable viewer count change event broadcast, set to 1 to enable, set to 0 to disable
extern const std::string kBroadcastPlayerCountChanged;
// 绑定的本地网卡ip [AUTO-TRANSLATED:daa90832]
// Bound local network card ip
extern const std::string kListenIP;
} // namespace General
namespace Protocol {
static constexpr char kFieldName[] = "protocol.";
// 时间戳修复这一路流标志位 [AUTO-TRANSLATED:cc208f41]
// Timestamp repair flag for this stream
extern const std::string kModifyStamp;
// 转协议是否开启音频 [AUTO-TRANSLATED:220dddfa]
// Whether to enable audio for protocol conversion
extern const std::string kEnableAudio;
// 添加静音音频,在关闭音频时,此开关无效 [AUTO-TRANSLATED:47c0ec8e]
// Add silent audio, this switch is invalid when audio is closed
extern const std::string kAddMuteAudio;
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) [AUTO-TRANSLATED:dba7ab70]
// When there are no viewers, whether to close directly (instead of returning close through the on_none_reader hook)
// 此配置置1时此流如果无人观看将不触发on_none_reader hook回调 [AUTO-TRANSLATED:a5ead314]
// When this configuration is set to 1, if this stream has no viewers, it will not trigger the on_none_reader hook callback,
// 而是将直接关闭流 [AUTO-TRANSLATED:06887d49]
// Instead, it will directly close the stream
extern const std::string kAutoClose;
// 断连续推延时,单位毫秒,默认采用配置文件 [AUTO-TRANSLATED:7a15b12f]
// When the continuous delay is interrupted, the unit is milliseconds, and the configuration file is used by default
extern const std::string kContinuePushMS;
// 平滑发送定时器间隔单位毫秒置0则关闭开启后影响cpu性能同时增加内存 [AUTO-TRANSLATED:ad4e306a]
// Smooth sending timer interval, unit is milliseconds, set to 0 to close; enabling it will affect CPU performance and increase memory
// 该配置开启后可以解决一些流发送不平滑导致zlmediakit转发也不平滑的问题 [AUTO-TRANSLATED:0f2b1657]
// Enabling this configuration can solve some problems where the stream is not sent smoothly, resulting in ZLMediaKit forwarding not being smooth
extern const std::string kPacedSenderMS;
// 是否开启转换为hls(mpegts) [AUTO-TRANSLATED:bfc1167a]
// Whether to enable conversion to HLS (MPEGTS)
extern const std::string kEnableHls;
// 是否开启转换为hls(fmp4) [AUTO-TRANSLATED:20548673]
// Whether to enable conversion to HLS (FMP4)
extern const std::string kEnableHlsFmp4;
// 是否开启MP4录制 [AUTO-TRANSLATED:0157b014]
// Whether to enable MP4 recording
extern const std::string kEnableMP4;
// 是否开启转换为rtsp/webrtc [AUTO-TRANSLATED:0711cb18]
// Whether to enable conversion to RTSP/WebRTC
extern const std::string kEnableRtsp;
// 是否开启转换为rtmp/flv [AUTO-TRANSLATED:d4774119]
// Whether to enable conversion to RTMP/FLV
extern const std::string kEnableRtmp;
// 是否开启转换为http-ts/ws-ts [AUTO-TRANSLATED:51acc798]
// Whether to enable conversion to HTTP-TS/WS-TS
extern const std::string kEnableTS;
// 是否开启转换为http-fmp4/ws-fmp4 [AUTO-TRANSLATED:8c96e1e4]
// Whether to enable conversion to HTTP-FMP4/WS-FMP4
extern const std::string kEnableFMP4;
// 是否将mp4录制当做观看者 [AUTO-TRANSLATED:ba351230]
// Whether to treat MP4 recording as a viewer
extern const std::string kMP4AsPlayer;
// mp4切片大小单位秒 [AUTO-TRANSLATED:c3fb8ec1]
// MP4 fragment size, unit is seconds
extern const std::string kMP4MaxSecond;
// mp4录制保存路径 [AUTO-TRANSLATED:6d860f27]
// MP4 recording save path
extern const std::string kMP4SavePath;
// hls录制保存路径 [AUTO-TRANSLATED:cfa90719]
// HLS recording save path
extern const std::string kHlsSavePath;
// 按需转协议的开关 [AUTO-TRANSLATED:9f350899]
// On-demand protocol conversion switch
extern const std::string kHlsDemand;
extern const std::string kRtspDemand;
extern const std::string kRtmpDemand;
extern const std::string kTSDemand;
extern const std::string kFMP4Demand;
} // !Protocol
// //////////HTTP配置/////////// [AUTO-TRANSLATED:a281d694]
// //////////HTTP configuration///////////
namespace Http {
// http 文件发送缓存大小 [AUTO-TRANSLATED:51fb08c0]
// HTTP file sending cache size
extern const std::string kSendBufSize;
// http 最大请求字节数 [AUTO-TRANSLATED:8239eb9c]
// HTTP maximum request byte size
extern const std::string kMaxReqSize;
// http keep-alive秒数 [AUTO-TRANSLATED:d4930c66]
// HTTP keep-alive seconds
extern const std::string kKeepAliveSecond;
// http 字符编码 [AUTO-TRANSLATED:f7e55c83]
// HTTP character encoding
extern const std::string kCharSet;
// http 服务器根目录 [AUTO-TRANSLATED:f8f55daf]
// HTTP server root directory
extern const std::string kRootPath;
// http 服务器虚拟目录 虚拟目录名和文件路径使用","隔开,多个配置路径间用";"隔开,例如 path_d,d:/record;path_e,e:/record [AUTO-TRANSLATED:fa4ee929]
// HTTP server virtual directory. Virtual directory name and file path are separated by ",", and multiple configuration paths are separated by ";", for example, path_d,d:/record;path_e,e:/record
extern const std::string kVirtualPath;
// http 404错误提示内容 [AUTO-TRANSLATED:91adb026]
// HTTP 404 error prompt content
extern const std::string kNotFound;
// 是否显示文件夹菜单 [AUTO-TRANSLATED:77301b85]
// Whether to display the folder menu
extern const std::string kDirMenu;
// 禁止缓存文件的后缀 [AUTO-TRANSLATED:92bcb7f7]
// Forbidden cache file suffixes
extern const std::string kForbidCacheSuffix;
// 可以把http代理前真实客户端ip放在http头中https://github.com/ZLMediaKit/ZLMediaKit/issues/1388 [AUTO-TRANSLATED:afcd9556]
// You can put the real client IP address before the HTTP proxy in the HTTP header: https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
extern const std::string kForwardedIpHeader;
// 是否允许所有跨域请求 [AUTO-TRANSLATED:2551c096]
// Whether to allow all cross-domain requests
extern const std::string kAllowCrossDomains;
// 允许访问http api和http文件索引的ip地址范围白名单置空情况下不做限制 [AUTO-TRANSLATED:ab939863]
// Whitelist of IP address ranges allowed to access HTTP API and HTTP file index. No restrictions are imposed when empty
extern const std::string kAllowIPRange;
} // namespace Http
// //////////SHELL配置/////////// [AUTO-TRANSLATED:f023ec45]
// //////////SHELL configuration///////////
namespace Shell {
extern const std::string kMaxReqSize;
} // namespace Shell
// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
// //////////RTSP Server Configuration///////////
namespace Rtsp {
// 是否优先base64方式认证默认Md5方式认证 [AUTO-TRANSLATED:0ea332b5]
// Is base64 authentication prioritized? Default is Md5 authentication
extern const std::string kAuthBasic;
// 握手超时时间默认15秒 [AUTO-TRANSLATED:6f69a65b]
// Handshake timeout, default 15 seconds
extern const std::string kHandshakeSecond;
// 维持链接超时时间默认15秒 [AUTO-TRANSLATED:b6339c90]
// Keep-alive timeout, default 15 seconds
extern const std::string kKeepAliveSecond;
// rtsp拉流代理是否直接代理 [AUTO-TRANSLATED:9cd82709]
// Whether RTSP pull stream proxy is direct proxy
// 直接代理后支持任意编码格式但是会导致GOP缓存无法定位到I帧可能会导致开播花屏 [AUTO-TRANSLATED:36525a92]
// Direct proxy supports any encoding format, but it will cause GOP cache unable to locate I-frame, which may lead to screen flickering
// 并且如果是tcp方式拉流如果rtp大于mtu会导致无法使用udp方式代理 [AUTO-TRANSLATED:a1ab467e]
// And if it is TCP pull stream, if RTP is larger than MTU, it will not be able to use UDP proxy
// 假定您的拉流源地址不是264或265或AAC那么你可以使用直接代理的方式来支持rtsp代理 [AUTO-TRANSLATED:9efaedcd]
// Assuming your pull stream source address is not 264 or 265 or AAC, then you can use direct proxy to support RTSP proxy
// 默认开启rtsp直接代理rtmp由于没有这些问题是强制开启直接代理的 [AUTO-TRANSLATED:0e55d051]
// Default to enable RTSP direct proxy, RTMP does not have these problems, it is forced to enable direct proxy
extern const std::string kDirectProxy;
// rtsp 转发是否使用低延迟模式当开启时不会缓存rtp包来提高并发可以降低一帧的延迟 [AUTO-TRANSLATED:f6fe8c6c]
// Whether RTSP forwarding uses low latency mode, when enabled, it will not cache RTP packets to improve concurrency and reduce one frame delay
extern const std::string kLowLatency;
// 强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制) [AUTO-TRANSLATED:38574ed5]
// Force negotiation of RTP transport method (0: TCP, 1: UDP, 2: MULTICAST, -1: no restriction)
// 当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupport Transport [AUTO-TRANSLATED:b0fd0336]
// When the client initiates RTSP SETUP, if the transport type is inconsistent with this configuration, it will return 461 Unsupport Transport
// 迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC [AUTO-TRANSLATED:45f9cddb]
// Force the client to re-SETUP and switch to the corresponding protocol. Currently supports FFMPEG and VLC
extern const std::string kRtpTransportType;
} // namespace Rtsp
// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
// //////////RTMP Server Configuration///////////
namespace Rtmp {
// 握手超时时间默认15秒 [AUTO-TRANSLATED:6f69a65b]
// Handshake timeout, default 15 seconds
extern const std::string kHandshakeSecond;
// 维持链接超时时间默认15秒 [AUTO-TRANSLATED:b6339c90]
// Keep-alive timeout, default 15 seconds
extern const std::string kKeepAliveSecond;
// 是否直接代理 [AUTO-TRANSLATED:25268b70]
// Whether direct proxy
extern const std::string kDirectProxy;
// h265-rtmp是否采用增强型(或者国内扩展) [AUTO-TRANSLATED:4a52d042]
// Whether h265-rtmp uses enhanced (or domestic extension)
extern const std::string kEnhanced;
} // namespace Rtmp
// //////////RTP配置/////////// [AUTO-TRANSLATED:23cbcb86]
// //////////RTP Configuration///////////
namespace Rtp {
// RTP打包最大MTU,公网情况下更小 [AUTO-TRANSLATED:869f5c4b]
// Maximum RTP packet MTU, smaller in public network
extern const std::string kVideoMtuSize;
// RTP打包最大MTU,公网情况下更小 [AUTO-TRANSLATED:869f5c4b]
// Maximum RTP packet MTU, smaller in public network
extern const std::string kAudioMtuSize;
// rtp包最大长度限制, 单位KB [AUTO-TRANSLATED:1da42584]
// Maximum RTP packet length limit, unit KB
extern const std::string kRtpMaxSize;
// rtp 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏 [AUTO-TRANSLATED:4cf0cb8d]
// When RTP is packaged, low latency switch, default off (0), H264 has multiple slices (NAL) in one frame, in this case, if enabled, it may cause screen flickering
extern const std::string kLowLatency;
// H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式 [AUTO-TRANSLATED:30632378]
// Whether H264 RTP packaging mode uses stap-a mode (for compatibility with webrtc on older browsers) or Single NAL unit packet per H.264 mode
extern const std::string kH264StapA;
} // namespace Rtp
// //////////组播配置/////////// [AUTO-TRANSLATED:dc39b9d6]
// //////////Multicast Configuration///////////
namespace MultiCast {
// 组播分配起始地址 [AUTO-TRANSLATED:069db91d]
// Multicast allocation start address
extern const std::string kAddrMin;
// 组播分配截止地址 [AUTO-TRANSLATED:6d3fc54c]
// Multicast allocation end address
extern const std::string kAddrMax;
// 组播TTL [AUTO-TRANSLATED:c7c5339c]
// Multicast TTL
extern const std::string kUdpTTL;
} // namespace MultiCast
// //////////录像配置/////////// [AUTO-TRANSLATED:19de3e96]
// //////////Recording Configuration///////////
namespace Record {
// 查看录像的应用名称 [AUTO-TRANSLATED:a71b5daf]
// Application name for viewing recordings
extern const std::string kAppName;
// 每次流化MP4文件的时长,单位毫秒 [AUTO-TRANSLATED:0add878d]
// Duration of each MP4 file streaming, in milliseconds
extern const std::string kSampleMS;
// mp4文件写缓存大小 [AUTO-TRANSLATED:9904413d]
// MP4 file write cache size
extern const std::string kFileBufSize;
// mp4录制完成后是否进行二次关键帧索引写入头部 [AUTO-TRANSLATED:53cfdcb5]
// Whether to perform secondary keyframe index writing to the header after MP4 recording is completed
extern const std::string kFastStart;
// mp4文件是否重头循环读取 [AUTO-TRANSLATED:69ac72de]
// Whether to loop read the MP4 file from the beginning
extern const std::string kFileRepeat;
// mp4录制文件是否采用fmp4格式 [AUTO-TRANSLATED:12559ae0]
// Whether to use fmp4 format for MP4 recording files
extern const std::string kEnableFmp4;
} // namespace Record
// //////////HLS相关配置/////////// [AUTO-TRANSLATED:873cc84c]
// //////////HLS related configuration///////////
namespace Hls {
// HLS切片时长,单位秒 [AUTO-TRANSLATED:ed6a4219]
// HLS slice duration, in seconds
extern const std::string kSegmentDuration;
// m3u8文件中HLS切片个数如果设置为0则不删除切片而是保存为点播 [AUTO-TRANSLATED:92388a5d]
// Number of HLS slices in the m3u8 file. If set to 0, the slices will not be deleted and will be saved as on-demand
extern const std::string kSegmentNum;
// 如果设置为0则不保留切片设置为1则一直保留切片 [AUTO-TRANSLATED:0933fd7b]
// If set to 0, the slices will not be retained, if set to 1, the slices will be retained all the time
extern const std::string kSegmentKeep;
// HLS切片延迟个数大于0将生成hls_delay.m3u8文件0则不生成 [AUTO-TRANSLATED:b1751b00]
// Number of HLS slice delays. Greater than 0 will generate hls_delay.m3u8 file, 0 will not generate
extern const std::string kSegmentDelay;
// HLS切片从m3u8文件中移除后继续保留在磁盘上的个数 [AUTO-TRANSLATED:b7a23e1a]
// Number of HLS slices that continue to be retained on disk after being removed from the m3u8 file
extern const std::string kSegmentRetain;
// HLS文件写缓存大小 [AUTO-TRANSLATED:81832c8b]
// HLS file write cache size
extern const std::string kFileBufSize;
// 是否广播 ts 切片完成通知 [AUTO-TRANSLATED:a53644a2]
// Whether to broadcast ts slice completion notification
extern const std::string kBroadcastRecordTs;
// hls直播文件删除延时单位秒 [AUTO-TRANSLATED:5643cab7]
// HLS live file deletion delay, in seconds
extern const std::string kDeleteDelaySec;
// 如果设置为1则第一个切片长度强制设置为1个GOP [AUTO-TRANSLATED:fbbb651d]
// If set to 1, the length of the first slice is forced to be 1 GOP
extern const std::string kFastRegister;
} // namespace Hls
// //////////Rtp代理相关配置/////////// [AUTO-TRANSLATED:7b285587]
// //////////Rtp proxy related configuration///////////
namespace RtpProxy {
// rtp调试数据保存目录,置空则不生成 [AUTO-TRANSLATED:aa004af0]
// Rtp debug data save directory, empty if not generated
extern const std::string kDumpDir;
// rtp接收超时时间 [AUTO-TRANSLATED:9e918489]
// Rtp receive timeout
extern const std::string kTimeoutSec;
// 随机端口范围最少确保36个端口 [AUTO-TRANSLATED:2f2b6b17]
// Random port range, at least 36 ports are guaranteed
// 该范围同时限制rtsp服务器udp端口范围 [AUTO-TRANSLATED:1ff8fd75]
// This range also limits the rtsp server udp port range
extern const std::string kPortRange;
// rtp server h264的pt [AUTO-TRANSLATED:b8cf877b]
// Rtp server h264 pt
extern const std::string kH264PT;
// rtp server h265的pt [AUTO-TRANSLATED:2bdb1dfb]
// Rtp server h265 pt
extern const std::string kH265PT;
// rtp server ps 的pt [AUTO-TRANSLATED:6feaf5f9]
// Rtp server ps pt
extern const std::string kPSPT;
// rtp server opus 的pt [AUTO-TRANSLATED:9f91f85a]
// Rtp server opus pt
extern const std::string kOpusPT;
// RtpSender相关功能是否提前开启gop缓存优化级联秒开体验默认开启 [AUTO-TRANSLATED:40c37c77]
// Whether to enable gop cache optimization cascade second-open experience for RtpSender related functions, enabled by default
extern const std::string kGopCache;
// 国标发送g711 rtp 打包时每个包的语音时长是多少默认是100 ms范围为20~180ms (gb28181-2016c.2.4规定) [AUTO-TRANSLATED:3b3916a3]
// When sending g711 rtp packets in national standard, what is the duration of each packet, the default is 100 ms, the range is 20~180ms (gb28181-2016, c.2.4),
// 最好为20 的倍数程序自动向20的倍数取整 [AUTO-TRANSLATED:7bc6e0ec]
// It is best to be a multiple of 20, the program automatically rounds to the nearest multiple of 20
extern const std::string kRtpG711DurMs;
// udp recv socket buffer size
extern const std::string kUdpRecvSocketBuffer;
} // namespace RtpProxy
/**
* rtsp/rtmp播放器
*
*
* Rtsp/rtmp player, pusher related settings name,
* These settings are not used in the configuration file
* Only used to set a specific player or pusher instance
* [AUTO-TRANSLATED:59086953]
*/
namespace Client {
// 指定网卡ip [AUTO-TRANSLATED:679fdccb]
// Specify network card ip
extern const std::string kNetAdapter;
// 设置rtp传输类型可选项有0(tcp默认)、1(udp)、2(组播) [AUTO-TRANSLATED:bf73f779]
// Set rtp transport type, options are 0 (tcp, default), 1 (udp), 2 (multicast)
// 设置方法:player[PlayerBase::kRtpType] = 0/1/2; [AUTO-TRANSLATED:30eb2936]
// Set method: player[PlayerBase::kRtpType] = 0/1/2;
extern const std::string kRtpType;
// rtsp播放器发送信令心跳还是rtcp心跳可选项有0(同时发)、1(rtcp心跳)、2(信令心跳) [AUTO-TRANSLATED:56d9ac7c]
// Whether the RTSP player sends signaling heartbeat or RTCP heartbeat, options are 0 (both), 1 (RTCP heartbeat), 2 (signaling heartbeat)
// 设置方法:player[PlayerBase::kRtspBeatType] = 0/1/2; [AUTO-TRANSLATED:ccc0726b]
// Set method: player[PlayerBase::kRtspBeatType] = 0/1/2;
extern const std::string kRtspBeatType;
// rtsp认证用户名 [AUTO-TRANSLATED:5ab80e57]
// RTSP authentication username
extern const std::string kRtspUser;
// rtsp认证用用户密码可以是明文也可以是md5,md5密码生成方式 md5(username:realm:password) [AUTO-TRANSLATED:1228f997]
// RTSP authentication user password, can be plain text or MD5, MD5 password generation method md5(username:realm:password)
extern const std::string kRtspPwd;
// rtsp认证用用户密码是否为md5类型 [AUTO-TRANSLATED:208696d1]
// Whether the RTSP authentication user password is MD5 type
extern const std::string kRtspPwdIsMD5;
// 握手超时时间默认10,000 毫秒 [AUTO-TRANSLATED:44b3f73f]
// Handshake timeout, default 10,000 milliseconds
extern const std::string kTimeoutMS;
// rtp/rtmp包接收超时时间默认5000秒 [AUTO-TRANSLATED:e450d4cc]
// RTP/RTMP packet receive timeout, default 5000 seconds
extern const std::string kMediaTimeoutMS;
// rtsp/rtmp心跳时间,默认5000毫秒 [AUTO-TRANSLATED:4d64f27f]
// RTSP/RTMP heartbeat time, default 5000 milliseconds
extern const std::string kBeatIntervalMS;
// 是否为性能测试模式性能测试模式开启后不会解析rtp或rtmp包 [AUTO-TRANSLATED:be9a797d]
// Whether it is performance test mode, performance test mode will not parse RTP or RTMP packets after being turned on
extern const std::string kBenchmarkMode;
// 播放器在触发播放成功事件时是否等待所有track ready时再回调 [AUTO-TRANSLATED:73523e6d]
// Whether the player waits for all tracks to be ready before calling back when triggering the playback success event
extern const std::string kWaitTrackReady;
// rtsp播放指定track可选项有0(不指定,默认)、1(视频)、2(音频) [AUTO-TRANSLATED:e4f481f9]
// RTSP playback specified track, options are 0 (not specified, default), 1 (video), 2 (audio)
// 设置方法:player[Client::kPlayTrack] = 0/1/2; [AUTO-TRANSLATED:0a2705c8]
// Set method: player[Client::kPlayTrack] = 0/1/2;
extern const std::string kPlayTrack;
// 设置代理url目前只支持http协议 [AUTO-TRANSLATED:c84918cc]
// Set proxy url, currently only supports http protocol
extern const std::string kProxyUrl;
// 设置开始rtsp倍速播放 [AUTO-TRANSLATED:5db03cad]
// Set the start RTSP playback speed
extern const std::string kRtspSpeed;
} // namespace Client
} // namespace mediakit
#endif /* COMMON_CONFIG_H */

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "macros.h"
using namespace toolkit;
#if defined(ENABLE_VERSION)
#include "ZLMVersion.h"
#endif
namespace mediakit {
/**
* MIT协议MIT协议义务的同时ZLMediaKit软件版权信息的义务
* ZLMediaKit提供的各种服务中包括但不限于 "title""Server""User-Agent" "ZLMediaKit"
* ()
* This project adopts a class MIT license. Users, while fulfilling the obligations of the MIT license, should also follow the obligation to retain the copyright information of ZLMediaKit software.
* Users may not remove the "ZLMediaKit" information from the various services provided by ZLMediaKit, including but not limited to the "title", "Server", "User-Agent" fields.
* Otherwise, the main rights holder of this project (project initiator, main author) reserves the right to claim and sue.
* [AUTO-TRANSLATED:f214f734]
*/
#if !defined(ENABLE_VERSION)
const char kServerName[] = "ZLMediaKit-8.0(build in " __DATE__ " " __TIME__ ")";
#else
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")";
#endif
}//namespace mediakit

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MACROS_H
#define ZLMEDIAKIT_MACROS_H
#include <sstream>
#include <iostream>
#include "Util/util.h"
#include "Util/logger.h"
#if defined(__MACH__)
#include <arpa/inet.h>
#include <machine/endian.h>
#define __BYTE_ORDER BYTE_ORDER
#define __BIG_ENDIAN BIG_ENDIAN
#define __LITTLE_ENDIAN LITTLE_ENDIAN
#elif defined(__linux__)
#include <arpa/inet.h>
#include <endian.h>
#elif defined(_WIN32)
#define BIG_ENDIAN 1
#define LITTLE_ENDIAN 0
#define BYTE_ORDER LITTLE_ENDIAN
#define __BYTE_ORDER BYTE_ORDER
#define __BIG_ENDIAN BIG_ENDIAN
#define __LITTLE_ENDIAN LITTLE_ENDIAN
#endif
#ifndef CHECK
#define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
#endif // CHECK
#ifndef CHECK_RET
#define CHECK_RET(...) \
try { \
CHECK(__VA_ARGS__); \
} catch (toolkit::AssertFailedException & ex) { \
WarnL << ex.what(); \
return; \
}
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif // MAX
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif // MIN
#ifndef CLEAR_ARR
#define CLEAR_ARR(arr) \
for (auto &item : arr) { \
item = 0; \
}
#endif // CLEAR_ARR
#define RTSP_SCHEMA "rtsp"
#define RTMP_SCHEMA "rtmp"
#define TS_SCHEMA "ts"
#define FMP4_SCHEMA "fmp4"
#define HLS_SCHEMA "hls"
#define HLS_FMP4_SCHEMA "hls.fmp4"
#define VHOST_KEY "vhost"
#define DEFAULT_VHOST "__defaultVhost__"
namespace mediakit {
extern const char kServerName[];
template <typename... ARGS>
void Assert_ThrowCpp(int failed, const char *exp, const char *func, const char *file, int line, ARGS &&...args) {
if (failed) {
std::stringstream ss;
toolkit::LoggerWrapper::appendLog(ss, std::forward<ARGS>(args)...);
Assert_Throw(failed, exp, func, file, line, ss.str().data());
}
}
} // namespace mediakit
#endif // ZLMEDIAKIT_MACROS_H

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <string.h>
#include "strCoding.h"
#if defined(_WIN32)
#include <windows.h>
#endif//defined(_WIN32)
using namespace std;
namespace mediakit {
// ////////////////////////通用/////////////////////// [AUTO-TRANSLATED:758fb788]
// ////////////////////////General///////////////////////
void UTF8ToUnicode(wchar_t *pOut, const char *pText) {
char *uchar = (char *) pOut;
uchar[1] = ((pText[0] & 0x0F) << 4) + ((pText[1] >> 2) & 0x0F);
uchar[0] = ((pText[1] & 0x03) << 6) + (pText[2] & 0x3F);
return;
}
void UnicodeToUTF8(char *pOut, const wchar_t *pText) {
// 注意 WCHAR高低字的顺序,低字节在前,高字节在后 [AUTO-TRANSLATED:95408ed0]
// Note the order of the high and low bytes of WCHAR, the low byte is in front, and the high byte is behind
const char *pchar = (const char *) pText;
pOut[0] = (0xE0 | ((pchar[1] & 0xF0) >> 4));
pOut[1] = (0x80 | ((pchar[1] & 0x0F) << 2)) + ((pchar[0] & 0xC0) >> 6);
pOut[2] = (0x80 | (pchar[0] & 0x3F));
return;
}
char HexCharToBin(char ch) {
if (ch >= '0' && ch <= '9') return (char)(ch - '0');
if (ch >= 'a' && ch <= 'f') return (char)(ch - 'a' + 10);
if (ch >= 'A' && ch <= 'F') return (char)(ch - 'A' + 10);
return -1;
}
char HexStrToBin(const char *str) {
auto high = HexCharToBin(str[0]);
auto low = HexCharToBin(str[1]);
if (high == -1 || low == -1) {
// 无法把16进制字符串转换为二进制 [AUTO-TRANSLATED:2c828a6f]
// Cannot convert hexadecimal string to binary
return -1;
}
return (high << 4) | low;
}
static string UrlEncodeCommon(const string &str,const char* dont_escape){
string out;
size_t len = str.size();
for (size_t i = 0; i < len; ++i) {
char ch = str[i];
if (isalnum((uint8_t) ch) || strchr(dont_escape, (uint8_t) ch) != NULL) {
out.push_back(ch);
} else {
char buf[4];
snprintf(buf, 4, "%%%X%X", (uint8_t) ch >> 4, (uint8_t) ch & 0x0F);
out.append(buf);
}
}
return out;
}
static string UrlDecodeCommon(const string &str,const char* dont_unescape){
string output;
size_t i = 0, len = str.length();
while (i < len) {
if (str[i] == '%') {
if (i + 3 > len) {
// %后面必须还有两个字节才会反转义 [AUTO-TRANSLATED:c7c4299a]
// There must be two bytes after % to escape
output.append(str, i, len - i);
break;
}
char ch = HexStrToBin(&(str[i + 1]));
if (ch == -1 || strchr(dont_unescape, (unsigned char)ch) != NULL) {
// %后面两个字节不是16进制字符串转义失败或者转义出来可能会造成url包含非path部分比如#?说明提交的是非法拼接的url直接拼接3个原始字符 [AUTO-TRANSLATED:7c734054]
// The two bytes after % are not hexadecimal strings, the escape fails; or the escaped result may cause the url to contain non-path parts, such as #?, indicating that the submitted url is illegally spliced; directly splice the three original characters
output.append(str, i, 3);
} else {
output += ch;
}
i += 3;
} else {
output += str[i];
++i;
}
}
return output;
}
string strCoding::UrlEncodePath(const string &str) {
const char *dont_escape = "!#&'*+:=?@/._-$,;~()";
return UrlEncodeCommon(str,dont_escape);
}
string strCoding::UrlEncodeComponent(const string &str) {
const char *dont_escape = "!'()*-._~";
return UrlEncodeCommon(str,dont_escape);
}
std::string strCoding::UrlEncodeUserOrPass(const std::string &str) {
// from rfc https://datatracker.ietf.org/doc/html/rfc3986
// §2.3 Unreserved characters (mark) [AUTO-TRANSLATED:d9a6a1d3]
// §2.3 Unreserved characters (mark)
//'-', '_', '.', '~'
// §2.2 Reserved characters (reserved) [AUTO-TRANSLATED:4da0c164]
// §2.2 Reserved characters (reserved)
// '$', '&', '+', ',', '/', ':', ';', '=', '?', '@',
// §3.2.1 [AUTO-TRANSLATED:f282bdcd]
// §3.2.1
// The RFC allows ';', ':', '&', '=', '+', '$', and ',' in
// userinfo, so we must escape only '@', '/', and '?'.
// The parsing of userinfo treats ':' as special so we must escape
// that too.
const char *dont_escape = "$&+,;=-._~";
return UrlEncodeCommon(str,dont_escape);
}
string strCoding::UrlDecodePath(const string &str) {
const char *dont_unescape = "#$&+,/:;=?@";
return UrlDecodeCommon(str,dont_unescape);
}
std::string strCoding::UrlDecodeComponent(const std::string &str) {
string output;
size_t i = 0, len = str.length();
while (i < len) {
if (str[i] == '%') {
if (i + 3 > len) {
// %后面必须还有两个字节才会反转义 [AUTO-TRANSLATED:c7c4299a]
// There must be two bytes after % to escape
output.append(str, i, len - i);
break;
}
char ch = HexStrToBin(&(str[i + 1]));
if (ch == -1) {
// %后面两个字节不是16进制字符串转义失败直接拼接3个原始字符 [AUTO-TRANSLATED:10e614a4]
// The two bytes after % are not hexadecimal strings, the escape fails; directly splice the three original characters
output.append(str, i, 3);
} else {
output += ch;
}
i += 3;
} else if (str[i] == '+') {
output += ' ';
++i;
} else {
output += str[i];
++i;
}
}
return output;
}
std::string strCoding::UrlDecodeUserOrPass(const std::string &str) {
const char *dont_unescape = "";
return UrlDecodeCommon(str,dont_unescape);
}
// /////////////////////////////windows专用/////////////////////////////////// [AUTO-TRANSLATED:e6109cf5]
// /////////////////////////////Windows Specific///////////////////////////////////
#if defined(_WIN32)
void UnicodeToGB2312(char* pOut, wchar_t uData)
{
WideCharToMultiByte(CP_ACP, NULL, &uData, 1, pOut, sizeof(wchar_t), NULL, NULL);
}
void Gb2312ToUnicode(wchar_t* pOut, const char *gbBuffer)
{
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gbBuffer, 2, pOut, 1);
}
string strCoding::UTF8ToGB2312(const string &str) {
auto len = str.size();
auto pText = str.data();
char Ctemp[4] = {0};
char *pOut = new char[len + 1];
memset(pOut, 0, len + 1);
int i = 0, j = 0;
while (i < len)
{
if (pText[i] >= 0)
{
pOut[j++] = pText[i++];
}
else
{
wchar_t Wtemp;
UTF8ToUnicode(&Wtemp, pText + i);
UnicodeToGB2312(Ctemp, Wtemp);
pOut[j] = Ctemp[0];
pOut[j + 1] = Ctemp[1];
i += 3;
j += 2;
}
}
string ret = pOut;
delete[] pOut;
return ret;
}
string strCoding::GB2312ToUTF8(const string &str) {
auto len = str.size();
auto pText = str.data();
char buf[4] = { 0 };
auto nLength = len * 3;
char* pOut = new char[nLength];
memset(pOut, 0, nLength);
size_t i = 0, j = 0;
while (i < len)
{
// 如果是英文直接复制就可以 [AUTO-TRANSLATED:d6abdf68]
// If it is English, you can copy it directly
if (*(pText + i) >= 0)
{
pOut[j++] = pText[i++];
}
else
{
wchar_t pbuffer;
Gb2312ToUnicode(&pbuffer, pText + i);
UnicodeToUTF8(buf, &pbuffer);
pOut[j] = buf[0];
pOut[j + 1] = buf[1];
pOut[j + 2] = buf[2];
j += 3;
i += 2;
}
}
string ret = pOut;
delete[] pOut;
return ret;
}
#endif//defined(_WIN32)
} /* namespace mediakit */

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_HTTP_STRCODING_H_
#define SRC_HTTP_STRCODING_H_
#include <iostream>
#include <string>
#include <cstdint>
namespace mediakit {
class strCoding {
public:
static std::string UrlEncodePath(const std::string &str); //url路径 utf8编码
static std::string UrlEncodeComponent(const std::string &str); // url参数 utf8编码
static std::string UrlDecodePath(const std::string &str); //url路径 utf8解码
static std::string UrlDecodeComponent(const std::string &str); // url参数 utf8解码
static std::string UrlEncodeUserOrPass(const std::string &str); // url中用户名与密码编码
static std::string UrlDecodeUserOrPass(const std::string &str); // url中用户名与密码解码
#if defined(_WIN32)
static std::string UTF8ToGB2312(const std::string &str);//utf_8转为gb2312
static std::string GB2312ToUTF8(const std::string &str); //gb2312 转utf_8
#endif//defined(_WIN32)
private:
strCoding(void);
virtual ~strCoding(void);
};
} /* namespace mediakit */
#endif /* SRC_HTTP_STRCODING_H_ */

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "CommonRtmp.h"
namespace mediakit {
void CommonRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp) {
auto frame = FrameImp::create();
frame->_codec_id = getTrack()->getCodecId();
frame->_buffer.assign(rtmp->buffer.data() + 1, rtmp->buffer.size() - 1);
frame->_dts = rtmp->time_stamp;
RtmpCodec::inputFrame(frame);
}
/////////////////////////////////////////////////////////////////////////////////////
bool CommonRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
if (!_audio_flv_flags) {
_audio_flv_flags = getAudioRtmpFlags(getTrack());
}
auto rtmp = RtmpPacket::create();
// header
rtmp->buffer.push_back(_audio_flv_flags);
// data
rtmp->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
rtmp->body_size = rtmp->buffer.size();
rtmp->chunk_id = CHUNK_AUDIO;
rtmp->stream_index = STREAM_MEDIA;
rtmp->time_stamp = frame->dts();
rtmp->type_id = MSG_AUDIO;
RtmpCodec::inputRtmp(rtmp);
return true;
}
}//namespace mediakit

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_COMMONRTMP_H
#define ZLMEDIAKIT_COMMONRTMP_H
#include "Frame.h"
#include "Rtmp/RtmpCodec.h"
namespace mediakit{
/**
* rtmp解码类
* Generic rtmp decoder class
* [AUTO-TRANSLATED:b04614f4]
*/
class CommonRtmpDecoder : public RtmpCodec {
public:
using Ptr = std::shared_ptr<CommonRtmpDecoder>;
/**
*
* Constructor
* [AUTO-TRANSLATED:41469869]
*/
CommonRtmpDecoder(const Track::Ptr &track) : RtmpCodec(track) {}
/**
* Rtmp并解码
* @param rtmp Rtmp数据包
* Input Rtmp and decode
* @param rtmp Rtmp data packet
* [AUTO-TRANSLATED:43b1eae8]
*/
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
};
/**
* rtmp编码类
* Generic rtmp encoder class
* [AUTO-TRANSLATED:4616a2a8]
*/
class CommonRtmpEncoder : public RtmpCodec {
public:
using Ptr = std::shared_ptr<CommonRtmpEncoder>;
CommonRtmpEncoder(const Track::Ptr &track) : RtmpCodec(track) {}
/**
*
* Input frame data
* [AUTO-TRANSLATED:d13bc7f2]
*/
bool inputFrame(const Frame::Ptr &frame) override;
private:
uint8_t _audio_flv_flags { 0 };
};
}//namespace mediakit
#endif //ZLMEDIAKIT_COMMONRTMP_H

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "CommonRtp.h"
using namespace mediakit;
CommonRtpDecoder::CommonRtpDecoder(CodecId codec, size_t max_frame_size ){
_codec = codec;
_max_frame_size = max_frame_size;
obtainFrame();
}
void CommonRtpDecoder::obtainFrame() {
_frame = FrameImp::create();
_frame->_codec_id = _codec;
}
bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
auto payload_size = rtp->getPayloadSize();
if (payload_size <= 0) {
// 无实际负载 [AUTO-TRANSLATED:305af48f]
// No actual load
return false;
}
auto payload = rtp->getPayload();
auto stamp = rtp->getStamp();
auto seq = rtp->getSeq();
if (_last_stamp != stamp || _frame->_buffer.size() > _max_frame_size) {
// 时间戳发生变化或者缓存超过MAX_FRAME_SIZE则清空上帧数据 [AUTO-TRANSLATED:96f15576]
// If the timestamp changes or the cache exceeds MAX_FRAME_SIZE, clear the previous frame data
if (!_frame->_buffer.empty()) {
// 有有效帧,则输出 [AUTO-TRANSLATED:f3ff1bda]
// If there is a valid frame, output it
RtpCodec::inputFrame(_frame);
}
// 新的一帧数据 [AUTO-TRANSLATED:5b5f3a35]
// New frame data
obtainFrame();
_frame->_dts = rtp->getStampMS();
_last_stamp = stamp;
_drop_flag = false;
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) {
// 时间戳未发生变化但是seq却不连续说明中间rtp丢包了那么整帧应该废弃 [AUTO-TRANSLATED:577bf835]
// If the timestamp does not change, but the seq is not continuous, it means that the RTP packet has been lost in the middle, so the entire frame should be discarded
WarnL << "rtp丢包:" << _last_seq << " -> " << seq;
_drop_flag = true;
_frame->_buffer.clear();
}
if (!_drop_flag) {
_frame->_buffer.append((char *)payload, payload_size);
}
_last_seq = seq;
if (_drop_flag && rtp->getHeader()->mark) {
_drop_flag = false;
}
return false;
}
////////////////////////////////////////////////////////////////
bool CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){
auto stamp = frame->pts();
auto ptr = frame->data() + frame->prefixSize();
auto len = frame->size() - frame->prefixSize();
auto remain_size = len;
auto max_size = getRtpInfo().getMaxSize();
bool is_key = frame->keyFrame();
bool mark = false;
while (remain_size > 0) {
size_t rtp_size;
if (remain_size > max_size) {
rtp_size = max_size;
} else {
rtp_size = remain_size;
mark = true;
}
RtpCodec::inputRtp(getRtpInfo().makeRtp(frame->getTrackType(), ptr, rtp_size, mark, stamp), is_key);
ptr += rtp_size;
remain_size -= rtp_size;
is_key = false;
}
return len > 0;
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_COMMONRTP_H
#define ZLMEDIAKIT_COMMONRTP_H
#include "Frame.h"
#include "Rtsp/RtpCodec.h"
namespace mediakit{
/**
* rtp解码类
* Generic rtp decoder class
* [AUTO-TRANSLATED:41b57089]
*/
class CommonRtpDecoder : public RtpCodec {
public:
using Ptr = std::shared_ptr <CommonRtpDecoder>;
/**
*
* @param codec id
* @param max_frame_size
* Constructor
* @param codec codec id
* @param max_frame_size maximum allowed frame size
* [AUTO-TRANSLATED:c6b0414f]
*/
CommonRtpDecoder(CodecId codec, size_t max_frame_size = 2 * 1024);
/**
* rtp并解码
* @param rtp rtp数据包
* @param key_pos false,
* Input rtp and decode
* @param rtp rtp data packet
* @param key_pos This parameter is internally forced to false, please ignore it
* [AUTO-TRANSLATED:2993fcbe]
*/
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = false) override;
private:
void obtainFrame();
private:
bool _drop_flag = false;
uint16_t _last_seq = 0;
uint64_t _last_stamp = 0;
size_t _max_frame_size;
CodecId _codec;
FrameImp::Ptr _frame;
};
/**
* rtp编码类
* Generic rtp encoder class
* [AUTO-TRANSLATED:bb3991a5]
*/
class CommonRtpEncoder : public RtpCodec {
public:
using Ptr = std::shared_ptr <CommonRtpEncoder>;
/**
* rtp
* Input frame data and encode into rtp
* [AUTO-TRANSLATED:02bc9009]
*/
bool inputFrame(const Frame::Ptr &frame) override;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_COMMONRTP_H

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Factory.h"
#include "Rtmp/Rtmp.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
static std::unordered_map<int, const CodecPlugin *> s_plugins;
extern CodecPlugin h264_plugin;
extern CodecPlugin h265_plugin;
extern CodecPlugin jpeg_plugin;
extern CodecPlugin aac_plugin;
extern CodecPlugin opus_plugin;
extern CodecPlugin g711a_plugin;
extern CodecPlugin g711u_plugin;
extern CodecPlugin l16_plugin;
REGISTER_CODEC(h264_plugin);
REGISTER_CODEC(h265_plugin);
REGISTER_CODEC(jpeg_plugin);
REGISTER_CODEC(aac_plugin);
REGISTER_CODEC(opus_plugin);
REGISTER_CODEC(g711a_plugin)
REGISTER_CODEC(g711u_plugin);
REGISTER_CODEC(l16_plugin);
void Factory::registerPlugin(const CodecPlugin &plugin) {
InfoL << "Load codec: " << getCodecName(plugin.getCodec());
s_plugins[(int)(plugin.getCodec())] = &plugin;
}
Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
auto codec = getCodecId(track->_codec);
if (codec == CodecInvalid) {
// 根据传统的payload type 获取编码类型以及采样率等信息 [AUTO-TRANSLATED:d01ca068]
// Get the encoding type, sampling rate, and other information based on the traditional payload type
codec = RtpPayload::getCodecId(track->_pt);
}
auto it = s_plugins.find(codec);
if (it == s_plugins.end()) {
WarnL << "Unsupported codec: " << track->getName();
return nullptr;
}
return it->second->getTrackBySdp(track);
}
Track::Ptr Factory::getTrackByAbstractTrack(const Track::Ptr &track) {
auto codec = track->getCodecId();
if (track->getTrackType() == TrackVideo) {
return getTrackByCodecId(codec);
}
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
return getTrackByCodecId(codec, audio_track->getAudioSampleRate(), audio_track->getAudioChannel(), audio_track->getAudioSampleBit());
}
RtpCodec::Ptr Factory::getRtpEncoderByCodecId(CodecId codec, uint8_t pt) {
auto it = s_plugins.find(codec);
if (it == s_plugins.end()) {
WarnL << "Unsupported codec: " << getCodecName(codec);
return nullptr;
}
return it->second->getRtpEncoderByCodecId(pt);
}
RtpCodec::Ptr Factory::getRtpDecoderByCodecId(CodecId codec) {
auto it = s_plugins.find(codec);
if (it == s_plugins.end()) {
WarnL << "Unsupported codec: " << getCodecName(codec);
return nullptr;
}
return it->second->getRtpDecoderByCodecId();
}
// ///////////////////////////rtmp相关/////////////////////////////////////////// [AUTO-TRANSLATED:da9645df]
// ///////////////////////////rtmp related///////////////////////////////////////////
static CodecId getVideoCodecIdByAmf(const AMFValue &val){
if (val.type() == AMF_STRING) {
auto str = val.as_string();
if (str == "avc1") {
return CodecH264;
}
if (str == "hev1" || str == "hvc1") {
return CodecH265;
}
WarnL << "Unsupported codec: " << str;
return CodecInvalid;
}
if (val.type() != AMF_NULL) {
auto type_id = (RtmpVideoCodec)val.as_integer();
switch (type_id) {
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 << "Unsupported codec: " << (int)type_id; return CodecInvalid;
}
}
return CodecInvalid;
}
Track::Ptr Factory::getTrackByCodecId(CodecId codec, int sample_rate, int channels, int sample_bit) {
auto it = s_plugins.find(codec);
if (it == s_plugins.end()) {
WarnL << "Unsupported codec: " << getCodecName(codec);
return nullptr;
}
return it->second->getTrackByCodecId(sample_rate, channels, sample_bit);
}
Track::Ptr Factory::getVideoTrackByAmf(const AMFValue &amf) {
CodecId codecId = getVideoCodecIdByAmf(amf);
if(codecId == CodecInvalid){
return nullptr;
}
return getTrackByCodecId(codecId);
}
static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
if (val.type() == AMF_STRING) {
auto str = val.as_string();
if (str == "mp4a") {
return CodecAAC;
}
WarnL << "Unsupported codec: " << str;
return CodecInvalid;
}
if (val.type() != AMF_NULL) {
auto type_id = (RtmpAudioCodec)val.as_integer();
switch (type_id) {
case RtmpAudioCodec::aac : return CodecAAC;
case RtmpAudioCodec::g711a : return CodecG711A;
case RtmpAudioCodec::g711u : return CodecG711U;
case RtmpAudioCodec::opus : return CodecOpus;
default : WarnL << "Unsupported codec: " << (int)type_id; return CodecInvalid;
}
}
return CodecInvalid;
}
Track::Ptr Factory::getAudioTrackByAmf(const AMFValue& amf, int sample_rate, int channels, int sample_bit){
CodecId codecId = getAudioCodecIdByAmf(amf);
if (codecId == CodecInvalid) {
return nullptr;
}
return getTrackByCodecId(codecId, sample_rate, channels, sample_bit);
}
RtmpCodec::Ptr Factory::getRtmpDecoderByTrack(const Track::Ptr &track) {
auto it = s_plugins.find(track->getCodecId());
if (it == s_plugins.end()) {
WarnL << "Unsupported codec: " << track->getCodecName();
return nullptr;
}
return it->second->getRtmpDecoderByTrack(track);
}
RtmpCodec::Ptr Factory::getRtmpEncoderByTrack(const Track::Ptr &track) {
auto it = s_plugins.find(track->getCodecId());
if (it == s_plugins.end()) {
WarnL << "Unsupported codec: " << track->getCodecName();
return nullptr;
}
return it->second->getRtmpEncoderByTrack(track);
}
AMFValue Factory::getAmfByCodecId(CodecId codecId) {
GET_CONFIG(bool, enhanced, Rtmp::kEnhanced);
switch (codecId) {
case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac);
case CodecH264: return AMFValue((int)RtmpVideoCodec::h264);
case CodecH265: return enhanced ? AMFValue((int)RtmpVideoCodec::fourcc_hevc) : 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);
case CodecAV1: return AMFValue((int)RtmpVideoCodec::fourcc_av1);
case CodecVP9: return AMFValue((int)RtmpVideoCodec::fourcc_vp9);
default: return AMFValue(AMF_NULL);
}
}
Frame::Ptr Factory::getFrameFromPtr(CodecId codec, const char *data, size_t bytes, uint64_t dts, uint64_t pts) {
auto it = s_plugins.find(codec);
if (it == s_plugins.end()) {
// 创建不支持codec的frame [AUTO-TRANSLATED:00936c6c]
// Create a frame that does not support the codec
return std::make_shared<FrameFromPtr>(codec, (char *)data, bytes, dts, pts);
}
return it->second->getFrameFromPtr(data, bytes, dts, pts);
}
Frame::Ptr Factory::getFrameFromBuffer(CodecId codec, Buffer::Ptr data, uint64_t dts, uint64_t pts) {
auto frame = Factory::getFrameFromPtr(codec, data->data(), data->size(), dts, pts);
if(!frame){
return nullptr;
}
return std::make_shared<FrameCacheAble>(frame, false, std::move(data));
}
}//namespace mediakit

View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_FACTORY_H
#define ZLMEDIAKIT_FACTORY_H
#include <string>
#include "Rtmp/amf.h"
#include "Extension/Track.h"
#include "Extension/Frame.h"
#include "Rtsp/RtpCodec.h"
#include "Rtmp/RtmpCodec.h"
#include "Util/onceToken.h"
#define REGISTER_STATIC_VAR_INNER(var_name, line) var_name##_##line##__
#define REGISTER_STATIC_VAR(var_name, line) REGISTER_STATIC_VAR_INNER(var_name, line)
#define REGISTER_CODEC(plugin) \
static toolkit::onceToken REGISTER_STATIC_VAR(s_token, __LINE__) ([]() { \
Factory::registerPlugin(plugin); \
});
namespace mediakit {
struct CodecPlugin {
CodecId (*getCodec)();
Track::Ptr (*getTrackByCodecId)(int sample_rate, int channels, int sample_bit);
Track::Ptr (*getTrackBySdp)(const SdpTrack::Ptr &track);
RtpCodec::Ptr (*getRtpEncoderByCodecId)(uint8_t pt);
RtpCodec::Ptr (*getRtpDecoderByCodecId)();
RtmpCodec::Ptr (*getRtmpEncoderByTrack)(const Track::Ptr &track);
RtmpCodec::Ptr (*getRtmpDecoderByTrack)(const Track::Ptr &track);
Frame::Ptr (*getFrameFromPtr)(const char *data, size_t bytes, uint64_t dts, uint64_t pts);
};
class Factory {
public:
/**
* 线
* Register plugin, not thread-safe
* [AUTO-TRANSLATED:43e22d01]
*/
static void registerPlugin(const CodecPlugin &plugin);
/**
* codec_id track
* @param codecId id
* @param sample_rate 90000
* @param channels
* @param sample_bit
* Get track by codec_id
* @param codecId codec id
* @param sample_rate sample rate, video is fixed to 90000
* @param channels number of audio channels
* @param sample_bit audio sample bit
* [AUTO-TRANSLATED:397b982e]
*/
static Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0);
// //////////////////////////////rtsp相关////////////////////////////////// [AUTO-TRANSLATED:884055ec]
// //////////////////////////////rtsp相关//////////////////////////////////
/**
* sdp生成Track对象
* Generate Track object based on sdp
* [AUTO-TRANSLATED:79a99990]
*/
static Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track);
/**
* c api Track生成具体Track对象
* Generate specific Track object based on Track abstracted from c api
* [AUTO-TRANSLATED:991e7721]
*/
static Track::Ptr getTrackByAbstractTrack(const Track::Ptr& track);
/**
* codec id生成rtp编码器
* @param codec_id id
* @param pt rtp payload type
* Generate rtp encoder based on codec id
* @param codec_id codec id
* @param pt rtp payload type
* [AUTO-TRANSLATED:3895b39c]
*/
static RtpCodec::Ptr getRtpEncoderByCodecId(CodecId codec_id, uint8_t pt);
/**
* Track生成Rtp解包器
* Generate Rtp unpacker based on Track
* [AUTO-TRANSLATED:50dbf826]
*/
static RtpCodec::Ptr getRtpDecoderByCodecId(CodecId codec);
// //////////////////////////////rtmp相关////////////////////////////////// [AUTO-TRANSLATED:df02d6fb]
// //////////////////////////////rtmp相关//////////////////////////////////
/**
* amf对象获取视频相应的Track
* @param amf rtmp metadata中的videocodecid的值
* Get the corresponding video Track based on the amf object
* @param amf the value of videocodecid in rtmp metadata
* [AUTO-TRANSLATED:c0c632c1]
*/
static Track::Ptr getVideoTrackByAmf(const AMFValue &amf);
/**
* amf对象获取音频相应的Track
* @param amf rtmp metadata中的audiocodecid的值
* Get the corresponding audio Track based on the amf object
* @param amf the value of audiocodecid in rtmp metadata
* [AUTO-TRANSLATED:fc34f9e4]
*/
static Track::Ptr getAudioTrackByAmf(const AMFValue& amf, int sample_rate, int channels, int sample_bit);
/**
* Track获取Rtmp的编码器
* @param track
* Get the Rtmp encoder based on Track
* @param track media description object
* [AUTO-TRANSLATED:81fc38af]
*/
static RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track);
/**
* Track获取Rtmp的解码器
* @param track
* Get the Rtmp decoder based on Track
* @param track media description object
* [AUTO-TRANSLATED:0744b09e]
*/
static RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track);
/**
* codecId获取rtmp的codec描述
* Get the rtmp codec description based on codecId
* [AUTO-TRANSLATED:67c749b7]
*/
static AMFValue getAmfByCodecId(CodecId codecId);
static Frame::Ptr getFrameFromPtr(CodecId codec, const char *data, size_t size, uint64_t dts, uint64_t pts);
static Frame::Ptr getFrameFromBuffer(CodecId codec, toolkit::Buffer::Ptr data, uint64_t dts, uint64_t pts);
};
}//namespace mediakit
#endif //ZLMEDIAKIT_FACTORY_H

View File

@ -0,0 +1,349 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Frame.h"
#include "Common/Parser.h"
#include "Common/Stamp.h"
#include "Common/MediaSource.h"
#if defined(ENABLE_MP4)
#include "mov-format.h"
#endif
#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY)
#include "mpeg-proto.h"
#endif
using namespace std;
using namespace toolkit;
namespace toolkit {
StatisticImp(mediakit::Frame);
StatisticImp(mediakit::FrameImp);
}
namespace mediakit{
Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
if(frame->cacheAble()){
return frame;
}
return std::make_shared<FrameCacheAble>(frame);
}
FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp)
{
setIndex(frame->getIndex());
_frame = std::move(frame);
// kModifyStampSystem时采用系统时间戳kModifyStampRelative采用相对时间戳 [AUTO-TRANSLATED:54dd5685]
// When using kModifyStampSystem, the system timestamp is used, and when using kModifyStampRelative, the relative timestamp is used.
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem);
}
TrackType getTrackType(CodecId codecId) {
switch (codecId) {
#define XX(name, type, value, str, mpeg_id, mp4_id) case name : return type;
CODEC_MAP(XX)
#undef XX
default : return TrackInvalid;
}
}
#if defined(ENABLE_MP4)
int getMovIdByCodec(CodecId codecId) {
switch (codecId) {
#define XX(name, type, value, str, mpeg_id, mp4_id) case name : return mp4_id;
CODEC_MAP(XX)
#undef XX
default : return MOV_OBJECT_NONE;
}
}
CodecId getCodecByMovId(int object_id) {
if (object_id == MOV_OBJECT_NONE) {
return CodecInvalid;
}
#define XX(name, type, value, str, mpeg_id, mp4_id) { mp4_id, name },
static map<int, CodecId> s_map = { CODEC_MAP(XX) };
#undef XX
auto it = s_map.find(object_id);
if (it == s_map.end()) {
WarnL << "Unsupported mov: " << object_id;
return CodecInvalid;
}
return it->second;
}
#endif
#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY)
int getMpegIdByCodec(CodecId codec) {
switch (codec) {
#define XX(name, type, value, str, mpeg_id, mp4_id) case name : return mpeg_id;
CODEC_MAP(XX)
#undef XX
default : return PSI_STREAM_RESERVED;
}
}
CodecId getCodecByMpegId(int mpeg_id) {
if (mpeg_id == PSI_STREAM_RESERVED || mpeg_id == 0xBD) {
// 海康的 PS 流中会有0xBD 的包 [AUTO-TRANSLATED:32a250cb]
// Hikvision's PS stream will have 0xBD packets.
return CodecInvalid;
}
#define XX(name, type, value, str, mpeg_id, mp4_id) { mpeg_id, name },
static map<int, CodecId> s_map = { CODEC_MAP(XX) };
#undef XX
auto it = s_map.find(mpeg_id);
if (it == s_map.end()) {
WarnL << "Unsupported mpeg: " << mpeg_id;
return CodecInvalid;
}
return it->second;
}
#endif
const char *getCodecName(CodecId codec) {
switch (codec) {
#define XX(name, type, value, str, mpeg_id, mp4_id) case name : return str;
CODEC_MAP(XX)
#undef XX
default : return "invalid";
}
}
#define XX(name, type, value, str, mpeg_id, mp4_id) {str, name},
static map<string, CodecId, StrCaseCompare> codec_map = { CODEC_MAP(XX) };
#undef XX
CodecId getCodecId(const string &str){
auto it = codec_map.find(str);
return it == codec_map.end() ? CodecInvalid : it->second;
}
static map<string, TrackType, StrCaseCompare> track_str_map = {
{"video", TrackVideo},
{"audio", TrackAudio},
{"application", TrackApplication}
};
TrackType getTrackType(const string &str) {
auto it = track_str_map.find(str);
return it == track_str_map.end() ? TrackInvalid : it->second;
}
const char* getTrackString(TrackType type){
switch (type) {
case TrackVideo : return "video";
case TrackAudio : return "audio";
case TrackApplication : return "application";
default: return "invalid";
}
}
const char *CodecInfo::getCodecName() const {
return mediakit::getCodecName(getCodecId());
}
TrackType CodecInfo::getTrackType() const {
return mediakit::getTrackType(getCodecId());
}
std::string CodecInfo::getTrackTypeStr() const {
return getTrackString(getTrackType());
}
static size_t constexpr kMaxFrameCacheSize = 100;
bool FrameMerger::willFlush(const Frame::Ptr &frame) const{
if (_frame_cache.empty()) {
// 缓存为空 [AUTO-TRANSLATED:b9505a19]
// Cache is empty.
return false;
}
if (!frame) {
return true;
}
switch (_type) {
case none : {
// frame不是完整的帧我们合并为一帧 [AUTO-TRANSLATED:00e9f200]
// The frame is not a complete frame, we merge it into one frame.
bool new_frame = false;
switch (frame->getCodecId()) {
case CodecH264:
case CodecH265: {
// 如果是新的一帧,前面的缓存需要输出 [AUTO-TRANSLATED:b4deff81]
// If it is a new frame, the previous cache needs to be output.
new_frame = frame->prefixSize();
break;
}
default: break;
}
// 遇到新帧、或时间戳变化或缓存太多防止内存溢出则flush输出 [AUTO-TRANSLATED:0292964a]
// When encountering a new frame, or a timestamp change, or too much cache, flush the output to prevent memory overflow.
return new_frame || _frame_cache.back()->dts() != frame->dts() || _frame_cache.size() > kMaxFrameCacheSize;
}
case mp4_nal_size:
case h264_prefix: {
if (!_have_decode_able_frame) {
// 缓存中没有有效的能解码的帧所以这次不flush [AUTO-TRANSLATED:5d860722]
// There are no valid frames that can be decoded in the cache, so no flush this time.
return _frame_cache.size() > kMaxFrameCacheSize;
}
if (_frame_cache.back()->dts() != frame->dts() || frame->decodeAble() || frame->configFrame()) {
// 时间戳变化了,或新的一帧或遇到config帧立即flush [AUTO-TRANSLATED:8c2523b1]
// When the timestamp changes, or a new frame, or a config frame is encountered, flush immediately.
return true;
}
return _frame_cache.size() > kMaxFrameCacheSize;
}
default: /*不可达*/ assert(0); return true;
}
}
void FrameMerger::doMerge(BufferLikeString &merged, const Frame::Ptr &frame) const{
switch (_type) {
case none : {
// 此处是合并ps解析输出的流解析出的流可能是半帧或多帧不能简单的根据nal type过滤 [AUTO-TRANSLATED:4a231bdc]
// Here, the PS parsing output stream is merged. The parsed stream may be half a frame or multiple frames, and cannot be simply filtered according to the nal type.
// 此流程只用于合并ps解析输出为H264/H265后面流程有split和忽略无效帧操作 [AUTO-TRANSLATED:2d40274e]
// This process is only used to merge PS parsing output into H264/H265. The subsequent process has split and ignore invalid frame operations.
merged.append(frame->data(), frame->size());
break;
}
case h264_prefix: {
if (frame->prefixSize()) {
merged.append(frame->data(), frame->size());
} else {
merged.append("\x00\x00\x00\x01", 4);
merged.append(frame->data(), frame->size());
}
break;
}
case mp4_nal_size: {
uint32_t nalu_size = (uint32_t) (frame->size() - frame->prefixSize());
nalu_size = htonl(nalu_size);
merged.append((char *) &nalu_size, 4);
merged.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
break;
}
default: /*不可达*/ assert(0); break;
}
}
static bool isNeedMerge(CodecId codec){
switch (codec) {
case CodecH264:
case CodecH265: return true;
default: return false;
}
}
bool FrameMerger::inputFrame(const Frame::Ptr &frame, onOutput cb, BufferLikeString *buffer) {
if (frame && !isNeedMerge(frame->getCodecId())) {
cb(frame->dts(), frame->pts(), frame, true);
return true;
}
if (willFlush(frame)) {
Frame::Ptr back = _frame_cache.back();
Buffer::Ptr merged_frame = back;
bool have_key_frame = back->keyFrame();
if (_frame_cache.size() != 1 || _type == mp4_nal_size || buffer) {
// 在MP4模式下一帧数据也需要在前添加nalu_size [AUTO-TRANSLATED:4a7e5c20]
// In MP4 mode, a frame of data also needs to add nalu_size in front.
BufferLikeString tmp;
BufferLikeString &merged = buffer ? *buffer : tmp;
if (!buffer) {
tmp.reserve(back->size() + 1024);
}
_frame_cache.for_each([&](const Frame::Ptr &frame) {
doMerge(merged, frame);
if (frame->keyFrame()) {
have_key_frame = true;
}
});
merged_frame = std::make_shared<BufferOffset<BufferLikeString> >(buffer ? merged : std::move(merged));
}
cb(back->dts(), back->pts(), merged_frame, have_key_frame);
_frame_cache.clear();
_have_decode_able_frame = false;
}
if (!frame) {
return false;
}
if (frame->decodeAble()) {
_have_decode_able_frame = true;
}
_cb = std::move(cb);
_frame_cache.emplace_back(Frame::getCacheAbleFrame(frame));
return true;
}
FrameMerger::FrameMerger(int type) {
_type = type;
}
void FrameMerger::clear() {
_frame_cache.clear();
_have_decode_able_frame = false;
}
void FrameMerger::flush() {
if (_cb) {
inputFrame(nullptr, std::move(_cb), nullptr);
}
clear();
}
/**
* function
* Write frame interface to function, auxiliary class
* [AUTO-TRANSLATED:ce04a5e9]
*/
class FrameWriterInterfaceHelper : public FrameWriterInterface {
public:
using Ptr = std::shared_ptr<FrameWriterInterfaceHelper>;
using onWriteFrame = std::function<bool(const Frame::Ptr &frame)>;
/**
* inputFrame后触发onWriteFrame回调
* Trigger onWriteFrame callback after inputFrame
* [AUTO-TRANSLATED:169e5944]
*/
FrameWriterInterfaceHelper(onWriteFrame cb) { _callback = std::move(cb); }
/**
*
* Write frame data
* [AUTO-TRANSLATED:d46c6fc2]
*/
bool inputFrame(const Frame::Ptr &frame) override { return _callback(frame); }
private:
onWriteFrame _callback;
};
FrameWriterInterface* FrameDispatcher::addDelegate(std::function<bool(const Frame::Ptr &frame)> cb) {
return addDelegate(std::make_shared<FrameWriterInterfaceHelper>(std::move(cb)));
}
}//namespace mediakit

View File

@ -0,0 +1,820 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_FRAME_H
#define ZLMEDIAKIT_FRAME_H
#include <map>
#include <mutex>
#include <functional>
#include "Util/List.h"
#include "Util/TimeTicker.h"
#include "Common/Stamp.h"
#include "Network/Buffer.h"
namespace mediakit {
class Stamp;
typedef enum {
TrackInvalid = -1,
TrackVideo = 0,
TrackAudio,
TrackTitle,
TrackApplication,
TrackMax
} TrackType;
#define CODEC_MAP(XX) \
XX(CodecH264, TrackVideo, 0, "H264", PSI_STREAM_H264, MOV_OBJECT_H264) \
XX(CodecH265, TrackVideo, 1, "H265", PSI_STREAM_H265, MOV_OBJECT_HEVC) \
XX(CodecAAC, TrackAudio, 2, "mpeg4-generic", PSI_STREAM_AAC, MOV_OBJECT_AAC) \
XX(CodecG711A, TrackAudio, 3, "PCMA", PSI_STREAM_AUDIO_G711A, MOV_OBJECT_G711a) \
XX(CodecG711U, TrackAudio, 4, "PCMU", PSI_STREAM_AUDIO_G711U, MOV_OBJECT_G711u) \
XX(CodecOpus, TrackAudio, 5, "opus", PSI_STREAM_AUDIO_OPUS, MOV_OBJECT_OPUS) \
XX(CodecL16, TrackAudio, 6, "L16", PSI_STREAM_RESERVED, MOV_OBJECT_NONE) \
XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8, MOV_OBJECT_VP8) \
XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9, MOV_OBJECT_VP9) \
XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1, MOV_OBJECT_AV1) \
XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_JPEG_2000, MOV_OBJECT_JPEG)
typedef enum {
CodecInvalid = -1,
#define XX(name, type, value, str, mpeg_id, mp4_id) name = value,
CODEC_MAP(XX)
#undef XX
CodecMax
} CodecId;
/**
*
* String to media type conversion
* [AUTO-TRANSLATED:59850011]
*/
TrackType getTrackType(const std::string &str);
/**
*
* Media type to string conversion
* [AUTO-TRANSLATED:0456e0e2]
*/
const char* getTrackString(TrackType type);
/**
* SDP中描述获取codec_id
* @param str
* @return
* Get codec_id from SDP description
* @param str
* @return
* [AUTO-TRANSLATED:024f2ed1]
*/
CodecId getCodecId(const std::string &str);
/**
*
* Get encoder name
* [AUTO-TRANSLATED:0253534b]
*/
const char *getCodecName(CodecId codecId);
/**
*
* Get audio/video type
* [AUTO-TRANSLATED:e2f06ac2]
*/
TrackType getTrackType(CodecId codecId);
/**
* codecid获取mov object id
* Get mov object id by codecid
* [AUTO-TRANSLATED:c315b87d]
*/
int getMovIdByCodec(CodecId codecId);
/**
* mov object id获取CodecId
* Get CodecId by mov object id
* [AUTO-TRANSLATED:de2237a1]
*/
CodecId getCodecByMovId(int object_id);
/**
* codecid获取mpeg id
* Get mpeg id by codecid
* [AUTO-TRANSLATED:d365eac7]
*/
int getMpegIdByCodec(CodecId codec);
/**
* mpeg id获取CodecId
* Get CodecId by mpeg id
* [AUTO-TRANSLATED:ca190565]
*/
CodecId getCodecByMpegId(int mpeg_id);
/**
*
* Abstract interface for encoding information
* [AUTO-TRANSLATED:c3b14625]
*/
class CodecInfo {
public:
using Ptr = std::shared_ptr<CodecInfo>;
virtual ~CodecInfo() = default;
/**
*
* Get codec type
* [AUTO-TRANSLATED:2dbf103b]
*/
virtual CodecId getCodecId() const = 0;
/**
*
* Get encoder name
* [AUTO-TRANSLATED:a92f41f6]
*/
const char *getCodecName() const;
/**
*
* Get audio/video type
* [AUTO-TRANSLATED:ff8ba95b]
*/
TrackType getTrackType() const;
/**
*
* Get audio/video type description
* [AUTO-TRANSLATED:a460e432]
*/
std::string getTrackTypeStr() const;
/**
* track index, track
* Set track index, for multi-track support
* [AUTO-TRANSLATED:da5bdb91]
*/
void setIndex(int index) { _index = index; }
/**
* track index, track
* Get track index, for multi-track support
* [AUTO-TRANSLATED:1e96c587]
*/
int getIndex() const { return _index < 0 ? (int)getTrackType() : _index; }
private:
int _index = -1;
};
/**
*
* Abstract interface for frame types
* [AUTO-TRANSLATED:eb166e7e]
*/
class Frame : public toolkit::Buffer, public CodecInfo {
public:
using Ptr = std::shared_ptr<Frame>;
/**
*
* Return decoding timestamp, in milliseconds
* [AUTO-TRANSLATED:00072dad]
*/
virtual uint64_t dts() const = 0;
/**
*
* Return display timestamp, in milliseconds
* [AUTO-TRANSLATED:c7eecb91]
*/
virtual uint64_t pts() const { return dts(); }
/**
* 2640x00 00 00 01,4
* aac前缀则为7个字节
* Prefix length, for example, the 264 prefix is 0x00 00 00 01, so the prefix length is 4
* aac prefix is 7 bytes
* [AUTO-TRANSLATED:6334f58e]
*/
virtual size_t prefixSize() const = 0;
/**
*
* Return whether it is a key frame
* [AUTO-TRANSLATED:2e52426a]
*/
virtual bool keyFrame() const = 0;
/**
* sps pps vps
* Whether it is a configuration frame, such as sps pps vps
* [AUTO-TRANSLATED:595c7ecf]
*/
virtual bool configFrame() const = 0;
/**
*
* Whether it can be cached
* [AUTO-TRANSLATED:5c35d3e0]
*/
virtual bool cacheAble() const { return true; }
/**
*
* SEI/AUD帧可以丢弃
*
* Whether this frame can be dropped
* SEI/AUD frames can be dropped
* By default, no frames can be dropped
* [AUTO-TRANSLATED:42957087]
*/
virtual bool dropAble() const { return false; }
/**
*
* sps pps等帧不能解码
* Whether it is a decodable frame
* sps pps frames cannot be decoded
* [AUTO-TRANSLATED:52f093c7]
*/
virtual bool decodeAble() const {
if (getTrackType() != TrackVideo) {
// 非视频帧都可以解码 [AUTO-TRANSLATED:24aa4342]
// Non-video frames can be decoded
return true;
}
// 默认非sps pps帧都可以解码 [AUTO-TRANSLATED:b14d1e34]
// By default, non-sps pps frames can be decoded
return !configFrame();
}
/**
* frame
* Return the cacheable frame
* [AUTO-TRANSLATED:88fb9c3e]
*/
static Ptr getCacheAbleFrame(const Ptr &frame);
private:
// 对象个数统计 [AUTO-TRANSLATED:3b43e8c2]
// Object count statistics
toolkit::ObjectStatistic<Frame> _statistic;
};
class FrameImp : public Frame {
public:
using Ptr = std::shared_ptr<FrameImp>;
template <typename C = FrameImp>
static std::shared_ptr<C> create() {
#if 0
static ResourcePool<C> packet_pool;
static onceToken token([]() {
packet_pool.setSize(1024);
});
auto ret = packet_pool.obtain2();
ret->_buffer.clear();
ret->_prefix_size = 0;
ret->_dts = 0;
ret->_pts = 0;
return ret;
#else
return std::shared_ptr<C>(new C());
#endif
}
char *data() const override { return (char *)_buffer.data(); }
size_t size() const override { return _buffer.size(); }
uint64_t dts() const override { return _dts; }
uint64_t pts() const override { return _pts ? _pts : _dts; }
size_t prefixSize() const override { return _prefix_size; }
CodecId getCodecId() const override { return _codec_id; }
bool keyFrame() const override { return false; }
bool configFrame() const override { return false; }
public:
CodecId _codec_id = CodecInvalid;
uint64_t _dts = 0;
uint64_t _pts = 0;
size_t _prefix_size = 0;
toolkit::BufferLikeString _buffer;
private:
// 对象个数统计 [AUTO-TRANSLATED:3b43e8c2]
// Object count statistics
toolkit::ObjectStatistic<FrameImp> _statistic;
protected:
friend class toolkit::ResourcePool_l<FrameImp>;
FrameImp() = default;
};
// 包装一个指针成不可缓存的frame [AUTO-TRANSLATED:c3e5d65e]
// Wrap a pointer into a non-cacheable frame
class FrameFromPtr : public Frame {
public:
using Ptr = std::shared_ptr<FrameFromPtr>;
FrameFromPtr(CodecId codec_id, char *ptr, size_t size, uint64_t dts, uint64_t pts = 0, size_t prefix_size = 0, bool is_key = false)
: FrameFromPtr(ptr, size, dts, pts, prefix_size, is_key) {
_codec_id = codec_id;
}
char *data() const override { return _ptr; }
size_t size() const override { return _size; }
uint64_t dts() const override { return _dts; }
uint64_t pts() const override { return _pts ? _pts : dts(); }
size_t prefixSize() const override { return _prefix_size; }
bool cacheAble() const override { return false; }
bool keyFrame() const override { return _is_key; }
bool configFrame() const override { return false; }
CodecId getCodecId() const override {
if (_codec_id == CodecInvalid) {
throw std::invalid_argument("Invalid codec type of FrameFromPtr");
}
return _codec_id;
}
protected:
FrameFromPtr() = default;
FrameFromPtr(char *ptr, size_t size, uint64_t dts, uint64_t pts = 0, size_t prefix_size = 0, bool is_key = false) {
_ptr = ptr;
_size = size;
_dts = dts;
_pts = pts;
_prefix_size = prefix_size;
_is_key = is_key;
}
protected:
bool _is_key;
char *_ptr;
uint64_t _dts;
uint64_t _pts = 0;
size_t _size;
size_t _prefix_size;
CodecId _codec_id = CodecInvalid;
};
/**
* Frame类中可以有多个帧(AAC)
* ZLMediaKit会先把这种复合帧split成单个帧然后再处理
* Frame
*
* A Frame class can have multiple frames (AAC), and the timestamp will change
* ZLMediaKit will first split this composite frame into single frames and then process it
* A composite frame can be split into multiple sub-Frames without memory copy
* The purpose of providing this class is to prevent memory copy when splitting composite frames, improving performance
* [AUTO-TRANSLATED:4010c0a5]
*/
template <typename Parent>
class FrameInternalBase : public Parent {
public:
using Ptr = std::shared_ptr<FrameInternalBase>;
FrameInternalBase(Frame::Ptr parent_frame, char *ptr, size_t size, uint64_t dts, uint64_t pts = 0, size_t prefix_size = 0)
: Parent(parent_frame->getCodecId(), ptr, size, dts, pts, prefix_size) {
_parent_frame = std::move(parent_frame);
this->setIndex(_parent_frame->getIndex());
}
bool cacheAble() const override { return _parent_frame->cacheAble(); }
private:
Frame::Ptr _parent_frame;
};
/**
* Frame类中可以有多个帧 0x 00 00 01
* ZLMediaKit会先把这种复合帧split成单个帧然后再处理
* Frame
*
* A Frame class can have multiple frames, they are separated by 0x 00 00 01
* ZLMediaKit will first split this composite frame into single frames and then process it
* A composite frame can be split into multiple sub-Frames without memory copy
* The purpose of providing this class is to prevent memory copy when splitting composite frames, improving performance
* [AUTO-TRANSLATED:ed49148b]
*/
template <typename Parent>
class FrameInternal : public FrameInternalBase<Parent> {
public:
using Ptr = std::shared_ptr<FrameInternal>;
FrameInternal(const Frame::Ptr &parent_frame, char *ptr, size_t size, size_t prefix_size)
: FrameInternalBase<Parent>(parent_frame, ptr, size, parent_frame->dts(), parent_frame->pts(), prefix_size) {}
};
// 管理一个指针生命周期并生产一个frame [AUTO-TRANSLATED:449d107b]
// Manage the lifetime of a pointer and produce a frame
class FrameAutoDelete : public FrameFromPtr {
public:
template <typename... ARGS>
FrameAutoDelete(ARGS &&...args) : FrameFromPtr(std::forward<ARGS>(args)...) {}
~FrameAutoDelete() override { delete[] _ptr; };
bool cacheAble() const override { return true; }
};
// 把一个不可缓存的frame声明为可缓存的 [AUTO-TRANSLATED:2c8d0659]
// Declare a non-cacheable frame as cacheable
template <typename Parent>
class FrameToCache : public Parent {
public:
template<typename ... ARGS>
FrameToCache(ARGS &&...args) : Parent(std::forward<ARGS>(args)...) {};
bool cacheAble() const override {
return true;
}
};
// 该对象的功能是把一个不可缓存的帧转换成可缓存的帧 [AUTO-TRANSLATED:5851119b]
// The function of this object is to convert a non-cacheable frame into a cacheable frame
class FrameCacheAble : public FrameFromPtr {
public:
using Ptr = std::shared_ptr<FrameCacheAble>;
FrameCacheAble(const Frame::Ptr &frame, bool force_key_frame = false, toolkit::Buffer::Ptr buf = nullptr) {
setIndex(frame->getIndex());
if (frame->cacheAble()) {
_ptr = frame->data();
_buffer = frame;
} else if (buf) {
_ptr = frame->data();
_buffer = std::move(buf);
} else {
auto buffer = std::make_shared<toolkit::BufferLikeString>();
buffer->assign(frame->data(), frame->size());
_ptr = buffer->data();
_buffer = std::move(buffer);
}
_size = frame->size();
_dts = frame->dts();
_pts = frame->pts();
_prefix_size = frame->prefixSize();
_codec_id = frame->getCodecId();
_key = force_key_frame ? true : frame->keyFrame();
_config = frame->configFrame();
_drop_able = frame->dropAble();
_decode_able = frame->decodeAble();
}
/**
*
* Can be cached
* [AUTO-TRANSLATED:7f9cec13]
*/
bool cacheAble() const override { return true; }
bool keyFrame() const override { return _key; }
bool configFrame() const override { return _config; }
bool dropAble() const override { return _drop_able; }
bool decodeAble() const override { return _decode_able; }
private:
bool _key;
bool _config;
bool _drop_able;
bool _decode_able;
toolkit::Buffer::Ptr _buffer;
};
// 该类实现frame级别的时间戳覆盖 [AUTO-TRANSLATED:77c28d0f]
// This class implements frame-level timestamp overwrite
class FrameStamp : public Frame {
public:
using Ptr = std::shared_ptr<FrameStamp>;
FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp);
~FrameStamp() override {}
uint64_t dts() const override { return (uint64_t)_dts; }
uint64_t pts() const override { return (uint64_t)_pts; }
size_t prefixSize() const override { return _frame->prefixSize(); }
bool keyFrame() const override { return _frame->keyFrame(); }
bool configFrame() const override { return _frame->configFrame(); }
bool cacheAble() const override { return _frame->cacheAble(); }
bool dropAble() const override { return _frame->dropAble(); }
bool decodeAble() const override { return _frame->decodeAble(); }
char *data() const override { return _frame->data(); }
size_t size() const override { return _frame->size(); }
CodecId getCodecId() const override { return _frame->getCodecId(); }
private:
int64_t _dts;
int64_t _pts;
Frame::Ptr _frame;
};
/**
* Buffer对象转换成可缓存的Frame对象
* This object can convert a Buffer object into a cacheable Frame object
* [AUTO-TRANSLATED:3c5786b8]
*/
template <typename Parent>
class FrameFromBuffer : public Parent {
public:
/**
* frame
* @param buf
* @param dts
* @param pts
* @param prefix
* @param offset buffer有效数据偏移量
* Construct frame
* @param buf Data cache
* @param dts Decode timestamp
* @param pts Display timestamp
* @param prefix Frame prefix length
* @param offset Buffer valid data offset
* [AUTO-TRANSLATED:6afec0f1]
*/
FrameFromBuffer(toolkit::Buffer::Ptr buf, uint64_t dts, uint64_t pts, size_t prefix = 0, size_t offset = 0)
: Parent(buf->data() + offset, buf->size() - offset, dts, pts, prefix) {
_buf = std::move(buf);
}
/**
* frame
* @param buf
* @param dts
* @param pts
* @param prefix
* @param offset buffer有效数据偏移量
* @param codec
* Construct frame
* @param buf Data cache
* @param dts Decode timestamp
* @param pts Display timestamp
* @param prefix Frame prefix length
* @param offset Buffer valid data offset
* @param codec Frame type
* [AUTO-TRANSLATED:f1c42e38]
*/
FrameFromBuffer(CodecId codec, toolkit::Buffer::Ptr buf, uint64_t dts, uint64_t pts, size_t prefix = 0, size_t offset = 0)
: Parent(codec, buf->data() + offset, buf->size() - offset, dts, pts, prefix) {
_buf = std::move(buf);
}
/**
*
* This frame is cacheable
* [AUTO-TRANSLATED:e089250f]
*/
bool cacheAble() const override { return true; }
private:
toolkit::Buffer::Ptr _buf;
};
/**
* frame
* Merge some frames with the same timestamp
* [AUTO-TRANSLATED:392a23df]
*/
class FrameMerger {
public:
using onOutput = std::function<void(uint64_t dts, uint64_t pts, const toolkit::Buffer::Ptr &buffer, bool have_key_frame)>;
using Ptr = std::shared_ptr<FrameMerger>;
enum {
none = 0,
h264_prefix,
mp4_nal_size,
};
FrameMerger(int type);
/**
* FrameMerger::inputFrame传入的onOutput回调
*
* Refresh the output buffer, note that FrameMerger::inputFrame's onOutput callback will be called at this time
* Please note whether the callback capture parameters are valid at this time
* [AUTO-TRANSLATED:18c25a14]
*/
void flush();
void clear();
bool inputFrame(const Frame::Ptr &frame, onOutput cb, toolkit::BufferLikeString *buffer = nullptr);
private:
bool willFlush(const Frame::Ptr &frame) const;
void doMerge(toolkit::BufferLikeString &buffer, const Frame::Ptr &frame) const;
private:
int _type;
bool _have_decode_able_frame = false;
onOutput _cb;
toolkit::List<Frame::Ptr> _frame_cache;
};
/**
*
* Abstract interface class for write frame interface
* [AUTO-TRANSLATED:dbe6a33c]
*/
class FrameWriterInterface {
public:
using Ptr = std::shared_ptr<FrameWriterInterface>;
virtual ~FrameWriterInterface() = default;
/**
*
* Write frame data
* [AUTO-TRANSLATED:d46c6fc2]
*/
virtual bool inputFrame(const Frame::Ptr &frame) = 0;
/**
* frame缓存
* Flush all frame caches in the output
* [AUTO-TRANSLATED:adaea568]
*/
virtual void flush() {};
};
/**
*
* Frame circular buffer that supports proxy forwarding
* [AUTO-TRANSLATED:06bf1541]
*/
class FrameDispatcher : public FrameWriterInterface {
public:
using Ptr = std::shared_ptr<FrameDispatcher>;
/**
*
* Add proxy
* [AUTO-TRANSLATED:0be3c076]
*/
FrameWriterInterface* addDelegate(FrameWriterInterface::Ptr delegate) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _delegates.emplace(delegate.get(), std::move(delegate)).first->second.get();
}
FrameWriterInterface* addDelegate(std::function<bool(const Frame::Ptr &frame)> cb);
/**
*
* Delete proxy
* [AUTO-TRANSLATED:c2c915aa]
*/
void delDelegate(FrameWriterInterface *ptr) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
_delegates.erase(ptr);
}
/**
*
* Write frame and dispatch
* [AUTO-TRANSLATED:a3e7e6db]
*/
bool inputFrame(const Frame::Ptr &frame) override {
std::lock_guard<std::recursive_mutex> lck(_mtx);
doStatistics(frame);
bool ret = false;
for (auto &pr : _delegates) {
if (pr.second->inputFrame(frame)) {
ret = true;
}
}
return ret;
}
/**
*
* Return the number of proxies
* [AUTO-TRANSLATED:93ebe7ec]
*/
size_t size() const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _delegates.size();
}
void clear() {
std::lock_guard<std::recursive_mutex> lck(_mtx);
_delegates.clear();
}
/**
*
* Get the cumulative number of keyframes
* [AUTO-TRANSLATED:73cb2ab0]
*/
uint64_t getVideoKeyFrames() const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _video_key_frames;
}
/**
*
* Get the number of frames
* [AUTO-TRANSLATED:118b395e]
*/
uint64_t getFrames() const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _frames;
}
size_t getVideoGopSize() const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _gop_size;
}
size_t getVideoGopInterval() const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _gop_interval_ms;
}
int64_t getDuration() const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _stamp.getRelativeStamp();
}
private:
void doStatistics(const Frame::Ptr &frame) {
if (!frame->configFrame() && !frame->dropAble()) {
// 忽略配置帧与可丢弃的帧 [AUTO-TRANSLATED:da4ff7ac]
// Ignore configuration frames and discardable frames
++_frames;
int64_t out;
_stamp.revise(frame->dts(), frame->pts(), out, out);
if (frame->keyFrame() && frame->getTrackType() == TrackVideo) {
// 遇视频关键帧时统计 [AUTO-TRANSLATED:72b0e569]
// Statistics when encountering video keyframes
++_video_key_frames;
_gop_size = _frames - _last_frames;
_gop_interval_ms = _ticker.elapsedTime();
_last_frames = _frames;
_ticker.resetTime();
}
}
}
private:
toolkit::Ticker _ticker;
size_t _gop_interval_ms = 0;
size_t _gop_size = 0;
uint64_t _last_frames = 0;
uint64_t _frames = 0;
uint64_t _video_key_frames = 0;
Stamp _stamp;
mutable std::recursive_mutex _mtx;
std::map<void *, FrameWriterInterface::Ptr> _delegates;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_FRAME_H

View File

@ -0,0 +1,369 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_TRACK_H
#define ZLMEDIAKIT_TRACK_H
#include <memory>
#include <string>
#include "Frame.h"
#include "Rtsp/Rtsp.h"
namespace mediakit{
/**
*
* Media channel description class, also supports frame input and output
* [AUTO-TRANSLATED:a3acd089]
*/
class Track : public FrameDispatcher, public CodecInfo {
public:
using Ptr = std::shared_ptr<Track>;
/**
*
* Default constructor
* [AUTO-TRANSLATED:acda54ab]
*/
Track() = default;
/**
*
*
* Copy, only copy information of derived classes,
* Circular buffer and proxy relationships cannot be copied, otherwise the relationship will be disordered
* [AUTO-TRANSLATED:308e6502]
*/
Track(const Track &that) {
_bit_rate = that._bit_rate;
setIndex(that.getIndex());
}
/**
* sps pps等信息
* Whether it is ready, it can be used to get information such as sps pps
* [AUTO-TRANSLATED:6d819ef7]
*/
virtual bool ready() const = 0;
/**
*
*
*
* Clone interface, used to copy this object
* When calling this interface, only the information of the derived class will be copied
* Circular buffer and proxy relationships cannot be copied, otherwise the relationship will be disordered
* [AUTO-TRANSLATED:270874c6]
*/
virtual Track::Ptr clone() const = 0;
/**
* track信息sps/pps解析
* Update track information, such as triggering sps/pps parsing
* [AUTO-TRANSLATED:324879ef]
*/
virtual bool update() { return false; }
/**
* sdp
* @return sdp对象
* Generate sdp
* @return sdp object
* [AUTO-TRANSLATED:3ab2fd30]
*/
virtual Sdp::Ptr getSdp(uint8_t payload_type) const = 0;
/**
* extra data, rtmp/mp4生成
* Get extra data, generally used for rtmp/mp4 generation
* [AUTO-TRANSLATED:d8ff2cd5]
*/
virtual toolkit::Buffer::Ptr getExtraData() const { return nullptr; }
/**
* extra data
* Set extra data,
* [AUTO-TRANSLATED:9e551857]
*/
virtual void setExtraData(const uint8_t *data, size_t size) {}
/**
*
* @return
* Return bitrate
* @return Bitrate
* [AUTO-TRANSLATED:265dda35]
*/
virtual int getBitRate() const { return _bit_rate; }
/**
*
* @param bit_rate
* Set bitrate
* @param bit_rate Bitrate
* [AUTO-TRANSLATED:77a43064]
*/
virtual void setBitRate(int bit_rate) { _bit_rate = bit_rate; }
private:
int _bit_rate = 0;
};
/**
* Track类fps信息
* Video channel description Track class, supports getting width, height and fps information
* [AUTO-TRANSLATED:8d1893c5]
*/
class VideoTrack : public Track {
public:
using Ptr = std::shared_ptr<VideoTrack>;
/**
*
* Return video height
* [AUTO-TRANSLATED:b24aabc0]
*/
virtual int getVideoHeight() const { return 0; }
/**
*
* Return video width
* [AUTO-TRANSLATED:2f3bb6e3]
*/
virtual int getVideoWidth() const { return 0; }
/**
* fps
* Return video fps
* [AUTO-TRANSLATED:ced99aef]
*/
virtual float getVideoFps() const { return 0; }
/**
* sps/pps
* Return related sps/pps, etc.
* [AUTO-TRANSLATED:30fc4f63]
*/
virtual std::vector<Frame::Ptr> getConfigFrames() const { return std::vector<Frame::Ptr>{}; }
};
class VideoTrackImp : public VideoTrack {
public:
using Ptr = std::shared_ptr<VideoTrackImp>;
/**
*
* @param codec_id
* @param width
* @param height
* @param fps
* Constructor
* @param codec_id Encoding type
* @param width Width
* @param height Height
* @param fps Frame rate
* [AUTO-TRANSLATED:b3d1ef4d]
*/
VideoTrackImp(CodecId codec_id, int width, int height, int fps) {
_codec_id = codec_id;
_width = width;
_height = height;
_fps = fps;
}
int getVideoWidth() const override { return _width; }
int getVideoHeight() const override { return _height; }
float getVideoFps() const override { return _fps; }
bool ready() const override { return true; }
Track::Ptr clone() const override { return std::make_shared<VideoTrackImp>(*this); }
Sdp::Ptr getSdp(uint8_t payload_type) const override { return nullptr; }
CodecId getCodecId() const override { return _codec_id; }
private:
CodecId _codec_id;
int _width = 0;
int _height = 0;
float _fps = 0;
};
/**
* Track派生类
* Audio Track derived class, supports sampling rate, number of channels, and sampling bit information
* [AUTO-TRANSLATED:5f57819d]
*/
class AudioTrack : public Track {
public:
using Ptr = std::shared_ptr<AudioTrack>;
/**
*
* Return audio sampling rate
* [AUTO-TRANSLATED:9af5a0a4]
*/
virtual int getAudioSampleRate() const {return 0;};
/**
* 168
* Return audio sampling bit depth, generally 16 or 8
* [AUTO-TRANSLATED:5fedc65d]
*/
virtual int getAudioSampleBit() const {return 0;};
/**
*
* Return audio number of channels
* [AUTO-TRANSLATED:2613b317]
*/
virtual int getAudioChannel() const {return 0;};
};
class AudioTrackImp : public AudioTrack{
public:
using Ptr = std::shared_ptr<AudioTrackImp>;
/**
*
* @param codecId
* @param sample_rate (HZ)
* @param channels
* @param sample_bit 16
* Constructor
* @param codecId Encoding type
* @param sample_rate Sampling rate (HZ)
* @param channels Number of channels
* @param sample_bit Sampling bit depth, generally 16
* [AUTO-TRANSLATED:0ad0211f]
*/
AudioTrackImp(CodecId codecId, int sample_rate, int channels, int sample_bit){
_codecid = codecId;
_sample_rate = sample_rate;
_channels = channels;
_sample_bit = sample_bit;
}
/**
*
* Return encoding type
* [AUTO-TRANSLATED:c8731864]
*/
CodecId getCodecId() const override{
return _codecid;
}
/**
*
* Whether it has been initialized
* [AUTO-TRANSLATED:5dc6693e]
*/
bool ready() const override {
return true;
}
/**
*
* Return audio sampling rate
* [AUTO-TRANSLATED:9af5a0a4]
*/
int getAudioSampleRate() const override{
return _sample_rate;
}
/**
* 168
* Return audio sampling bit depth, generally 16 or 8
* [AUTO-TRANSLATED:5fedc65d]
*/
int getAudioSampleBit() const override{
return _sample_bit;
}
/**
*
* Return audio number of channels
* [AUTO-TRANSLATED:2613b317]
*/
int getAudioChannel() const override{
return _channels;
}
Track::Ptr clone() const override { return std::make_shared<AudioTrackImp>(*this); }
Sdp::Ptr getSdp(uint8_t payload_type) const override { return nullptr; }
private:
CodecId _codecid;
int _sample_rate;
int _channels;
int _sample_bit;
};
class TrackSource {
public:
virtual ~TrackSource() = default;
/**
* Track
* @param trackReady Track
* Get all Tracks
* @param trackReady Whether to get all ready Tracks
* [AUTO-TRANSLATED:f0779985]
*/
virtual std::vector<Track::Ptr> getTracks(bool trackReady = true) const = 0;
/**
* Track
* @param type track类型
* @param trackReady Track
* Get specific Track
* @param type Track type
* @param trackReady Whether to get all ready Tracks
* [AUTO-TRANSLATED:c50781b9]
*/
Track::Ptr getTrack(TrackType type , bool trackReady = true) const {
auto tracks = getTracks(trackReady);
for(auto &track : tracks){
if(track->getTrackType() == type){
return track;
}
}
return nullptr;
}
};
}//namespace mediakit
#endif //ZLMEDIAKIT_TRACK_H

View File

@ -0,0 +1,175 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCE_H
#define ZLMEDIAKIT_FMP4MEDIASOURCE_H
#include "Common/MediaSource.h"
#include "Common/PacketCache.h"
#include "Util/RingBuffer.h"
#define FMP4_GOP_SIZE 512
namespace mediakit {
// FMP4直播数据包 [AUTO-TRANSLATED:64f8a1d1]
// FMP4 Live Data Packet
class FMP4Packet : public toolkit::BufferString{
public:
using Ptr = std::shared_ptr<FMP4Packet>;
template<typename ...ARGS>
FMP4Packet(ARGS && ...args) : toolkit::BufferString(std::forward<ARGS>(args)...) {};
public:
uint64_t time_stamp = 0;
};
// FMP4直播源 [AUTO-TRANSLATED:15c43604]
// FMP4 Live Source
class FMP4MediaSource final : public MediaSource, public toolkit::RingDelegate<FMP4Packet::Ptr>, private PacketCache<FMP4Packet>{
public:
using Ptr = std::shared_ptr<FMP4MediaSource>;
using RingDataType = std::shared_ptr<toolkit::List<FMP4Packet::Ptr> >;
using RingType = toolkit::RingBuffer<RingDataType>;
FMP4MediaSource(const MediaTuple& tuple,
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, tuple), _ring_size(ring_size) {}
~FMP4MediaSource() override {
try {
flush();
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
/**
*
* Get the circular buffer of the media source
* [AUTO-TRANSLATED:91a762bc]
*/
const RingType::Ptr &getRing() const {
return _ring;
}
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);
}
/**
* fmp4 init segment
* Get the fmp4 init segment
* [AUTO-TRANSLATED:6c704ec9]
*/
const std::string &getInitSegment() const{
return _init_segment;
}
/**
* fmp4 init segment
* @param str init segment
* Set the fmp4 init segment
* @param str init segment
* [AUTO-TRANSLATED:3f41879f]
*/
void setInitSegment(std::string str) {
_init_segment = std::move(str);
createRing();
}
/**
*
* Get the number of players
* [AUTO-TRANSLATED:a451c846]
*/
int readerCount() override {
return _ring ? _ring->readerCount() : 0;
}
/**
* FMP4包
* @param packet FMP4包
* @param key
* Input FMP4 packet
* @param packet FMP4 packet
* @param key Whether it is the first packet of the key frame
* [AUTO-TRANSLATED:3b310b27]
*/
void onWrite(FMP4Packet::Ptr packet, bool key) override {
if (!_ring) {
createRing();
}
if (key) {
_have_video = true;
}
_speed[TrackVideo] += packet->size();
auto stamp = packet->time_stamp;
PacketCache<FMP4Packet>::inputPacket(stamp, true, std::move(packet), key);
}
/**
* GOP缓存
* Clear GOP cache
* [AUTO-TRANSLATED:d863f8c9]
*/
void clearCache() override {
PacketCache<FMP4Packet>::clearCache();
_ring->clearCache();
}
private:
void createRing(){
std::weak_ptr<FMP4MediaSource> weak_self = std::static_pointer_cast<FMP4MediaSource>(shared_from_this());
_ring = std::make_shared<RingType>(_ring_size, [weak_self](int size) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strong_self->onReaderChanged(size);
});
if (!_init_segment.empty()) {
regist();
}
}
/**
*
* @param packet_list
* @param key_pos
* Merge write callback
* @param packet_list Merge write cache queue
* @param key_pos Whether it contains a key frame
* [AUTO-TRANSLATED:6e93913e]
*/
void onFlush(std::shared_ptr<toolkit::List<FMP4Packet::Ptr> > packet_list, bool key_pos) override {
// 如果不存在视频那么就没有存在GOP缓存的意义所以确保一直清空GOP缓存 [AUTO-TRANSLATED:66208f94]
// If there is no video, then there is no meaning to the existence of GOP cache, so make sure to clear the GOP cache all the time
_ring->write(std::move(packet_list), _have_video ? key_pos : true);
}
private:
bool _have_video = false;
int _ring_size;
std::string _init_segment;
RingType::Ptr _ring;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_FMP4MEDIASOURCE_H

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#include "FMP4MediaSource.h"
#include "Record/MP4Muxer.h"
namespace mediakit {
class FMP4MediaSourceMuxer final : public MP4MuxerMemory, public MediaSourceEventInterceptor,
public std::enable_shared_from_this<FMP4MediaSourceMuxer> {
public:
using Ptr = std::shared_ptr<FMP4MediaSourceMuxer>;
FMP4MediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) {
_option = option;
_media_src = std::make_shared<FMP4MediaSource>(tuple);
}
~FMP4MediaSourceMuxer() override {
try {
MP4MuxerMemory::flush();
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
setDelegate(listener);
_media_src->setListener(shared_from_this());
}
int readerCount() const{
return _media_src->readerCount();
}
void onReaderChanged(MediaSource &sender, int size) override {
_enabled = _option.fmp4_demand ? size : true;
if (!size && _option.fmp4_demand) {
_clear_cache = true;
}
MediaSourceEventInterceptor::onReaderChanged(sender, size);
}
bool inputFrame(const Frame::Ptr &frame) override {
if (_clear_cache && _option.fmp4_demand) {
_clear_cache = false;
_media_src->clearCache();
}
if (_enabled || !_option.fmp4_demand) {
return MP4MuxerMemory::inputFrame(frame);
}
return false;
}
bool isEnabled() {
// 缓存尚未清空时还允许触发inputFrame函数以便及时清空缓存 [AUTO-TRANSLATED:7cfd4d49]
// The inputFrame function is still allowed to be triggered when the cache has not been cleared, so that the cache can be cleared in time.
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
}
void addTrackCompleted() override {
MP4MuxerMemory::addTrackCompleted();
_media_src->setInitSegment(getInitSegment());
}
protected:
void onSegmentData(std::string string, uint64_t stamp, bool key_frame) override {
if (string.empty()) {
return;
}
FMP4Packet::Ptr packet = std::make_shared<FMP4Packet>(std::move(string));
packet->time_stamp = stamp;
_media_src->onWrite(std::move(packet), key_frame);
}
private:
bool _enabled = true;
bool _clear_cache = false;
ProtocolOption _option;
FMP4MediaSource::Ptr _media_src;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <cstdlib>
#include <cinttypes>
#include "HlsParser.h"
#include "Util/util.h"
#include "Common/Parser.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
bool HlsParser::parse(const string &http_url, const string &m3u8) {
float extinf_dur = 0;
ts_segment segment;
map<int, ts_segment> ts_map;
_total_dur = 0;
_is_live = true;
_is_m3u8_inner = false;
int index = 0;
auto lines = split(m3u8, "\n");
for (auto &line : lines) {
trim(line);
if (line.size() < 2) {
continue;
}
if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') {
segment.duration = extinf_dur;
segment.url = Parser::mergeUrl(http_url, line);
if (!_is_m3u8_inner) {
// ts按照先后顺序排序 [AUTO-TRANSLATED:c34f8c9d]
// Sort by order of appearance
ts_map.emplace(index++, segment);
} else {
// 子m3u8按照带宽排序 [AUTO-TRANSLATED:749cb42b]
// Sort sub m3u8 by bandwidth
ts_map.emplace(segment.bandwidth, segment);
}
extinf_dur = 0;
continue;
}
_is_m3u8_inner = false;
if (line.find("#EXTINF:") == 0) {
sscanf(line.data(), "#EXTINF:%f,", &extinf_dur);
_total_dur += extinf_dur;
continue;
}
static const string s_stream_inf = "#EXT-X-STREAM-INF:";
if (line.find(s_stream_inf) == 0) {
_is_m3u8_inner = true;
auto key_val = Parser::parseArgs(line.substr(s_stream_inf.size()), ",", "=");
segment.program_id = atoi(key_val["PROGRAM-ID"].data());
segment.bandwidth = atoi(key_val["BANDWIDTH"].data());
sscanf(key_val["RESOLUTION"].data(), "%dx%d", &segment.width, &segment.height);
continue;
}
if (line == "#EXTM3U") {
_is_m3u8 = true;
continue;
}
if (line.find("#EXT-X-ALLOW-CACHE:") == 0) {
_allow_cache = (line.find(":YES") != string::npos);
continue;
}
if (line.find("#EXT-X-VERSION:") == 0) {
sscanf(line.data(), "#EXT-X-VERSION:%d", &_version);
continue;
}
if (line.find("#EXT-X-TARGETDURATION:") == 0) {
sscanf(line.data(), "#EXT-X-TARGETDURATION:%d", &_target_dur);
continue;
}
if (line.find("#EXT-X-MEDIA-SEQUENCE:") == 0) {
sscanf(line.data(), "#EXT-X-MEDIA-SEQUENCE:%" PRId64, &_sequence);
continue;
}
if (line.find("#EXT-X-ENDLIST") == 0) {
// 点播 [AUTO-TRANSLATED:a64427bc]
// On-demand
_is_live = false;
continue;
}
continue;
}
return _is_m3u8 && onParsed(_is_m3u8_inner, _sequence, ts_map);
}
bool HlsParser::isM3u8() const {
return _is_m3u8;
}
bool HlsParser::isLive() const {
return _is_live;
}
bool HlsParser::allowCache() const {
return _allow_cache;
}
int HlsParser::getVersion() const {
return _version;
}
int HlsParser::getTargetDur() const {
return _target_dur;
}
int64_t HlsParser::getSequence() const {
return _sequence;
}
bool HlsParser::isM3u8Inner() const {
return _is_m3u8_inner;
}
float HlsParser::getTotalDuration() const {
return _total_dur;
}
}//namespace mediakit

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HTTP_HLSPARSER_H
#define HTTP_HLSPARSER_H
#include <string>
#include <list>
#include <map>
namespace mediakit {
typedef struct{
// url地址 [AUTO-TRANSLATED:64a1b5d1]
// URL address
std::string url;
// ts切片长度 [AUTO-TRANSLATED:9d5545f8]
// TS segment length
float duration;
// ////内嵌m3u8////// [AUTO-TRANSLATED:c3fabbfd]
// //// Embedded m3u8 //////
// 节目id [AUTO-TRANSLATED:8c6000cc]
// Program ID
int program_id;
// 带宽 [AUTO-TRANSLATED:5f852828]
// Bandwidth
int bandwidth;
// 宽度 [AUTO-TRANSLATED:06ad2724]
// Width
int width;
// 高度 [AUTO-TRANSLATED:87a07641]
// Height
int height;
} ts_segment;
class HlsParser {
public:
bool parse(const std::string &http_url,const std::string &m3u8);
/**
* #EXTM3U字段m3u8文件
* Whether the #EXTM3U field exists, whether it is an m3u8 file
* [AUTO-TRANSLATED:ac1bf089]
*/
bool isM3u8() const;
/**
* #EXT-X-ALLOW-CACHE值cache
* #EXT-X-ALLOW-CACHE value, whether caching is allowed
* [AUTO-TRANSLATED:90e88422]
*/
bool allowCache() const;
/**
* #EXT-X-ENDLIST字段
* Whether the #EXT-X-ENDLIST field exists, whether it is a live stream
* [AUTO-TRANSLATED:f18e3c44]
*/
bool isLive() const ;
/**
* #EXT-X-VERSION值
* #EXT-X-VERSION value, version number
* [AUTO-TRANSLATED:89a99b3d]
*/
int getVersion() const;
/**
* #EXT-X-TARGETDURATION字段值
* #EXT-X-TARGETDURATION field value
* [AUTO-TRANSLATED:6720dc84]
*/
int getTargetDur() const;
/**
* #EXT-X-MEDIA-SEQUENCE字段值m3u8序号
* #EXT-X-MEDIA-SEQUENCE field value, the sequence number of this m3u8
* [AUTO-TRANSLATED:1a75250a]
*/
int64_t getSequence() const;
/**
* m3u8
* Whether it contains sub-m3u8 internally
* [AUTO-TRANSLATED:67b4a20c]
*/
bool isM3u8Inner() const;
/**
*
* Get the total time
* [AUTO-TRANSLATED:aa5e797b]
*/
float getTotalDuration() const;
protected:
/**
* m3u8文件回调
* @param is_m3u8_inner m3u8文件中是否包含多个hls地址
* @param sequence ts序号
* @param ts_list ts地址列表
* @return false时HlsParser::parse返回false
* Callback for parsing the m3u8 file
* @param is_m3u8_inner Whether this m3u8 file contains multiple HLS addresses
* @param sequence TS sequence number
* @param ts_list TS address list
* @return Whether the parsing is successful, returning false will cause HlsParser::parse to return false
* [AUTO-TRANSLATED:be34e59f]
*/
virtual bool onParsed(bool is_m3u8_inner, int64_t sequence, const std::map<int, ts_segment> &ts_list) = 0;
private:
bool _is_m3u8 = false;
bool _allow_cache = false;
bool _is_live = true;
int _version = 0;
int _target_dur = 0;
float _total_dur = 0;
int64_t _sequence = 0;
// 每部是否有m3u8 [AUTO-TRANSLATED:c0d01536]
// Whether each part has an m3u8
bool _is_m3u8_inner = false;
};
}//namespace mediakit
#endif //HTTP_HLSPARSER_H

View File

@ -0,0 +1,530 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HlsPlayer.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller) {
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
}
void HlsPlayer::play(const string &url) {
_play_result = false;
_play_url = url;
setProxyUrl((*this)[Client::kProxyUrl]);
setAllowResendRequest(true);
fetchIndexFile();
}
void HlsPlayer::fetchIndexFile() {
if (waitResponse()) {
return;
}
if (!(*this)[Client::kNetAdapter].empty()) {
setNetAdapter((*this)[Client::kNetAdapter]);
}
setCompleteTimeout((*this)[Client::kTimeoutMS].as<int>());
setMethod("GET");
sendRequest(_play_url);
}
void HlsPlayer::teardown_l(const SockException &ex) {
if (!_play_result) {
_play_result = true;
onPlayResult(ex);
} else {
// 如果不是主动关闭的,则重新拉取索引文件 [AUTO-TRANSLATED:e187c069]
// If it is not actively closed, then re-pull the index file
// if not actively closed, re-fetch the index file
if (ex.getErrCode() != Err_shutdown && HlsParser::isLive()) {
// 如果重试次数已经达到最大次数时, 且切片列表已空, 而且没有正在下载的切片, 则认为失败关闭播放器 [AUTO-TRANSLATED:2afe6c3a]
// If the retry count has reached the maximum number of times, and the slice list is empty, and there are no slices being downloaded, then it is considered a failure to close the player
// If the retry count has reached the maximum number of times, and the segments list is empty, and there is no segment being downloaded,
// the player is considered to be closed due to failure
if (_ts_list.empty() && !(_http_ts_player && _http_ts_player->waitResponse()) && _try_fetch_index_times >= MAX_TRY_FETCH_INDEX_TIMES) {
onShutdown(ex);
} else {
_try_fetch_index_times += 1;
shutdown(ex);
WarnL << "Attempt to pull the m3u8 file again[" << _try_fetch_index_times << "]:" << _play_url;
// 当网络波动时有可能拉取m3u8文件失败, 因此快速重试拉取m3u8文件, 而不是直接关闭播放器 [AUTO-TRANSLATED:0cb45f5f]
// When the network fluctuates, it is possible that the m3u8 file will fail to be pulled, so quickly retry pulling the m3u8 file instead of directly closing the player
// 这里增加一个延时是为了防止_http_ts_player的socket还保持alive状态就多次拉取m3u8文件了 [AUTO-TRANSLATED:f779e7e9]
// A delay is added here to prevent the _http_ts_player socket from remaining alive and pulling the m3u8 file multiple times
// When the network fluctuates, it is possible to fail to pull the m3u8 file, so quickly retry to pull the m3u8 file instead of closing the player directly
// The delay here is to prevent the socket of _http_ts_player from still keeping alive state, and pull the m3u8 file multiple times
// todo _http_ts_player->waitResponse()这个判断条件是否有必要因为有时候存在_complete==true但是_http_ts_player->alive()为true的情况 [AUTO-TRANSLATED:a92efd3e]
// todo Is the _http_ts_player->waitResponse() condition necessary? Because sometimes there is _complete==true, but _http_ts_player->alive() is true
playDelay(0.3);
return;
}
} else {
onShutdown(ex);
}
}
_timer.reset();
_timer_ts.reset();
_http_ts_player.reset();
shutdown(ex);
}
void HlsPlayer::teardown() {
teardown_l(SockException(Err_shutdown, "teardown"));
}
void HlsPlayer::fetchSegment() {
if (_ts_list.empty()) {
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628 [AUTO-TRANSLATED:c2d0b647]
// If it is an on-demand file, an empty playlist means that the file playback is finished, and the player is closed: #2628
// If it is a video-on-demand file, the playlist is empty means the file is finished playing, close the player: #2628
if (!HlsParser::isLive()) {
teardown();
return;
}
// 播放列表为空那么立即重新下载m3u8文件 [AUTO-TRANSLATED:e01943f3]
// If the playlist is empty, then immediately re-download the m3u8 file
// The playlist is empty, so download the m3u8 file immediately
_timer.reset();
fetchIndexFile();
return;
}
if (_http_ts_player && _http_ts_player->waitResponse()) {
// 播放器目前还存活,正在下载中 [AUTO-TRANSLATED:c18d8446]
// The player is still alive and is currently downloading
return;
}
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
if (!_http_ts_player) {
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller());
_http_ts_player->setProxyUrl((*this)[Client::kProxyUrl]);
_http_ts_player->setAllowResendRequest(true);
_http_ts_player->setOnCreateSocket([weak_self](const EventPoller::Ptr &poller) {
auto strong_self = weak_self.lock();
if (strong_self) {
return strong_self->createSocket();
}
return Socket::createSocket(poller, true);
});
auto benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
if (!benchmark_mode) {
_http_ts_player->setOnPacket([weak_self](const char *data, size_t len) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
// 收到ts包 [AUTO-TRANSLATED:334862da]
// Received ts packet
// Received ts package
strong_self->onPacket(data, len);
});
}
if (!(*this)[Client::kNetAdapter].empty()) {
_http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]);
}
}
Ticker ticker;
auto url = _ts_list.front().url;
auto duration = _ts_list.front().duration;
_ts_list.pop_front();
_http_ts_player->setOnComplete([weak_self, ticker, duration, url](const SockException &err) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
if (err) {
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);
}
strong_self->_ts_download_failed_count++;
if (strong_self->_ts_download_failed_count > MAX_TS_DOWNLOAD_FAILED_COUNT) {
WarnL << "ts segment " << url << " download failed count is " << strong_self->_ts_download_failed_count << ", teardown player";
strong_self->teardown_l(SockException(Err_shutdown, "ts segment download failed"));
return;
}
} else {
strong_self->_ts_download_failed_count = 0;
}
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628 [AUTO-TRANSLATED:82247326]
// Download 0.5 seconds in advance to support on-demand file download speed control: #2628
// Download 0.5 seconds in advance to support video-on-demand files to control download speed: #2628
auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f;
if (delay > 2.0) {
// 提前1秒下载 [AUTO-TRANSLATED:852349aa]
// Download 1 second in advance
// Download 1 second in advance
delay -= 1.0;
} else if (delay <= 0) {
// 延时最小10ms [AUTO-TRANSLATED:fbb3665e]
// Delay a minimum of 10ms
// Delay at least 10ms
delay = 0.01;
}
// 延时下载下一个切片 [AUTO-TRANSLATED:26eb528d]
// Delay downloading the next slice
strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->fetchSegment();
}
return false;
}, strong_self->getPoller()));
});
_http_ts_player->setMethod("GET");
// ts切片必须在其时长的2-5倍内下载完毕 [AUTO-TRANSLATED:d458e7b5]
// The ts slice must be downloaded within 2-5 times its duration
// The ts segment must be downloaded within 2-5 times its duration
_http_ts_player->setCompleteTimeout(_timeout_multiple * duration * 1000);
_http_ts_player->sendRequest(url);
}
bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts_segment> &ts_map) {
if (!is_m3u8_inner) {
// 这是ts播放列表 [AUTO-TRANSLATED:7ce3d81b]
// This is the ts playlist
// This is the ts playlist
if (_last_sequence == sequence) {
// 如果是重复的ts列表那么忽略 [AUTO-TRANSLATED:d15a47f3]
// If it is a duplicate ts list, then ignore it
// 但是需要注意, 如果当前ts列表为空了, 那么表明直播结束了或者m3u8文件有问题,需要重新拉流 [AUTO-TRANSLATED:438a8df0]
// However, it should be noted that if the current ts list is empty, then it means that the live broadcast has ended or the m3u8 file has a problem, and the stream needs to be re-pulled
// 这里的5倍是为了防止m3u8文件有问题导致的无限重试 [AUTO-TRANSLATED:3c8d073d]
// The 5 times here is to prevent infinite retries caused by problems with the m3u8 file
// If it is a duplicate ts list, ignore it
// But it should be noted that if the current ts list is empty, it means that the live broadcast is over or the m3u8 file is problematic, and you need to re-pull the stream
// The 5 times here is to prevent infinite retries caused by problems with the m3u8 file
if (_last_sequence > 0 && _ts_list.empty() && HlsParser::isLive()
&& _wait_index_update_ticker.elapsedTime() > (uint64_t)HlsParser::getTargetDur() * 1000 * 5) {
_wait_index_update_ticker.resetTime();
WarnL << "Fetch new ts list from m3u8 timeout";
return false;
}
return true;
}
_last_sequence = sequence;
_wait_index_update_ticker.resetTime();
for (auto &pr : ts_map) {
auto &ts = pr.second;
if (_ts_url_cache.emplace(ts.url).second) {
// 该ts未重复 [AUTO-TRANSLATED:4b6fab6b]
// This ts is not duplicated
// The ts is not repeated
_ts_list.emplace_back(ts);
// 按时间排序 [AUTO-TRANSLATED:7b61e414]
// Sort by time
// Sort by time
_ts_url_sort.emplace_back(ts.url);
}
}
if (_ts_url_sort.size() > 2 * ts_map.size()) {
// 去除防重列表中过多的数据 [AUTO-TRANSLATED:94173d03]
// Remove excessive data from the anti-repetition list
// Remove too much data from the anti-repetition list
_ts_url_cache.erase(_ts_url_sort.front());
_ts_url_sort.pop_front();
}
fetchSegment();
} else {
// 这是m3u8列表,我们播放最高清的子hls [AUTO-TRANSLATED:6e6981ef]
// This is the m3u8 list, we play the highest definition sub-hls
// This is the m3u8 list, we play the highest quality sub-hls
if (ts_map.empty()) {
throw invalid_argument("empty sub hls list:" + getUrl());
}
_timer.reset();
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
auto url = ts_map.rbegin()->second.url;
getPoller()->async([weak_self, url]() {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->play(url);
}
}, false);
}
return true;
}
void HlsPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &headers) {
if (status != "200" && status != "206") {
// 失败 [AUTO-TRANSLATED:ba46763c]
// Failure
// Failed
throw invalid_argument("bad http status code:" + status);
}
auto content_type = strToLower(const_cast<HttpClient::HttpHeader &>(headers)["Content-Type"]);
if (content_type.find("application/vnd.apple.mpegurl") != 0 && content_type.find("/x-mpegurl") == _StrPrinter::npos) {
WarnL << "May not a hls video: " << content_type << ", url: " << getUrl();
}
_m3u8.clear();
}
void HlsPlayer::onResponseBody(const char *buf, size_t size) {
_m3u8.append(buf, size);
}
void HlsPlayer::onResponseCompleted(const SockException &ex) {
if (ex) {
teardown_l(ex);
return;
}
if (!HlsParser::parse(getUrl(), _m3u8)) {
teardown_l(SockException(Err_other, "parse m3u8 failed:" + _play_url));
return;
}
// 如果有或取到新的切片, 那么就算成功, 应该重置失败次数 [AUTO-TRANSLATED:ae8dad10]
// If there are or new slices are obtained, then it is considered successful, and the failure count should be reset
// if there are new segments or get new segments, it is considered successful, and the number of failures should be reset
if (!_ts_list.empty()) {
_try_fetch_index_times = 0;
}
if (!_play_result) {
_play_result = true;
onPlayResult(SockException());
}
playDelay();
}
float HlsPlayer::delaySecond() {
if (HlsParser::isM3u8() && HlsParser::getTargetDur() > 0) {
float targetOffset;
if (HlsParser::isLive()) {
// see RFC 8216, Section 4.4.3.8.
// 根据rfc刷新index列表的周期应该是分段时间x3, 因为根据规范播放器只处理最后3个Segment [AUTO-TRANSLATED:07168708]
// According to the rfc, the refresh cycle of the index list should be 3 times the segment time, because according to the specification, the player only processes the last 3 Segments
// refresh the index list according to rfc cycle should be the segment time x3,
// because according to the specification, the player only handles the last 3 segments
targetOffset = (float)(3 * HlsParser::getTargetDur());
} else {
// 点播则一般m3u8文件不会在改变了, 没必要频繁的刷新, 所以按照总时间来进行刷新 [AUTO-TRANSLATED:2ac0a29e]
// On-demand generally does not change the m3u8 file, there is no need to refresh frequently, so refresh according to the total time
// On-demand, the m3u8 file will generally not change, so there is no need to refresh frequently,
targetOffset = HlsParser::getTotalDuration();
}
// 取最小值, 避免因为分段时长不规则而导致的问题 [AUTO-TRANSLATED:073dff48]
// Take the minimum value to avoid problems caused by irregular segment durations
// Take the minimum value to avoid problems caused by irregular segment duration
if (targetOffset > HlsParser::getTotalDuration()) {
targetOffset = HlsParser::getTotalDuration();
}
// 根据规范为一半的时间 [AUTO-TRANSLATED:07652637]
// According to the specification, it is half the time
// According to the specification, it is half the time
if (targetOffset / 2 > 1.0f) {
return targetOffset / 2;
}
}
return 1.0f;
}
bool HlsPlayer::onRedirectUrl(const string &url, bool temporary) {
_play_url = url;
return true;
}
void HlsPlayer::playDelay(float delay_sec) {
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
if (delay_sec == 0) {
delay_sec = delaySecond();
}
_timer.reset(new Timer(delay_sec, [weak_self]() {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->fetchIndexFile();
}
return false;
}, getPoller()));
}
//////////////////////////////////////////////////////////////////////////
void HlsDemuxer::start(const EventPoller::Ptr &poller, TrackListener *listener) {
_frame_cache.clear();
_delegate.setTrackListener(listener);
// 每50毫秒执行一次 [AUTO-TRANSLATED:e32f2140]
// Execute once every 50 milliseconds
// Execute every 50 milliseconds
weak_ptr<HlsDemuxer> weak_self = shared_from_this();
_timer = std::make_shared<Timer>(0.05f, [weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
return false;
}
strong_self->onTick();
return true;
}, poller);
}
void HlsDemuxer::pushTask(std::function<void()> task) {
int64_t stamp = 0;
if (!_frame_cache.empty()) {
stamp = _frame_cache.back().first;
}
_frame_cache.emplace_back(std::make_pair(stamp, std::move(task)));
}
bool HlsDemuxer::inputFrame(const Frame::Ptr &frame) {
// 为了避免track准备时间过长, 因此在没准备好之前, 直接消费掉所有的帧 [AUTO-TRANSLATED:72b35430]
// To avoid the track preparation time being too long, all frames are directly consumed before it is ready
// In order to avoid the track preparation time is too long, so before it is ready, all frames are consumed directly
if (!_delegate.isAllTrackReady()) {
_delegate.inputFrame(frame);
return true;
}
if (_frame_cache.empty()) {
// 设置当前播放位置时间戳 [AUTO-TRANSLATED:14799e6c]
// Set the current playback position timestamp
// Set the current playback position timestamp
setPlayPosition(frame->dts());
}
// 根据时间戳缓存frame [AUTO-TRANSLATED:f84d3698]
// Cache frames based on the timestamp
// Cache frame according to timestamp
auto cached_frame = Frame::getCacheAbleFrame(frame);
_frame_cache.emplace_back(std::make_pair(frame->dts(), [cached_frame, this]() {
_delegate.inputFrame(cached_frame);
}));
if (getBufferMS() > 30 * 1000) {
// 缓存超过30秒强制消费至15秒(减少延时或内存占用) [AUTO-TRANSLATED:d6d58dde]
// If the cache exceeds 30 seconds, force consumption to 15 seconds (reduce latency or memory usage)
// The cache exceeds 30 seconds, and the consumption is forced to 15 seconds (reduce delay or memory usage)
while (getBufferMS() > 15 * 1000) {
_frame_cache.begin()->second();
_frame_cache.erase(_frame_cache.begin());
}
// 接着播放缓存中最早的帧 [AUTO-TRANSLATED:a1c76e0e]
// Then play the earliest frame in the cache
// Then play the earliest frame in the cache
setPlayPosition(_frame_cache.begin()->first);
}
return true;
}
int64_t HlsDemuxer::getPlayPosition() {
return _ticker.elapsedTime() + _ticker_offset;
}
int64_t HlsDemuxer::getBufferMS() {
if (_frame_cache.empty()) {
return 0;
}
return _frame_cache.rbegin()->first - _frame_cache.begin()->first;
}
void HlsDemuxer::setPlayPosition(int64_t pos) {
_ticker.resetTime();
_ticker_offset = pos;
}
void HlsDemuxer::onTick() {
auto it = _frame_cache.begin();
while (it != _frame_cache.end()) {
if (it->first > getPlayPosition()) {
// 这些帧还未到时间播放 [AUTO-TRANSLATED:e1ef7fe2]
// These frames have not yet reached their playback time
// These frames are not yet time to play
break;
}
if (getBufferMS() < 3 * 1000) {
// 缓存小于3秒,那么降低定时器消费速度(让剩余的数据在3秒后消费完毕) [AUTO-TRANSLATED:bc14fe02]
// If the cache is less than 3 seconds, then reduce the timer consumption speed (so that the remaining data is consumed after 3 seconds)
// 目的是为了防止定时器长时间干等后,数据瞬间消费完毕 [AUTO-TRANSLATED:55ac9c3d]
// The goal is to prevent the timer from waiting for a long time before the data is consumed instantly
// If the cache is less than 3 seconds, then reduce the speed of the timer to consume (let the remaining data be consumed after 3 seconds)
// The purpose is to prevent the timer from waiting for a long time, and the data is consumed instantly
setPlayPosition(_frame_cache.begin()->first);
}
// 消费掉已经到期的帧 [AUTO-TRANSLATED:f2d1230a]
// Consume expired frames
// Consume expired frames
it->second();
it = _frame_cache.erase(it);
}
}
//////////////////////////////////////////////////////////////////////////
HlsPlayerImp::HlsPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<HlsPlayer, PlayerBase>(poller) {}
void HlsPlayerImp::onPacket(const char *data, size_t len) {
if (!_decoder && _demuxer) {
_decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _demuxer.get());
}
if (_decoder && _demuxer) {
_decoder->input((uint8_t *) data, len);
}
}
void HlsPlayerImp::addTrackCompleted() {
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(SockException(Err_success, "play hls success"));
}
void HlsPlayerImp::onPlayResult(const SockException &ex) {
auto benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
if (ex || benchmark_mode) {
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
} else {
auto demuxer = std::make_shared<HlsDemuxer>();
demuxer->start(getPoller(), this);
_demuxer = std::move(demuxer);
}
}
void HlsPlayerImp::onShutdown(const SockException &ex) {
while (_demuxer) {
try {
// shared_from_this()可能抛异常 [AUTO-TRANSLATED:c57c464a]
// shared_from_this() may throw an exception
// shared_from_this() may throw an exception
std::weak_ptr<HlsPlayerImp> weak_self = static_pointer_cast<HlsPlayerImp>(shared_from_this());
if (_decoder) {
_decoder->flush();
}
// 等待所有frame flush输出后再触发onShutdown事件 [AUTO-TRANSLATED:6db59f15]
// Wait for all frames to be flushed before triggering the onShutdown event
// Wait for all frame flush output, then trigger the onShutdown event
static_pointer_cast<HlsDemuxer>(_demuxer)->pushTask([weak_self, ex]() {
if (auto strong_self = weak_self.lock()) {
strong_self->_demuxer = nullptr;
strong_self->onShutdown(ex);
}
});
return;
} catch (...) {
break;
}
}
PlayerImp<HlsPlayer, PlayerBase>::onShutdown(ex);
}
vector<Track::Ptr> HlsPlayerImp::getTracks(bool ready) const {
if (!_demuxer) {
return vector<Track::Ptr>();
}
return static_pointer_cast<HlsDemuxer>(_demuxer)->getTracks(ready);
}
}//namespace mediakit

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HTTP_HLSPLAYER_H
#define HTTP_HLSPLAYER_H
#include "Player/PlayerBase.h"
#include "HttpTSPlayer.h"
#include "HlsParser.h"
#include "Rtp/TSDecoder.h"
#define MIN_TIMEOUT_MULTIPLE 2
#define MAX_TIMEOUT_MULTIPLE 5
#define MAX_TRY_FETCH_INDEX_TIMES 5
#define MAX_TS_DOWNLOAD_FAILED_COUNT 10
namespace mediakit {
class HlsDemuxer : public MediaSinkInterface , public TrackSource, public std::enable_shared_from_this<HlsDemuxer> {
public:
~HlsDemuxer() override { _timer = nullptr; }
void start(const toolkit::EventPoller::Ptr &poller, TrackListener *listener);
bool inputFrame(const Frame::Ptr &frame) override;
bool addTrack(const Track::Ptr &track) override { return _delegate.addTrack(track); }
void addTrackCompleted() override { _delegate.addTrackCompleted(); }
void resetTracks() override { ((MediaSink &)_delegate).resetTracks(); }
std::vector<Track::Ptr> getTracks(bool ready = true) const override { return _delegate.getTracks(ready); }
void pushTask(std::function<void()> task);
private:
void onTick();
int64_t getBufferMS();
int64_t getPlayPosition();
void setPlayPosition(int64_t pos);
private:
int64_t _ticker_offset = 0;
toolkit::Ticker _ticker;
toolkit::Timer::Ptr _timer;
MediaSinkDelegate _delegate;
std::deque<std::pair<int64_t, std::function<void()> > > _frame_cache;
};
class HlsPlayer : public HttpClientImp , public PlayerBase , public HlsParser{
public:
HlsPlayer(const toolkit::EventPoller::Ptr &poller);
/**
*
* start play
* Start playing
* start play
* [AUTO-TRANSLATED:03d41cf7]
*/
void play(const std::string &url) override;
/**
*
* stop play
* Stop playing
* stop play
* [AUTO-TRANSLATED:88068dac]
*/
void teardown() override;
protected:
/**
* ts包
* Received ts package
* @param data ts数据负载 ts data payload
* @param len ts包长度 ts package length
* Received ts package
* Received ts package
* @param data ts data payload
* @param len ts package length
* [AUTO-TRANSLATED:159a6559]
*/
virtual void onPacket(const char *data, size_t len) = 0;
private:
bool onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts_segment> &ts_map) override;
void onResponseHeader(const std::string &status, const HttpHeader &headers) override;
void onResponseBody(const char *buf, size_t size) override;
void onResponseCompleted(const toolkit::SockException &e) override;
bool onRedirectUrl(const std::string &url, bool temporary) override;
private:
void playDelay(float delay_sec = 0);
float delaySecond();
void fetchSegment();
void teardown_l(const toolkit::SockException &ex);
void fetchIndexFile();
private:
struct UrlComp {
// url忽略后面的参数 [AUTO-TRANSLATED:788784c3]
// url ignore? parameters after
// Ignore the parameters after the url?
bool operator()(const std::string& __x, const std::string& __y) const {
return toolkit::split(__x,"?")[0] < toolkit::split(__y,"?")[0];
}
};
private:
bool _play_result = false;
int64_t _last_sequence = -1;
std::string _m3u8;
std::string _play_url;
toolkit::Timer::Ptr _timer;
toolkit::Timer::Ptr _timer_ts;
toolkit::Ticker _wait_index_update_ticker;
std::list<ts_segment> _ts_list;
std::list<std::string> _ts_url_sort;
std::set<std::string, UrlComp> _ts_url_cache;
HttpTSPlayer::Ptr _http_ts_player;
int _timeout_multiple = MIN_TIMEOUT_MULTIPLE;
int _try_fetch_index_times = 0;
int _ts_download_failed_count = 0;
};
class HlsPlayerImp : public PlayerImp<HlsPlayer, PlayerBase>, private TrackListener {
public:
using Ptr = std::shared_ptr<HlsPlayerImp>;
HlsPlayerImp(const toolkit::EventPoller::Ptr &poller = nullptr);
private:
//// HlsPlayer override////
void onPacket(const char *data, size_t len) override;
private:
//// PlayerBase override////
void onPlayResult(const toolkit::SockException &ex) override;
std::vector<Track::Ptr> getTracks(bool ready = true) const override;
void onShutdown(const toolkit::SockException &ex) override;
private:
//// TrackListener override////
bool addTrack(const Track::Ptr &track) override { return true; };
void addTrackCompleted() override;
private:
DecoderImp::Ptr _decoder;
MediaSinkInterface::Ptr _demuxer;
};
}//namespace mediakit
#endif //HTTP_HLSPLAYER_H

View File

@ -0,0 +1,396 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <csignal>
#include <tuple>
#ifndef _WIN32
#include <sys/mman.h>
#endif
#if defined(__linux__) || defined(__linux)
#include <sys/sendfile.h>
#endif
#include "Util/File.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/util.h"
#include "Util/uv_errno.h"
#include "HttpBody.h"
#include "HttpClient.h"
#include "Common/macros.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
HttpStringBody::HttpStringBody(string str) {
_str = std::move(str);
}
int64_t HttpStringBody::remainSize() {
return _str.size() - _offset;
}
Buffer::Ptr HttpStringBody::readData(size_t size) {
size = MIN((size_t)remainSize(), size);
if (!size) {
// 没有剩余字节了 [AUTO-TRANSLATED:7bbaa343]
// No remaining bytes
return nullptr;
}
auto ret = std::make_shared<BufferString>(_str, _offset, size);
_offset += size;
return ret;
}
//////////////////////////////////////////////////////////////////
static mutex s_mtx;
static unordered_map<string /*file_path*/, std::tuple<char */*ptr*/, int64_t /*size*/, weak_ptr<char> /*mmap*/ > > s_shared_mmap;
#if defined(_WIN32)
static void mmap_close(HANDLE _hfile, HANDLE _hmapping, void *_addr) {
if (_addr) {
::UnmapViewOfFile(_addr);
}
if (_hmapping) {
::CloseHandle(_hmapping);
}
if (_hfile != INVALID_HANDLE_VALUE) {
::CloseHandle(_hfile);
}
}
#endif
// 删除mmap记录 [AUTO-TRANSLATED:c956201d]
// Delete mmap record
static void delSharedMmap(const string &file_path, char *ptr) {
lock_guard<mutex> lck(s_mtx);
auto it = s_shared_mmap.find(file_path);
if (it != s_shared_mmap.end() && std::get<0>(it->second) == ptr) {
s_shared_mmap.erase(it);
}
}
static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &file_size) {
{
lock_guard<mutex> lck(s_mtx);
auto it = s_shared_mmap.find(file_path);
if (it != s_shared_mmap.end()) {
auto ret = std::get<2>(it->second).lock();
if (ret) {
// 命中mmap缓存 [AUTO-TRANSLATED:95131a66]
// Hit mmap cache
file_size = std::get<1>(it->second);
return ret;
}
}
}
// 打开文件 [AUTO-TRANSLATED:55bfe68a]
// Open file
std::shared_ptr<FILE> fp(fopen(file_path.data(), "rb"), [](FILE *fp) {
if (fp) {
fclose(fp);
}
});
if (!fp) {
// 文件不存在 [AUTO-TRANSLATED:ed160bcf]
// File does not exist
file_size = -1;
return nullptr;
}
#if defined(_WIN32)
auto fd = _fileno(fp.get());
#else
// 获取文件大小 [AUTO-TRANSLATED:82974eea]
// Get file size
file_size = File::fileSize(fp.get());
auto fd = fileno(fp.get());
#endif
if (fd < 0) {
WarnL << "fileno failed:" << get_uv_errmsg(false);
return nullptr;
}
#ifndef _WIN32
auto ptr = (char *)mmap(NULL, file_size, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
WarnL << "mmap " << file_path << " failed:" << get_uv_errmsg(false);
return nullptr;
}
std::shared_ptr<char> ret(ptr, [file_size, fp, file_path](char *ptr) {
munmap(ptr, file_size);
delSharedMmap(file_path, ptr);
});
#else
auto hfile = ::CreateFileA(file_path.data(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE) {
WarnL << "CreateFileA() " << file_path << " failed:";
return nullptr;
}
LARGE_INTEGER FileSize;
GetFileSizeEx(hfile, &FileSize); //GetFileSize函数的拓展可用于获取大于4G的文件大小
file_size = FileSize.QuadPart;
auto hmapping = ::CreateFileMapping(hfile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hmapping == NULL) {
mmap_close(hfile, NULL, NULL);
WarnL << "CreateFileMapping() " << file_path << " failed:";
return nullptr;
}
auto addr_ = ::MapViewOfFile(hmapping, FILE_MAP_READ, 0, 0, 0);
if (addr_ == nullptr) {
mmap_close(hfile, hmapping, addr_);
WarnL << "MapViewOfFile() " << file_path << " failed:";
return nullptr;
}
std::shared_ptr<char> ret((char *)(addr_), [hfile, hmapping, file_path](char *addr_) {
mmap_close(hfile, hmapping, addr_);
delSharedMmap(file_path, addr_);
});
#endif
#if 0
if (file_size < 10 * 1024 * 1024 && file_path.rfind(".ts") != string::npos) {
// 如果是小ts文件那么尝试先加载到内存 [AUTO-TRANSLATED:0d96c5cd]
// If it is a small ts file, try to load it into memory first
auto buf = BufferRaw::create();
buf->assign(ret.get(), file_size);
ret.reset(buf->data(), [buf, file_path](char *ptr) {
delSharedMmap(file_path, ptr);
});
}
#endif
{
lock_guard<mutex> lck(s_mtx);
s_shared_mmap[file_path] = std::make_tuple(ret.get(), file_size, ret);
}
return ret;
}
HttpFileBody::HttpFileBody(const string &file_path, bool use_mmap) {
if (use_mmap ) {
_map_addr = getSharedMmap(file_path, _read_to);
}
if (!_map_addr && _read_to != -1) {
// mmap失败(且不是由于文件不存在导致的)或未执行mmap时才进入fread逻辑分支 [AUTO-TRANSLATED:8c7efed5]
// Only enter the fread logic branch when mmap fails (and is not due to file not existing) or when mmap is not executed
_fp.reset(fopen(file_path.data(), "rb"), [](FILE *fp) {
if (fp) {
fclose(fp);
}
});
if (!_fp) {
// 文件不存在 [AUTO-TRANSLATED:ed160bcf]
// File does not exist
_read_to = -1;
return;
}
if (!_read_to) {
// _read_to等于0时说明还未尝试获取文件大小 [AUTO-TRANSLATED:4e3ef6ca]
// When _read_to equals 0, it means that the file size has not been attempted to be obtained yet
// 加上该判断逻辑在mmap失败时可以省去一次该操作 [AUTO-TRANSLATED:b9b585de]
// Adding this judgment logic can save one operation when mmap fails
_read_to = File::fileSize(_fp.get());
}
}
}
void HttpFileBody::setRange(uint64_t offset, uint64_t max_size) {
CHECK((int64_t)offset <= _read_to && (int64_t)(max_size + offset) <= _read_to);
_read_to = max_size + offset;
_file_offset = offset;
if (_fp && !_map_addr) {
fseek64(_fp.get(), _file_offset, SEEK_SET);
}
}
int HttpFileBody::sendFile(int fd) {
#if defined(__linux__) || defined(__linux)
if (!_fp) {
return -1;
}
static onceToken s_token([]() { signal(SIGPIPE, SIG_IGN); });
off_t off = _file_offset;
return sendfile(fd, fileno(_fp.get()), &off, _read_to - _file_offset);
#else
return -1;
#endif
}
class BufferMmap : public Buffer {
public:
using Ptr = std::shared_ptr<BufferMmap>;
BufferMmap(const std::shared_ptr<char> &map_addr, size_t offset, size_t size) {
_map_addr = map_addr;
_data = map_addr.get() + offset;
_size = size;
}
// 返回数据长度 [AUTO-TRANSLATED:955f731c]
// Return data length
char *data() const override { return _data; }
size_t size() const override { return _size; }
private:
char *_data;
size_t _size;
std::shared_ptr<char> _map_addr;
};
int64_t HttpFileBody::remainSize() {
return _read_to - _file_offset;
}
Buffer::Ptr HttpFileBody::readData(size_t size) {
size = (size_t)(MIN(remainSize(), (int64_t)size));
if (!size) {
// 没有剩余字节了 [AUTO-TRANSLATED:7bbaa343]
// No remaining bytes
return nullptr;
}
if (!_map_addr) {
// fread模式 [AUTO-TRANSLATED:c4dee2a3]
// fread mode
ssize_t iRead;
auto ret = _pool.obtain2();
ret->setCapacity(size + 1);
do {
iRead = fread(ret->data(), 1, size, _fp.get());
} while (-1 == iRead && UV_EINTR == get_uv_error(false));
if (iRead > 0) {
// 读到数据了 [AUTO-TRANSLATED:7e5ada62]
// Data is read
ret->setSize(iRead);
_file_offset += iRead;
return std::move(ret);
}
// 读取文件异常,文件真实长度小于声明长度 [AUTO-TRANSLATED:89d09f9b]
// File reading exception, the actual length of the file is less than the declared length
_file_offset = _read_to;
WarnL << "read file err:" << get_uv_errmsg();
return nullptr;
}
// mmap模式 [AUTO-TRANSLATED:b8d616f1]
// mmap mode
auto ret = std::make_shared<BufferMmap>(_map_addr, _file_offset, size);
_file_offset += size;
return ret;
}
//////////////////////////////////////////////////////////////////
HttpMultiFormBody::HttpMultiFormBody(const HttpArgs &args, const string &filePath, const string &boundary) {
_fileBody = std::make_shared<HttpFileBody>(filePath);
if (_fileBody->remainSize() < 0) {
throw std::invalid_argument(StrPrinter << "open file failed" << filePath << " " << get_uv_errmsg());
}
auto fileName = filePath;
auto pos = filePath.rfind('/');
if (pos != string::npos) {
fileName = filePath.substr(pos + 1);
}
_bodyPrefix = multiFormBodyPrefix(args, boundary, fileName);
_bodySuffix = multiFormBodySuffix(boundary);
_totalSize = _bodyPrefix.size() + _bodySuffix.size() + _fileBody->remainSize();
}
int64_t HttpMultiFormBody::remainSize() {
return _totalSize - _offset;
}
Buffer::Ptr HttpMultiFormBody::readData(size_t size) {
if (_bodyPrefix.size()) {
auto ret = std::make_shared<BufferString>(_bodyPrefix);
_offset += _bodyPrefix.size();
_bodyPrefix.clear();
return ret;
}
if (_fileBody->remainSize()) {
auto ret = _fileBody->readData(size);
if (!ret) {
// 读取文件出现异常,提前中断 [AUTO-TRANSLATED:5b8052d9]
// An exception occurred while reading the file, and the process was interrupted prematurely
_offset = _totalSize;
} else {
_offset += ret->size();
}
return ret;
}
if (_bodySuffix.size()) {
auto ret = std::make_shared<BufferString>(_bodySuffix);
_offset = _totalSize;
_bodySuffix.clear();
return ret;
}
return nullptr;
}
string HttpMultiFormBody::multiFormBodySuffix(const string &boundary) {
return "\r\n--" + boundary + "--";
}
string HttpMultiFormBody::multiFormContentType(const string &boundary) {
return StrPrinter << "multipart/form-data; boundary=" << boundary;
}
string HttpMultiFormBody::multiFormBodyPrefix(const HttpArgs &args, const string &boundary, const string &fileName) {
string MPboundary = string("--") + boundary;
_StrPrinter body;
for (auto &pr : args) {
body << MPboundary << "\r\n";
body << "Content-Disposition: form-data; name=\"" << pr.first << "\"\r\n\r\n";
body << pr.second << "\r\n";
}
body << MPboundary << "\r\n";
body << "Content-Disposition: form-data; name=\""
<< "file"
<< "\"; filename=\"" << fileName << "\"\r\n";
body << "Content-Type: application/octet-stream\r\n\r\n";
return std::move(body);
}
HttpBufferBody::HttpBufferBody(Buffer::Ptr buffer) {
_buffer = std::move(buffer);
}
int64_t HttpBufferBody::remainSize() {
return _buffer ? _buffer->size() : 0;
}
Buffer::Ptr HttpBufferBody::readData(size_t size) {
return Buffer::Ptr(std::move(_buffer));
}
} // namespace mediakit

220
MediaServer/Http/HttpBody.h Normal file
View File

@ -0,0 +1,220 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_FILEREADER_H
#define ZLMEDIAKIT_FILEREADER_H
#include <stdlib.h>
#include <memory>
#include "Network/Buffer.h"
#include "Util/ResourcePool.h"
#include "Util/logger.h"
#include "Thread/WorkThreadPool.h"
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b) )
#endif //MIN
namespace mediakit {
/**
* http content部分基类定义
* Base class definition for http content part
* [AUTO-TRANSLATED:1eee419a]
*/
class HttpBody : public std::enable_shared_from_this<HttpBody>{
public:
using Ptr = std::shared_ptr<HttpBody>;
virtual ~HttpBody() = default;
/**
* -1, content-length
* Remaining data size, if -1 is returned, then content-length is not set
* [AUTO-TRANSLATED:75375ce7]
*/
virtual int64_t remainSize() { return 0;};
/**
* size
* @param size
* @return ,nullptr
* Read a certain number of bytes, the returned size may be less than size
* @param size Request size
* @return Byte object, if it is read, please return nullptr
* [AUTO-TRANSLATED:6fd85f91]
*/
virtual toolkit::Buffer::Ptr readData(size_t size) { return nullptr;};
/**
* size
* @param size
* @param cb
* Asynchronously request to read a certain number of bytes, the returned size may be less than size
* @param size Request size
* @param cb Callback function
* [AUTO-TRANSLATED:a5304046]
*/
virtual void readDataAsync(size_t size,const std::function<void(const toolkit::Buffer::Ptr &buf)> &cb){
// 由于unix和linux是通过mmap的方式读取文件所以把读文件操作放在后台线程并不能提高性能 [AUTO-TRANSLATED:59ef443d]
// Since unix and linux read files through mmap, putting file reading operations in the background thread does not improve performance
// 反而会由于频繁的线程切换导致性能降低以及延时增加,所以我们默认同步获取文件内容 [AUTO-TRANSLATED:93d2a0b5]
// On the contrary, frequent thread switching will lead to performance degradation and increased latency, so we get the file content synchronously by default
// (其实并没有读,拷贝文件数据时在内核态完成文件读) [AUTO-TRANSLATED:6eb98a5d]
// (Actually, there is no reading, the file data is copied in the kernel state when copying)
cb(readData(size));
}
/**
* 使sendfile优化文件发送
* @param fd socket fd
* @return 0
* Use sendfile to optimize file sending
* @param fd socket fd
* @return 0 success, other error codes
* [AUTO-TRANSLATED:eacc5f98]
*/
virtual int sendFile(int fd) {
return -1;
}
};
/**
* std::string类型的content
* std::string type content
* [AUTO-TRANSLATED:59fc3e5b]
*/
class HttpStringBody : public HttpBody{
public:
using Ptr = std::shared_ptr<HttpStringBody>;
HttpStringBody(std::string str);
int64_t remainSize() override;
toolkit::Buffer::Ptr readData(size_t size) override ;
private:
size_t _offset = 0;
mutable std::string _str;
};
/**
* Buffer类型的content
* Buffer type content
* [AUTO-TRANSLATED:350b9513]
*/
class HttpBufferBody : public HttpBody{
public:
using Ptr = std::shared_ptr<HttpBufferBody>;
HttpBufferBody(toolkit::Buffer::Ptr buffer);
int64_t remainSize() override;
toolkit::Buffer::Ptr readData(size_t size) override;
private:
toolkit::Buffer::Ptr _buffer;
};
/**
* content
* File type content
* [AUTO-TRANSLATED:baf9c0f3]
*/
class HttpFileBody : public HttpBody {
public:
using Ptr = std::shared_ptr<HttpFileBody>;
/**
*
* @param file_path
* @param use_mmap 使mmap方式访问文件
* Constructor
* @param file_path File path
* @param use_mmap Whether to use mmap to access the file
* [AUTO-TRANSLATED:40c85c53]
*/
HttpFileBody(const std::string &file_path, bool use_mmap = true);
/**
*
* @param offset
* @param max_size
* Set the reading range
* @param offset Offset relative to the file header
* @param max_size Maximum number of bytes to read
* [AUTO-TRANSLATED:30532a4e]
*/
void setRange(uint64_t offset, uint64_t max_size);
int64_t remainSize() override;
toolkit::Buffer::Ptr readData(size_t size) override;
int sendFile(int fd) override;
private:
int64_t _read_to = 0;
uint64_t _file_offset = 0;
std::shared_ptr<FILE> _fp;
std::shared_ptr<char> _map_addr;
toolkit::ResourcePool<toolkit::BufferRaw> _pool;
};
class HttpArgs;
/**
* http MultiForm http content
* http MultiForm way to submit http content
* [AUTO-TRANSLATED:211a2d8e]
*/
class HttpMultiFormBody : public HttpBody {
public:
using Ptr = std::shared_ptr<HttpMultiFormBody>;
/**
*
* @param args http提交参数列表
* @param filePath
* @param boundary boundary字符串
* Constructor
* @param args http submission parameter list
* @param filePath File path
* @param boundary Boundary string
* [AUTO-TRANSLATED:d093cfa7]
*/
HttpMultiFormBody(const HttpArgs &args,const std::string &filePath,const std::string &boundary = "0xKhTmLbOuNdArY");
int64_t remainSize() override ;
toolkit::Buffer::Ptr readData(size_t size) override;
public:
static std::string multiFormBodyPrefix(const HttpArgs &args,const std::string &boundary,const std::string &fileName);
static std::string multiFormBodySuffix(const std::string &boundary);
static std::string multiFormContentType(const std::string &boundary);
private:
uint64_t _offset = 0;
int64_t _totalSize;
std::string _bodyPrefix;
std::string _bodySuffix;
HttpFileBody::Ptr _fileBody;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_FILEREADER_H

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <string.h>
#include "Common/macros.h"
#include "HttpChunkedSplitter.h"
using namespace std;
//[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]
namespace mediakit{
const char *HttpChunkedSplitter::onSearchPacketTail(const char *data, size_t len) {
auto pos = strstr(data, "\r\n");
if (!pos) {
return nullptr;
}
return pos + 2;
}
void HttpChunkedSplitter::onRecvContent(const char *data, size_t len) {
onRecvChunk(data, len - 2);
}
ssize_t HttpChunkedSplitter::onRecvHeader(const char *data, size_t len) {
int size;
CHECK(sscanf(data, "%X", &size) == 1 && size >= 0);
// 包括后面\r\n两个字节 [AUTO-TRANSLATED:f5567007]
// Including the following two bytes \r\n
return size + 2;
}
}//namespace mediakit

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_HTTPCHUNKEDSPLITTER_H
#define ZLMEDIAKIT_HTTPCHUNKEDSPLITTER_H
#include <functional>
#include "HttpRequestSplitter.h"
namespace mediakit{
class HttpChunkedSplitter : public HttpRequestSplitter {
public:
/**
* len == 0
* When len == 0, it represents the end.
* [AUTO-TRANSLATED:1607d203]
*/
using onChunkData = std::function<void(const char *data, size_t len)>;
HttpChunkedSplitter(const onChunkData &cb) { _onChunkData = cb; };
~HttpChunkedSplitter() override { _onChunkData = nullptr; };
protected:
ssize_t onRecvHeader(const char *data,size_t len) override;
void onRecvContent(const char *data,size_t len) override;
const char *onSearchPacketTail(const char *data,size_t len) override;
protected:
virtual void onRecvChunk(const char *data,size_t len){
if(_onChunkData){
_onChunkData(data,len);
}
};
private:
onChunkData _onChunkData;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_HTTPCHUNKEDSPLITTER_H

View File

@ -0,0 +1,493 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <cstdlib>
#include "Util/base64.h"
#include "HttpClient.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
void HttpClient::sendRequest(const string &url) {
clearResponse();
_url = url;
auto protocol = findSubString(url.data(), NULL, "://");
uint16_t port;
bool is_https;
if (strcasecmp(protocol.data(), "http") == 0) {
port = 80;
is_https = false;
} else if (strcasecmp(protocol.data(), "https") == 0) {
port = 443;
is_https = true;
} else {
auto strErr = StrPrinter << "非法的http url:" << url << endl;
throw std::invalid_argument(strErr);
}
auto host = findSubString(url.data(), "://", "/");
if (host.empty()) {
host = findSubString(url.data(), "://", NULL);
}
_path = findSubString(url.data(), host.data(), NULL);
if (_path.empty()) {
_path = "/";
}
// 重新设置header防止上次请求的header干扰 [AUTO-TRANSLATED:d8d06841]
// Reset the header to prevent interference from the previous request's header
_header = _user_set_header;
auto pos = host.find('@');
if (pos != string::npos) {
// 去除?后面的字符串 [AUTO-TRANSLATED:0ccb41c2]
// Remove the string after the "?"
auto authStr = host.substr(0, pos);
host = host.substr(pos + 1, host.size());
_header.emplace("Authorization", "Basic " + encodeBase64(authStr));
}
auto host_header = host;
splitUrl(host, host, port);
_header.emplace("Host", host_header);
_header.emplace("User-Agent", kServerName);
_header.emplace("Accept", "*/*");
_header.emplace("Accept-Language", "zh-CN,zh;q=0.8");
if (_http_persistent) {
_header.emplace("Connection", "keep-alive");
} else {
_header.emplace("Connection", "close");
}
_http_persistent = true;
if (_body && _body->remainSize()) {
_header.emplace("Content-Length", to_string(_body->remainSize()));
GET_CONFIG(string, charSet, Http::kCharSet);
_header.emplace("Content-Type", "application/x-www-form-urlencoded; charset=" + charSet);
}
bool host_changed = (_last_host != host + ":" + to_string(port)) || (_is_https != is_https);
_last_host = host + ":" + to_string(port);
_is_https = is_https;
auto cookies = HttpCookieStorage::Instance().get(_last_host, _path);
_StrPrinter printer;
for (auto &cookie : cookies) {
printer << cookie->getKey() << "=" << cookie->getVal() << ";";
}
if (!printer.empty()) {
printer.pop_back();
_header.emplace("Cookie", printer);
}
if (!alive() || host_changed || !_http_persistent) {
if (isUsedProxy()) {
_proxy_connected = false;
startConnect(_proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
} else {
startConnect(host, port, _wait_header_ms / 1000.0f);
}
} else {
SockException ex;
onConnect_l(ex);
}
}
void HttpClient::clear() {
_url.clear();
_user_set_header.clear();
_body.reset();
_method.clear();
clearResponse();
}
void HttpClient::clearResponse() {
_complete = false;
_header_recved = false;
_recved_body_size = 0;
_total_body_size = 0;
_parser.clear();
_chunked_splitter = nullptr;
_wait_header.resetTime();
_wait_body.resetTime();
_wait_complete.resetTime();
HttpRequestSplitter::reset();
}
void HttpClient::setMethod(string method) {
_method = std::move(method);
}
void HttpClient::setHeader(HttpHeader header) {
_user_set_header = std::move(header);
}
HttpClient &HttpClient::addHeader(string key, string val, bool force) {
if (!force) {
_user_set_header.emplace(std::move(key), std::move(val));
} else {
_user_set_header[std::move(key)] = std::move(val);
}
return *this;
}
void HttpClient::setBody(string body) {
_body.reset(new HttpStringBody(std::move(body)));
}
void HttpClient::setBody(HttpBody::Ptr body) {
_body = std::move(body);
}
const Parser &HttpClient::response() const {
return _parser;
}
ssize_t HttpClient::responseBodyTotalSize() const {
return _total_body_size;
}
size_t HttpClient::responseBodySize() const {
return _recved_body_size;
}
const string &HttpClient::getUrl() const {
return _url;
}
void HttpClient::onConnect(const SockException &ex) {
onConnect_l(ex);
}
void HttpClient::onConnect_l(const SockException &ex) {
if (ex) {
onResponseCompleted_l(ex);
return;
}
_StrPrinter printer;
// 不使用代理或者代理服务器已经连接成功 [AUTO-TRANSLATED:e051567c]
// No proxy is used or the proxy server has connected successfully
if (_proxy_connected || !isUsedProxy()) {
printer << _method + " " << _path + " HTTP/1.1\r\n";
for (auto &pr : _header) {
printer << pr.first + ": ";
printer << pr.second + "\r\n";
}
_header.clear();
_path.clear();
} else {
printer << "CONNECT " << _last_host << " HTTP/1.1\r\n";
printer << "Proxy-Connection: keep-alive\r\n";
if (!_proxy_auth.empty()) {
printer << "Proxy-Authorization: Basic " << _proxy_auth << "\r\n";
}
}
SockSender::send(printer << "\r\n");
onFlush();
}
void HttpClient::onRecv(const Buffer::Ptr &pBuf) {
_wait_body.resetTime();
HttpRequestSplitter::input(pBuf->data(), pBuf->size());
}
void HttpClient::onError(const SockException &ex) {
if (ex.getErrCode() == Err_reset && _allow_resend_request && _http_persistent && _recved_body_size == 0 && !_header_recved) {
// 连接被重置,可能是服务器主动断开了连接, 或者服务器内核参数或防火墙的持久连接空闲时间超时或不一致. [AUTO-TRANSLATED:8a78f452]
// The connection was reset, possibly because the server actively closed the connection, or the server kernel parameters or firewall's persistent connection idle timeout or inconsistency.
// 如果是持久化连接,那么我们可以通过重连来解决这个问题 [AUTO-TRANSLATED:6c113e17]
// If it is a persistent connection, we can solve this problem by reconnecting
// The connection was reset, possibly because the server actively disconnected the connection,
// or the persistent connection idle time of the server kernel parameters or firewall timed out or inconsistent.
// If it is a persistent connection, then we can solve this problem by reconnecting
WarnL << "http persistent connect reset, try reconnect";
_http_persistent = false;
sendRequest(_url);
return;
}
onResponseCompleted_l(ex);
}
ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
_parser.parse(data, len);
if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") {
auto new_url = Parser::mergeUrl(_url, _parser["Location"]);
if (new_url.empty()) {
throw invalid_argument("未找到Location字段(跳转url)");
}
if (onRedirectUrl(new_url, _parser.status() == "302")) {
HttpClient::sendRequest(new_url);
return 0;
}
}
checkCookie(_parser.getHeader());
onResponseHeader(_parser.status(), _parser.getHeader());
_header_recved = true;
if (_parser["Transfer-Encoding"] == "chunked") {
// 如果Transfer-Encoding字段等于chunked则认为后续的content是不限制长度的 [AUTO-TRANSLATED:ebbcb35c]
// If the Transfer-Encoding field is equal to chunked, it is considered that the subsequent content is unlimited in length
_total_body_size = -1;
_chunked_splitter = std::make_shared<HttpChunkedSplitter>([this](const char *data, size_t len) {
if (len > 0) {
_recved_body_size += len;
onResponseBody(data, len);
} else {
_total_body_size = _recved_body_size;
if (_recved_body_size > 0) {
onResponseCompleted_l(SockException(Err_success, "success"));
} else {
onResponseCompleted_l(SockException(Err_other, "no body"));
}
}
});
// 后续为源源不断的body [AUTO-TRANSLATED:bf551bbd]
// The following is a continuous body
return -1;
}
if (!_parser["Content-Length"].empty()) {
// 有Content-Length字段时忽略onResponseHeader的返回值 [AUTO-TRANSLATED:50380ba8]
// Ignore the return value of onResponseHeader when there is a Content-Length field
_total_body_size = atoll(_parser["Content-Length"].data());
} else {
_total_body_size = -1;
}
if (_total_body_size == 0) {
// 后续没content本次http请求结束 [AUTO-TRANSLATED:8532172f]
// There is no content afterwards, this http request ends
onResponseCompleted_l(SockException(Err_success, "The request is successful but has no body"));
return 0;
}
// 当_total_body_size != 0时到达这里代表后续有content [AUTO-TRANSLATED:3a55b268]
// When _total_body_size != 0, it means there is content afterwards
// 虽然我们在_total_body_size >0 时知道content的确切大小 [AUTO-TRANSLATED:af91f74f]
// Although we know the exact size of the content when _total_body_size > 0,
// 但是由于我们没必要等content接收完毕才回调onRecvContent(因为这样浪费内存并且要多次拷贝数据) [AUTO-TRANSLATED:fd71692c]
// But because we don't need to wait for the content to be received before calling onRecvContent (because this wastes memory and requires multiple data copies)
// 所以返回-1代表我们接下来分段接收content [AUTO-TRANSLATED:388756f6]
// So returning -1 means we will receive the content in segments next
_recved_body_size = 0;
return -1;
}
void HttpClient::onRecvContent(const char *data, size_t len) {
if (_chunked_splitter) {
_chunked_splitter->input(data, len);
return;
}
_recved_body_size += len;
if (_total_body_size < 0) {
// 不限长度的content [AUTO-TRANSLATED:325a9dbc]
// Unlimited length content
onResponseBody(data, len);
return;
}
// 固定长度的content [AUTO-TRANSLATED:4d169746]
// Fixed length content
if (_recved_body_size < (size_t) _total_body_size) {
// content还未接收完毕 [AUTO-TRANSLATED:b30ca92c]
// Content has not been received yet
onResponseBody(data, len);
return;
}
if (_recved_body_size == (size_t)_total_body_size) {
// content接收完毕 [AUTO-TRANSLATED:e730ea8c]
// Content received
onResponseBody(data, len);
onResponseCompleted_l(SockException(Err_success, "completed"));
return;
}
// 声明的content数据比真实的小断开链接 [AUTO-TRANSLATED:38204302]
// The declared content data is smaller than the real one, disconnect
onResponseBody(data, len);
throw invalid_argument("http response content size bigger than expected");
}
void HttpClient::onFlush() {
GET_CONFIG(uint32_t, send_buf_size, Http::kSendBufSize);
while (_body && _body->remainSize() && !isSocketBusy()) {
auto buffer = _body->readData(send_buf_size);
if (!buffer) {
// 数据发送结束或读取数据异常 [AUTO-TRANSLATED:75179972]
// Data transmission ends or data reading exception
break;
}
if (send(buffer) <= 0) {
// 发送数据失败不需要回滚数据因为发送前已经通过isSocketBusy()判断socket可写 [AUTO-TRANSLATED:30762202]
// Data transmission failed, no need to roll back data, because the socket is writable before sending
// 所以发送缓存区肯定未满,该buffer肯定已经写入socket [AUTO-TRANSLATED:769fff52]
// So the send buffer is definitely not full, this buffer must have been written to the socket
break;
}
}
}
void HttpClient::onManager() {
// onManager回调在连接中或已连接状态才会调用 [AUTO-TRANSLATED:acf86dce]
// The onManager callback is only called when the connection is in progress or connected
if (_wait_complete_ms > 0) {
// 设置了总超时时间 [AUTO-TRANSLATED:ac47c234]
// Total timeout is set
if (!_complete && _wait_complete.elapsedTime() > _wait_complete_ms) {
// 等待http回复完毕超时 [AUTO-TRANSLATED:711ebc7b]
// Timeout waiting for http reply to finish
shutdown(SockException(Err_timeout, "wait http response complete timeout"));
return;
}
return;
}
// 未设置总超时时间 [AUTO-TRANSLATED:a936338f]
// Total timeout is not set
if (!_header_recved) {
// 等待header中 [AUTO-TRANSLATED:f8635de6]
// Waiting for header
if (_wait_header.elapsedTime() > _wait_header_ms) {
// 等待header中超时 [AUTO-TRANSLATED:860d3a16]
// Timeout waiting for header
shutdown(SockException(Err_timeout, "wait http response header timeout"));
return;
}
} else if (_wait_body_ms > 0 && _wait_body.elapsedTime() > _wait_body_ms) {
// 等待body中等待超时 [AUTO-TRANSLATED:f9bb1d66]
// Waiting for body, timeout
shutdown(SockException(Err_timeout, "wait http response body timeout"));
return;
}
}
void HttpClient::onResponseCompleted_l(const SockException &ex) {
if (_complete) {
return;
}
_complete = true;
_wait_complete.resetTime();
if (!ex) {
// 确认无疑的成功 [AUTO-TRANSLATED:e1db8ce2]
// Confirmed success
onResponseCompleted(ex);
return;
}
// 可疑的失败 [AUTO-TRANSLATED:1258a436]
// Suspicious failure
if (_total_body_size > 0 && _recved_body_size >= (size_t)_total_body_size) {
// 回复header中有content-length信息那么收到的body大于等于声明值则认为成功 [AUTO-TRANSLATED:2f813650]
// If the response header contains content-length information, then the received body is considered successful if it is greater than or equal to the declared value
onResponseCompleted(SockException(Err_success, "read body completed"));
return;
}
if (_total_body_size == -1 && _recved_body_size > 0) {
// 回复header中无content-length信息那么收到一点body也认为成功 [AUTO-TRANSLATED:6c0e87fc]
// If the response header does not contain content-length information, then receiving any body is considered successful
onResponseCompleted(SockException(Err_success, ex.what()));
return;
}
// 确认无疑的失败 [AUTO-TRANSLATED:33b216d9]
// Confirmed failure
onResponseCompleted(ex);
}
bool HttpClient::waitResponse() const {
return !_complete && alive();
}
bool HttpClient::isHttps() const {
return _is_https;
}
void HttpClient::checkCookie(HttpClient::HttpHeader &headers) {
//Set-Cookie: IPTV_SERVER=8E03927B-CC8C-4389-BC00-31DBA7EC7B49;expires=Sun, Sep 23 2018 15:07:31 GMT;path=/index/api/
for (auto it_set_cookie = headers.find("Set-Cookie"); it_set_cookie != headers.end(); ++it_set_cookie) {
auto key_val = Parser::parseArgs(it_set_cookie->second, ";", "=");
HttpCookie::Ptr cookie = std::make_shared<HttpCookie>();
cookie->setHost(_last_host);
int index = 0;
auto arg_vec = split(it_set_cookie->second, ";");
for (string &key_val : arg_vec) {
auto key = findSubString(key_val.data(), NULL, "=");
auto val = findSubString(key_val.data(), "=", NULL);
if (index++ == 0) {
cookie->setKeyVal(key, val);
continue;
}
if (key == "path") {
cookie->setPath(val);
continue;
}
if (key == "expires") {
cookie->setExpires(val, headers["Date"]);
continue;
}
}
if (!(*cookie)) {
// 无效的cookie [AUTO-TRANSLATED:5f06aec8]
// Invalid cookie
continue;
}
HttpCookieStorage::Instance().set(cookie);
}
}
void HttpClient::setHeaderTimeout(size_t timeout_ms) {
CHECK(timeout_ms > 0);
_wait_header_ms = timeout_ms;
}
void HttpClient::setBodyTimeout(size_t timeout_ms) {
_wait_body_ms = timeout_ms;
}
void HttpClient::setCompleteTimeout(size_t timeout_ms) {
_wait_complete_ms = timeout_ms;
}
bool HttpClient::isUsedProxy() const {
return _used_proxy;
}
bool HttpClient::isProxyConnected() const {
return _proxy_connected;
}
void HttpClient::setProxyUrl(string proxy_url) {
_proxy_url = std::move(proxy_url);
if (!_proxy_url.empty()) {
parseProxyUrl(_proxy_url, _proxy_host, _proxy_port, _proxy_auth);
_used_proxy = true;
} else {
_used_proxy = false;
}
}
bool HttpClient::checkProxyConnected(const char *data, size_t len) {
auto ret = strstr(data, "HTTP/1.1 200 Connection established");
_proxy_connected = ret != nullptr;
return _proxy_connected;
}
void HttpClient::setAllowResendRequest(bool allow) {
_allow_resend_request = allow;
}
} /* namespace mediakit */

View File

@ -0,0 +1,321 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef Http_HttpClient_h
#define Http_HttpClient_h
#include <stdio.h>
#include <string.h>
#include <functional>
#include <memory>
#include "Util/util.h"
#include "Util/mini.h"
#include "Network/TcpClient.h"
#include "Common/Parser.h"
#include "HttpRequestSplitter.h"
#include "HttpCookie.h"
#include "HttpChunkedSplitter.h"
#include "Common/strCoding.h"
#include "HttpBody.h"
namespace mediakit {
class HttpArgs : public std::map<std::string, toolkit::variant, StrCaseCompare> {
public:
std::string make() const {
std::string ret;
for (auto &pr : *this) {
ret.append(pr.first);
ret.append("=");
ret.append(strCoding::UrlEncodeComponent(pr.second));
ret.append("&");
}
if (ret.size()) {
ret.pop_back();
}
return ret;
}
};
class HttpClient : public toolkit::TcpClient, public HttpRequestSplitter {
public:
using HttpHeader = StrCaseMap;
using Ptr = std::shared_ptr<HttpClient>;
/**
* http[s]
* @param url url
* Send http[s] request
* @param url Request url
* [AUTO-TRANSLATED:01b6c9ac]
*/
virtual void sendRequest(const std::string &url);
/**
*
* Reset object
* [AUTO-TRANSLATED:d23b5bbb]
*/
virtual void clear();
/**
* http方法
* @param method GET/POST等
* Set http method
* @param method GET/POST etc.
* [AUTO-TRANSLATED:5199546a]
*/
void setMethod(std::string method);
/**
* http头
* @param header
* Override http header
* @param header
* [AUTO-TRANSLATED:ea31a471]
*/
void setHeader(HttpHeader header);
HttpClient &addHeader(std::string key, std::string val, bool force = false);
/**
* http content
* @param body http content
* Set http content
* @param body http content
* [AUTO-TRANSLATED:9993580c]
*/
void setBody(std::string body);
/**
* http content
* @param body http content
* Set http content
* @param body http content
* [AUTO-TRANSLATED:9993580c]
*/
void setBody(HttpBody::Ptr body);
/**
*
* Get response, valid after receiving the complete response
* [AUTO-TRANSLATED:b107995e]
*/
const Parser &response() const;
/**
* header声明的body大小
* Get the body size declared in the response header
* [AUTO-TRANSLATED:65f8e782]
*/
ssize_t responseBodyTotalSize() const;
/**
* body的大小
* Get the size of the body that has been downloaded
* [AUTO-TRANSLATED:a3cde7b4]
*/
size_t responseBodySize() const;
/**
* url
* Get the request url
* [AUTO-TRANSLATED:cc7fe537]
*/
const std::string &getUrl() const;
/**
*
* Determine if the response is pending
* [AUTO-TRANSLATED:058719d7]
*/
bool waitResponse() const;
/**
* https
* Determine if it is https
* [AUTO-TRANSLATED:9b3a0254]
*/
bool isHttps() const;
/**
* header完毕的延时10
* 0
* Set the delay from initiating the connection to receiving the header, default 10 seconds
* This parameter must be greater than 0
* [AUTO-TRANSLATED:4cce3e85]
*/
void setHeaderTimeout(size_t timeout_ms);
/**
* body数据超时时间, 5
* body回复的超时问题
* 0
* Set the timeout for receiving body data, default 5 seconds
* This parameter can be used to handle timeout issues for large body responses
* This parameter can be equal to 0
* [AUTO-TRANSLATED:48585852]
*/
void setBodyTimeout(size_t timeout_ms);
/**
* , 0
* 0HeaderTimeout和BodyTimeout无效
* Set the timeout for the entire link, default 0
* After this value is set to non-zero, HeaderTimeout and BodyTimeout are invalid
* [AUTO-TRANSLATED:df094868]
*/
void setCompleteTimeout(size_t timeout_ms);
/**
* http代理url
* Set http proxy url
* [AUTO-TRANSLATED:95df17e7]
*/
void setProxyUrl(std::string proxy_url);
/**
* ,
* If the reuse connection fails, whether to allow the request to be resent
* @param allow true: / true: allow the request to be resent
* When the reuse connection fails, whether to allow the request to be resent
* @param allow true: allow the request to be resent
* [AUTO-TRANSLATED:71bd8e67]
*/
void setAllowResendRequest(bool allow);
protected:
/**
* http回复头
* @param status :200 OK
* @param headers http头
* Receive http response header
* @param status Status code, such as: 200 OK
* @param headers http header
* [AUTO-TRANSLATED:a685f8ef]
*/
virtual void onResponseHeader(const std::string &status, const HttpHeader &headers) = 0;
/**
* http conten数据
* @param buf
* @param size
* Receive http content data
* @param buf Data pointer
* @param size Data size
* [AUTO-TRANSLATED:bee3bf62]
*/
virtual void onResponseBody(const char *buf, size_t size) = 0;
/**
* http回复完毕,
* Receive http response complete,
* [AUTO-TRANSLATED:b96ed715]
*/
virtual void onResponseCompleted(const toolkit::SockException &ex) = 0;
/**
*
* @param url url
* @param temporary
* @return
* Redirect event
* @param url Redirect url
* @param temporary Whether it is a temporary redirect
* @return Whether to continue
* [AUTO-TRANSLATED:b64d5f8b]
*/
virtual bool onRedirectUrl(const std::string &url, bool temporary) { return true; };
protected:
//// HttpRequestSplitter override ////
ssize_t onRecvHeader(const char *data, size_t len) override;
void onRecvContent(const char *data, size_t len) override;
//// TcpClient override ////
void onConnect(const toolkit::SockException &ex) override;
void onRecv(const toolkit::Buffer::Ptr &pBuf) override;
void onError(const toolkit::SockException &ex) override;
void onFlush() override;
void onManager() override;
void clearResponse();
bool checkProxyConnected(const char *data, size_t len);
bool isUsedProxy() const;
bool isProxyConnected() const;
private:
void onResponseCompleted_l(const toolkit::SockException &ex);
void onConnect_l(const toolkit::SockException &ex);
void checkCookie(HttpHeader &headers);
private:
//for http response
bool _complete = false;
bool _header_recved = false;
bool _http_persistent = true;
bool _allow_resend_request = false;
size_t _recved_body_size;
ssize_t _total_body_size;
Parser _parser;
std::shared_ptr<HttpChunkedSplitter> _chunked_splitter;
//for request args
bool _is_https;
std::string _url;
HttpHeader _user_set_header;
HttpBody::Ptr _body;
std::string _method;
std::string _last_host;
//for this request
std::string _path;
HttpHeader _header;
//for timeout
size_t _wait_header_ms = 10 * 1000;
size_t _wait_body_ms = 10 * 1000;
size_t _wait_complete_ms = 0;
toolkit::Ticker _wait_header;
toolkit::Ticker _wait_body;
toolkit::Ticker _wait_complete;
bool _used_proxy = false;
bool _proxy_connected = false;
uint16_t _proxy_port;
std::string _proxy_url;
std::string _proxy_host;
std::string _proxy_auth;
};
} /* namespace mediakit */
#endif /* Http_HttpClient_h */

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Http/HttpClientImp.h"
using namespace toolkit;
namespace mediakit {
void HttpClientImp::onConnect(const SockException &ex) {
if (isUsedProxy() && !isProxyConnected()) {
// 连接代理服务器 [AUTO-TRANSLATED:e7a8979a]
// Connect to the proxy server
setDoNotUseSSL();
HttpClient::onConnect(ex);
} else {
if (!isHttps()) {
// https 302跳转 http时需要关闭ssl [AUTO-TRANSLATED:2ba55daf]
// When https 302 redirects to http, ssl needs to be closed
setDoNotUseSSL();
HttpClient::onConnect(ex);
} else {
TcpClientWithSSL<HttpClient>::onConnect(ex);
}
}
}
ssize_t HttpClientImp::onRecvHeader(const char *data, size_t len) {
if (isUsedProxy() && !isProxyConnected()) {
if (checkProxyConnected(data, len)) {
clearResponse();
onConnect(SockException(Err_success, "proxy connected"));
return 0;
}
}
return HttpClient::onRecvHeader(data, len);
}
} /* namespace mediakit */

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_HTTP_HTTPCLIENTIMP_H_
#define SRC_HTTP_HTTPCLIENTIMP_H_
#include "HttpClient.h"
#include "Util/SSLBox.h"
namespace mediakit {
class HttpClientImp : public toolkit::TcpClientWithSSL<HttpClient> {
public:
using Ptr = std::shared_ptr<HttpClientImp>;
protected:
void onConnect(const toolkit::SockException &ex) override;
ssize_t onRecvHeader(const char *data, size_t len) override;
};
} /* namespace mediakit */
#endif /* SRC_HTTP_HTTPCLIENTIMP_H_ */

View File

@ -0,0 +1,219 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <string.h>
#include "HttpConst.h"
#include "Common/Parser.h"
#include "Util/onceToken.h"
using namespace std;
using namespace toolkit;
namespace mediakit{
const char *HttpConst::getHttpStatusMessage(int status) {
switch (status) {
case 100: return "Continue";
case 101: return "Switching Protocol";
case 102: return "Processing";
case 103: return "Early Hints";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 208: return "Already Reported";
case 226: return "IM Used";
case 300: return "Multiple Choice";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 306: return "unused";
case 307: return "Temporary Redirect";
case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Payload Too Large";
case 414: return "URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Range Not Satisfiable";
case 417: return "Expectation Failed";
case 418: return "I'm a teapot";
case 421: return "Misdirected Request";
case 422: return "Unprocessable Entity";
case 423: return "Locked";
case 424: return "Failed Dependency";
case 425: return "Too Early";
case 426: return "Upgrade Required";
case 428: return "Precondition Required";
case 429: return "Too Many Requests";
case 431: return "Request Header Fields Too Large";
case 451: return "Unavailable For Legal Reasons";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
case 506: return "Variant Also Negotiates";
case 507: return "Insufficient Storage";
case 508: return "Loop Detected";
case 510: return "Not Extended";
case 511: return "Network Authentication Required";
default:
case 500: return "Internal Server Error";
}
}
static const char *s_mime_src[][2] = {
{"html", "text/html"},
{"htm", "text/html"},
{"shtml", "text/html"},
{"css", "text/css"},
{"xml", "text/xml"},
{"gif", "image/gif"},
{"jpeg", "image/jpeg"},
{"jpg", "image/jpeg"},
{"js", "application/javascript"},
{"map", "application/javascript" },
{"atom", "application/atom+xml"},
{"rss", "application/rss+xml"},
{"mml", "text/mathml"},
{"txt", "text/plain"},
{"jad", "text/vnd.sun.j2me.app-descriptor"},
{"wml", "text/vnd.wap.wml"},
{"htc", "text/x-component"},
{"png", "image/png"},
{"tif", "image/tiff"},
{"tiff", "image/tiff"},
{"wbmp", "image/vnd.wap.wbmp"},
{"ico", "image/x-icon"},
{"jng", "image/x-jng"},
{"bmp", "image/x-ms-bmp"},
{"svg", "image/svg+xml"},
{"svgz", "image/svg+xml"},
{"webp", "image/webp"},
{"woff", "application/font-woff"},
{"woff2","application/font-woff" },
{"jar", "application/java-archive"},
{"war", "application/java-archive"},
{"ear", "application/java-archive"},
{"json", "application/json"},
{"hqx", "application/mac-binhex40"},
{"doc", "application/msword"},
{"pdf", "application/pdf"},
{"ps", "application/postscript"},
{"eps", "application/postscript"},
{"ai", "application/postscript"},
{"rtf", "application/rtf"},
{"m3u8", "application/vnd.apple.mpegurl"},
{"xls", "application/vnd.ms-excel"},
{"eot", "application/vnd.ms-fontobject"},
{"ppt", "application/vnd.ms-powerpoint"},
{"wmlc", "application/vnd.wap.wmlc"},
{"kml", "application/vnd.google-earth.kml+xml"},
{"kmz", "application/vnd.google-earth.kmz"},
{"7z", "application/x-7z-compressed"},
{"cco", "application/x-cocoa"},
{"jardiff", "application/x-java-archive-diff"},
{"jnlp", "application/x-java-jnlp-file"},
{"run", "application/x-makeself"},
{"pl", "application/x-perl"},
{"pm", "application/x-perl"},
{"prc", "application/x-pilot"},
{"pdb", "application/x-pilot"},
{"rar", "application/x-rar-compressed"},
{"rpm", "application/x-redhat-package-manager"},
{"sea", "application/x-sea"},
{"swf", "application/x-shockwave-flash"},
{"sit", "application/x-stuffit"},
{"tcl", "application/x-tcl"},
{"tk", "application/x-tcl"},
{"der", "application/x-x509-ca-cert"},
{"pem", "application/x-x509-ca-cert"},
{"crt", "application/x-x509-ca-cert"},
{"xpi", "application/x-xpinstall"},
{"xhtml", "application/xhtml+xml"},
{"xspf", "application/xspf+xml"},
{"zip", "application/zip"},
{"bin", "application/octet-stream"},
{"exe", "application/octet-stream"},
{"dll", "application/octet-stream"},
{"deb", "application/octet-stream"},
{"dmg", "application/octet-stream"},
{"iso", "application/octet-stream"},
{"img", "application/octet-stream"},
{"msi", "application/octet-stream"},
{"msp", "application/octet-stream"},
{"msm", "application/octet-stream"},
{"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{"mid", "audio/midi"},
{"midi", "audio/midi"},
{"kar", "audio/midi"},
{"mp3", "audio/mpeg"},
{"ogg", "audio/ogg"},
{"m4a", "audio/x-m4a"},
{"ra", "audio/x-realaudio"},
{"3gpp", "video/3gpp"},
{"3gp", "video/3gpp"},
{"ts", "video/mp2t"},
{"mp4", "video/mp4"},
{"mpeg", "video/mpeg"},
{"mpg", "video/mpeg"},
{"mov", "video/quicktime"},
{"webm", "video/webm"},
{"flv", "video/x-flv"},
{"m4v", "video/x-m4v"},
{"mng", "video/x-mng"},
{"asx", "video/x-ms-asf"},
{"asf", "video/x-ms-asf"},
{"wmv", "video/x-ms-wmv"},
{"avi", "video/x-msvideo"},
};
const string& HttpConst::getHttpContentType(const char *name) {
const char *dot;
dot = strrchr(name, '.');
static StrCaseMap mapType;
static onceToken token([&]() {
for (unsigned int i = 0; i < sizeof(s_mime_src) / sizeof(s_mime_src[0]); ++i) {
mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]);
}
});
static string defaultType = "text/plain";
if (!dot) {
return defaultType;
}
auto it = mapType.find(dot + 1);
if (it == mapType.end()) {
return defaultType;
}
return it->second;
}
}//namespace mediakit

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_HTTPCONST_H
#define ZLMEDIAKIT_HTTPCONST_H
#include <string>
namespace mediakit{
class HttpConst {
public:
HttpConst() = delete;
~HttpConst() = delete;
/**
* http错误代码获取字符说明
* @param status 404
* @return Not Found
* Get character description based on http error code
* @param status For example 404
* @return Error code character description, for example Not Found
* [AUTO-TRANSLATED:7b844410]
*/
static const char *getHttpStatusMessage(int status);
/**
* http mime
* @param name html
* @return mime值text/html
* Return http mime based on file suffix
* @param name File suffix, for example html
* @return mime value, for example text/html
* [AUTO-TRANSLATED:03d63e1f]
*/
static const std::string &getHttpContentType(const char *name);
};
}//mediakit
#endif //ZLMEDIAKIT_HTTPCONST_H

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HttpCookie.h"
#include "Util/util.h"
#include "Util/onceToken.h"
#if defined(_WIN32)
#include "Util/strptime_win.h"
#endif
using namespace toolkit;
using namespace std;
namespace mediakit {
void HttpCookie::setPath(const string &path) {
_path = path;
}
void HttpCookie::setHost(const string &host) {
_host = host;
}
// from https://gmbabar.wordpress.com/2010/12/01/mktime-slow-use-custom-function/#comment-58
static time_t time_to_epoch(const struct tm *ltm, int utcdiff) {
const int mon_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
long tyears, tdays, leaps, utc_hrs;
int i;
tyears = ltm->tm_year - 70; // tm->tm_year is from 1900.
leaps = (tyears + 2) / 4; // no of next two lines until year 2100.
// i = (ltm->tm_year 100) / 100; [AUTO-TRANSLATED:12beea30]
// i = (ltm->tm_year 100) / 100;
// leaps -= ( (i/4)*3 + i%4 );
tdays = 0;
for (i = 0; i < ltm->tm_mon; i++)
tdays += mon_days[i];
tdays += ltm->tm_mday - 1; // days of month passed.
tdays = tdays + (tyears * 365) + leaps;
utc_hrs = ltm->tm_hour + utcdiff; // for your time zone.
return (tdays * 86400) + (utc_hrs * 3600) + (ltm->tm_min * 60) + ltm->tm_sec;
}
static time_t timeStrToInt(const string &date) {
struct tm tt;
strptime(date.data(), "%a, %b %d %Y %H:%M:%S %Z", &tt);
// mktime内部有使用互斥锁非常影响性能 [AUTO-TRANSLATED:b3270635]
// mktime uses mutex internally, which significantly affects performance
return time_to_epoch(&tt, getGMTOff() / 3600); // mktime(&tt);
}
void HttpCookie::setExpires(const string &expires, const string &server_date) {
_expire = timeStrToInt(expires);
if (!server_date.empty()) {
_expire = time(NULL) + (_expire - timeStrToInt(server_date));
}
}
void HttpCookie::setKeyVal(const string &key, const string &val) {
_key = key;
_val = val;
}
HttpCookie::operator bool() {
return !_host.empty() && !_key.empty() && !_val.empty() && (_expire > time(NULL));
}
const string &HttpCookie::getVal() const {
return _val;
}
const string &HttpCookie::getKey() const {
return _key;
}
HttpCookieStorage &HttpCookieStorage::Instance() {
static HttpCookieStorage instance;
return instance;
}
void HttpCookieStorage::set(const HttpCookie::Ptr &cookie) {
lock_guard<mutex> lck(_mtx_cookie);
if (!cookie || !(*cookie)) {
return;
}
_all_cookie[cookie->_host][cookie->_path][cookie->_key] = cookie;
}
vector<HttpCookie::Ptr> HttpCookieStorage::get(const string &host, const string &path) {
vector<HttpCookie::Ptr> ret(0);
lock_guard<mutex> lck(_mtx_cookie);
auto it = _all_cookie.find(host);
if (it == _all_cookie.end()) {
// 未找到该host相关记录 [AUTO-TRANSLATED:0655542a]
// No record found for this host
return ret;
}
// 遍历该host下所有path [AUTO-TRANSLATED:94ca2180]
// Traverse all paths under this host
for (auto &pr : it->second) {
if (path.find(pr.first) != 0) {
// 这个path不匹配 [AUTO-TRANSLATED:3ec99732]
// This path does not match
continue;
}
// 遍历该path下的各个cookie [AUTO-TRANSLATED:ceab9c83]
// Traverse all cookies under this path
for (auto it_cookie = pr.second.begin(); it_cookie != pr.second.end();) {
if (!*(it_cookie->second)) {
// 该cookie已经过期移除之 [AUTO-TRANSLATED:52762286]
// This cookie has expired, remove it
it_cookie = pr.second.erase(it_cookie);
continue;
}
// 保存有效cookie [AUTO-TRANSLATED:bd875507]
// Save valid cookies
ret.emplace_back(it_cookie->second);
++it_cookie;
}
}
return ret;
}
} /* namespace mediakit */

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_HTTPCOOKIE_H
#define ZLMEDIAKIT_HTTPCOOKIE_H
#include <string>
#include <memory>
#include <vector>
#include <map>
#include <unordered_map>
#include <mutex>
namespace mediakit {
/**
* http客户端cookie对象
* http client cookie object
* [AUTO-TRANSLATED:5c1840bb]
*/
class HttpCookie {
public:
using Ptr = std::shared_ptr<HttpCookie>;
friend class HttpCookieStorage;
void setPath(const std::string &path);
void setHost(const std::string &host);
void setExpires(const std::string &expires,const std::string &server_date);
void setKeyVal(const std::string &key,const std::string &val);
operator bool ();
const std::string &getKey() const ;
const std::string &getVal() const ;
private:
std::string _host;
std::string _path = "/";
std::string _key;
std::string _val;
time_t _expire = 0;
};
/**
* http客户端cookie全局保存器
* http client cookie global saver
* [AUTO-TRANSLATED:cac4a704]
*/
class HttpCookieStorage{
public:
static HttpCookieStorage &Instance();
void set(const HttpCookie::Ptr &cookie);
std::vector<HttpCookie::Ptr> get(const std::string &host,const std::string &path);
private:
HttpCookieStorage() = default;
private:
std::unordered_map<std::string/*host*/, std::map<std::string/*cookie path*/,std::map<std::string/*cookie_key*/, HttpCookie::Ptr> > > _all_cookie;
std::mutex _mtx_cookie;
};
} /* namespace mediakit */
#endif //ZLMEDIAKIT_HTTPCOOKIE_H

View File

@ -0,0 +1,320 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HttpCookieManager.h"
#include "Common/config.h"
#include "Util/MD5.h"
#include "Util/util.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
//////////////////////////////HttpServerCookie////////////////////////////////////
HttpServerCookie::HttpServerCookie(
const std::shared_ptr<HttpCookieManager> &manager, const string &cookie_name, const string &uid,
const string &cookie, uint64_t max_elapsed) {
_uid = uid;
_max_elapsed = max_elapsed;
_cookie_uuid = cookie;
_cookie_name = cookie_name;
_manager = manager;
manager->onAddCookie(_cookie_name, _uid, _cookie_uuid);
}
HttpServerCookie::~HttpServerCookie() {
auto strongManager = _manager.lock();
if (strongManager) {
strongManager->onDelCookie(_cookie_name, _uid, _cookie_uuid);
}
}
const string &HttpServerCookie::getUid() const {
return _uid;
}
string HttpServerCookie::getCookie(const string &path) const {
return (StrPrinter << _cookie_name << "=" << _cookie_uuid << ";expires=" << cookieExpireTime() << ";path=" << path);
}
const string &HttpServerCookie::getCookie() const {
return _cookie_uuid;
}
const string &HttpServerCookie::getCookieName() const {
return _cookie_name;
}
void HttpServerCookie::updateTime() {
_ticker.resetTime();
}
bool HttpServerCookie::isExpired() {
return _ticker.elapsedTime() > _max_elapsed * 1000;
}
void HttpServerCookie::setAttach(toolkit::Any attach) {
_attach = std::move(attach);
}
string HttpServerCookie::cookieExpireTime() const {
char buf[64];
time_t tt = time(nullptr) + _max_elapsed;
strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
return buf;
}
//////////////////////////////CookieManager////////////////////////////////////
INSTANCE_IMP(HttpCookieManager);
HttpCookieManager::HttpCookieManager() {
// 定时删除过期的cookie防止内存膨胀 [AUTO-TRANSLATED:dd9dc9c0]
// Delete expired cookies periodically to prevent memory bloat
_timer = std::make_shared<Timer>(
10.0f,
[this]() {
onManager();
return true;
},
nullptr);
}
HttpCookieManager::~HttpCookieManager() {
_timer.reset();
}
void HttpCookieManager::onManager() {
lock_guard<recursive_mutex> lck(_mtx_cookie);
// 先遍历所有类型 [AUTO-TRANSLATED:4917ee89]
// First iterate through all types
for (auto it_name = _map_cookie.begin(); it_name != _map_cookie.end();) {
// 再遍历该类型下的所有cookie [AUTO-TRANSLATED:0aab9e18]
// Then iterate through all cookies under that type
for (auto it_cookie = it_name->second.begin(); it_cookie != it_name->second.end();) {
if (it_cookie->second->isExpired()) {
// cookie过期,移除记录 [AUTO-TRANSLATED:8b48b8a2]
// Cookie expired, remove record
DebugL << it_cookie->second->getUid() << " cookie过期:" << it_cookie->second->getCookie();
it_cookie = it_name->second.erase(it_cookie);
continue;
}
++it_cookie;
}
if (it_name->second.empty()) {
// 该类型下没有任何cookie记录,移除之 [AUTO-TRANSLATED:92e3b783]
// There are no cookie records under this type, remove it
DebugL << "该path下没有任何cookie记录:" << it_name->first;
it_name = _map_cookie.erase(it_name);
continue;
}
++it_name;
}
}
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, uint64_t max_elapsed, toolkit::Any attach, int max_client) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto cookie = _generator.obtain();
auto uid = uid_in.empty() ? cookie : uid_in;
auto oldCookie = getOldestCookie(cookie_name, uid, max_client);
if (!oldCookie.empty()) {
// 假如该账号已经登录了那么删除老的cookie。 [AUTO-TRANSLATED:f18d826d]
// If the account has already logged in, delete the old cookie.
// 目的是实现单账号多地登录时挤占登录 [AUTO-TRANSLATED:8a64aec7]
// The purpose is to achieve login squeeze when multiple devices log in with the same account
delCookie(cookie_name, oldCookie);
}
HttpServerCookie::Ptr data(new HttpServerCookie(shared_from_this(), cookie_name, uid, cookie, max_elapsed));
data->setAttach(std::move(attach));
// 保存该账号下的新cookie [AUTO-TRANSLATED:e476c9c8]
// Save the new cookie under this account
_map_cookie[cookie_name][cookie] = data;
return data;
}
HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, const string &cookie) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto it_name = _map_cookie.find(cookie_name);
if (it_name == _map_cookie.end()) {
// 不存在该类型的cookie [AUTO-TRANSLATED:d32b0997]
// There is no cookie of this type
return nullptr;
}
auto it_cookie = it_name->second.find(cookie);
if (it_cookie == it_name->second.end()) {
// 该类型下没有对应的cookie [AUTO-TRANSLATED:62caa764]
// There is no corresponding cookie under this type
return nullptr;
}
if (it_cookie->second->isExpired()) {
// cookie过期 [AUTO-TRANSLATED:a980453f]
// Cookie expired
DebugL << "cookie过期:" << it_cookie->second->getCookie();
it_name->second.erase(it_cookie);
return nullptr;
}
return it_cookie->second;
}
HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, const StrCaseMap &http_header) {
auto it = http_header.find("Cookie");
if (it == http_header.end()) {
return nullptr;
}
auto cookie = findSubString(it->second.data(), (cookie_name + "=").data(), ";");
if (cookie.empty()) {
cookie = findSubString(it->second.data(), (cookie_name + "=").data(), nullptr);
}
if (cookie.empty()) {
return nullptr;
}
return getCookie(cookie_name, cookie);
}
HttpServerCookie::Ptr HttpCookieManager::getCookieByUid(const string &cookie_name, const string &uid) {
if (cookie_name.empty() || uid.empty()) {
return nullptr;
}
auto cookie = getOldestCookie(cookie_name, uid);
if (cookie.empty()) {
return nullptr;
}
return getCookie(cookie_name, cookie);
}
bool HttpCookieManager::delCookie(const HttpServerCookie::Ptr &cookie) {
if (!cookie) {
return false;
}
return delCookie(cookie->getCookieName(), cookie->getCookie());
}
bool HttpCookieManager::delCookie(const string &cookie_name, const string &cookie) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto it_name = _map_cookie.find(cookie_name);
if (it_name == _map_cookie.end()) {
return false;
}
return it_name->second.erase(cookie);
}
void HttpCookieManager::onAddCookie(const string &cookie_name, const string &uid, const string &cookie) {
// 添加新的cookie我们记录下这个uid下有哪些cookie目的是实现单账号多地登录时挤占登录 [AUTO-TRANSLATED:60b752e9]
// Add a new cookie, we record which cookies are under this uid, the purpose is to achieve login squeeze when multiple devices log in with the same account
lock_guard<recursive_mutex> lck(_mtx_cookie);
// 相同用户下可以存在多个cookie(意味多地登录)这些cookie根据登录时间的早晚依次排序 [AUTO-TRANSLATED:1e0b93b9]
// Multiple cookies can exist under the same user (meaning multiple devices log in), these cookies are sorted in order of login time
_map_uid_to_cookie[cookie_name][uid][getCurrentMillisecond()] = cookie;
}
void HttpCookieManager::onDelCookie(const string &cookie_name, const string &uid, const string &cookie) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
// 回收随机字符串 [AUTO-TRANSLATED:18a699ff]
// Recycle random string
_generator.release(cookie);
auto it_name = _map_uid_to_cookie.find(cookie_name);
if (it_name == _map_uid_to_cookie.end()) {
// 该类型下未有任意用户登录 [AUTO-TRANSLATED:8ba458b9]
// No user has logged in under this type
return;
}
auto it_uid = it_name->second.find(uid);
if (it_uid == it_name->second.end()) {
// 该用户尚未登录 [AUTO-TRANSLATED:ec07ce1b]
// This user has not logged in yet
return;
}
// 遍历同一名用户下的所有客户端,移除命中的客户端 [AUTO-TRANSLATED:cae6e264]
// Iterate through all clients under the same user and remove the matching client
for (auto it_cookie = it_uid->second.begin(); it_cookie != it_uid->second.end(); ++it_cookie) {
if (it_cookie->second != cookie) {
// 不是该cookie [AUTO-TRANSLATED:cf5eca3b]
// Not this cookie
continue;
}
// 移除该用户名下的某个cookie这个设备cookie将失效 [AUTO-TRANSLATED:bf2de2a0]
// Remove a cookie under this username, this device cookie will become invalid
it_uid->second.erase(it_cookie);
if (!it_uid->second.empty()) {
break;
}
// 该用户名下没有任何设备在线,移除之 [AUTO-TRANSLATED:6a8a2305]
// There are no devices online under this username, remove it
it_name->second.erase(it_uid);
if (!it_name->second.empty()) {
break;
}
// 该类型下未有任何用户在线,移除之 [AUTO-TRANSLATED:e705cfe6]
// There are no users online under this type, remove it
_map_uid_to_cookie.erase(it_name);
break;
}
}
string HttpCookieManager::getOldestCookie(const string &cookie_name, const string &uid, int max_client) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto it_name = _map_uid_to_cookie.find(cookie_name);
if (it_name == _map_uid_to_cookie.end()) {
// 不存在该类型的cookie [AUTO-TRANSLATED:d32b0997]
// There is no cookie of this type
return "";
}
auto it_uid = it_name->second.find(uid);
if (it_uid == it_name->second.end()) {
// 该用户从未登录过 [AUTO-TRANSLATED:fc6dbcf6]
// This user has never logged in
return "";
}
if ((int)it_uid->second.size() < MAX(1, max_client)) {
// 同一名用户下,客户端个数还没达到限制个数 [AUTO-TRANSLATED:a31f6ada]
// Under the same user, the number of clients has not reached the limit
return "";
}
// 客户端个数超过限制,移除最先登录的客户端 [AUTO-TRANSLATED:a284ce91]
// The number of clients exceeds the limit, remove the first client to log in
return it_uid->second.begin()->second;
}
/////////////////////////////////RandStrGenerator////////////////////////////////////
string RandStrGenerator::obtain() {
// 获取唯一的防膨胀的随机字符串 [AUTO-TRANSLATED:1306465c]
// Get a unique anti-bloating random string
while (true) {
auto str = obtain_l();
if (_obtained.find(str) == _obtained.end()) {
// 没有重复 [AUTO-TRANSLATED:16af311b]
// No duplicates
_obtained.emplace(str);
return str;
}
}
}
void RandStrGenerator::release(const string &str) {
// 从防膨胀库中移除 [AUTO-TRANSLATED:1165d5fe]
// Remove from the anti-bloating library
_obtained.erase(str);
}
string RandStrGenerator::obtain_l() {
// 12个伪随机字节 + 4个递增的整形字节然后md5即为随机字符串 [AUTO-TRANSLATED:8571a327]
// 12 pseudo-random bytes + 4 incrementing integer bytes, then md5 is the random string
auto str = makeRandStr(12, false);
str.append((char *)&_index, sizeof(_index));
++_index;
return MD5(str).hexdigest();
}
} // namespace mediakit

View File

@ -0,0 +1,369 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_HTTP_COOKIEMANAGER_H
#define SRC_HTTP_COOKIEMANAGER_H
#include "Common/Parser.h"
#include "Network/Socket.h"
#include "Util/TimeTicker.h"
#include "Util/mini.h"
#include "Util/util.h"
#include <memory>
#include <unordered_map>
#define COOKIE_DEFAULT_LIFE (7 * 24 * 60 * 60)
namespace mediakit {
class HttpCookieManager;
/**
* cookie对象cookie的一些相关属性
* cookie object, used to store some related attributes of the cookie
* [AUTO-TRANSLATED:267fbbc3]
*/
class HttpServerCookie : public toolkit::noncopyable {
public:
using Ptr = std::shared_ptr<HttpServerCookie>;
/**
* cookie
* @param manager cookie管理者对象
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param cookie cookie随机字符串
* @param max_elapsed
* Construct cookie
* @param manager cookie manager object
* @param cookie_name cookie name, such as MY_SESSION
* @param uid user unique id
* @param cookie cookie random string
* @param max_elapsed maximum expiration time, in seconds
* [AUTO-TRANSLATED:a24f209d]
*/
HttpServerCookie(
const std::shared_ptr<HttpCookieManager> &manager, const std::string &cookie_name, const std::string &uid,
const std::string &cookie, uint64_t max_elapsed);
~HttpServerCookie();
/**
* uid
* @return uid
* Get uid
* @return uid
* [AUTO-TRANSLATED:71a3afab]
*/
const std::string &getUid() const;
/**
* http中Set-Cookie字段的值
* @param cookie_name cookie的名称 MY_SESSION
* @param path http访问路径
* @return MY_SESSION=XXXXXX;expires=Wed, Jun 12 2019 06:30:48 GMT;path=/index/files/
* Get the value of the Set-Cookie field in http
* @param cookie_name the name of this cookie, such as MY_SESSION
* @param path http access path
* @return For example, MY_SESSION=XXXXXX;expires=Wed, Jun 12 2019 06:30:48 GMT;path=/index/files/
* [AUTO-TRANSLATED:8699036b]
*/
std::string getCookie(const std::string &path) const;
/**
* cookie随机字符串
* @return cookie随机字符串
* Get cookie random string
* @return cookie random string
* [AUTO-TRANSLATED:1853611a]
*/
const std::string &getCookie() const;
/**
* cookie名
* @return
* Get the name of this cookie
* @return
* [AUTO-TRANSLATED:6251f9f5]
*/
const std::string &getCookieName() const;
/**
* cookie的过期时间cookie不失效
* Update the expiration time of this cookie, so that this cookie will not expire
* [AUTO-TRANSLATED:d3a3300b]
*/
void updateTime();
/**
* cookie是否过期
* @return
* Determine whether this cookie has expired
* @return
* [AUTO-TRANSLATED:3b0d3d59]
*/
bool isExpired();
/**
*
* Set additional data
* [AUTO-TRANSLATED:afde9874]
*/
void setAttach(toolkit::Any attach);
/*
*
/*
* Get additional data
* [AUTO-TRANSLATED:e277d75d]
*/
template <class T>
T& getAttach() {
return _attach.get<T>();
}
private:
std::string cookieExpireTime() const;
private:
std::string _uid;
std::string _cookie_name;
std::string _cookie_uuid;
uint64_t _max_elapsed;
toolkit::Ticker _ticker;
toolkit::Any _attach;
std::weak_ptr<HttpCookieManager> _manager;
};
/**
* cookie随机字符串生成器
* cookie random string generator
* [AUTO-TRANSLATED:501ea34c]
*/
class RandStrGenerator {
public:
/**
*
* @return
* Get a random string that does not collide
* @return random string
* [AUTO-TRANSLATED:6daa3fd8]
*/
std::string obtain();
/**
*
* @param str
* Release random string
* @param str random string
* [AUTO-TRANSLATED:90ea164a]
*/
void release(const std::string &str);
private:
std::string obtain_l();
private:
// 碰撞库 [AUTO-TRANSLATED:25a2ca2b]
// Collision library
std::unordered_set<std::string> _obtained;
// 增长index防止碰撞用 [AUTO-TRANSLATED:85778468]
// Increase index, used to prevent collisions
int _index = 0;
};
/**
* cookie管理器cookie的生成以及过期管理
*
* Cookie manager, used to manage cookie generation and expiration management, and also implements the function of occupying login from different locations with the same account
* This object implements the function that the same account can log in to at most several devices
* [AUTO-TRANSLATED:ad6008e8]
*/
class HttpCookieManager : public std::enable_shared_from_this<HttpCookieManager> {
public:
friend class HttpServerCookie;
using Ptr = std::shared_ptr<HttpCookieManager>;
~HttpCookieManager();
/**
*
* Get singleton
* [AUTO-TRANSLATED:d082a6ee]
*/
static HttpCookieManager &Instance();
/**
* cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param max_client
* @param max_elapsed cookie过期时间
* @return cookie对象
* Add cookie
* @param cookie_name cookie name, such as MY_SESSION
* @param uid user id, if empty, it is anonymous login
* @param max_client the maximum number of devices that this account can log in to
* @param max_elapsed the expiration time of this cookie, in seconds
* @return cookie object
* [AUTO-TRANSLATED:c23f2321]
*/
HttpServerCookie::Ptr addCookie(
const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,
toolkit::Any = toolkit::Any{},
int max_client = 1);
/**
* cookie随机字符串查找cookie对象
* @param cookie_name cookie名MY_SESSION
* @param cookie cookie随机字符串
* @return cookie对象nullptr
* Find cookie object by cookie random string
* @param cookie_name cookie name, such as MY_SESSION
* @param cookie cookie random string
* @return cookie object, can be nullptr
* [AUTO-TRANSLATED:a0c7ed63]
*/
HttpServerCookie::Ptr getCookie(const std::string &cookie_name, const std::string &cookie);
/**
* http头中获取cookie对象
* @param cookie_name cookie名MY_SESSION
* @param http_header http头
* @return cookie对象
* Get cookie object from http header
* @param cookie_name cookie name, such as MY_SESSION
* @param http_header http header
* @return cookie object
* [AUTO-TRANSLATED:93661474]
*/
HttpServerCookie::Ptr getCookie(const std::string &cookie_name, const StrCaseMap &http_header);
/**
* uid获取cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @return cookie对象
* Get cookie by uid
* @param cookie_name cookie name, such as MY_SESSION
* @param uid user id
* @return cookie object
* [AUTO-TRANSLATED:623277e4]
*/
HttpServerCookie::Ptr getCookieByUid(const std::string &cookie_name, const std::string &uid);
/**
* cookie使
* @param cookie cookie对象nullptr
* @return
* Delete cookie, used when user logs out
* @param cookie cookie object, can be nullptr
* @return
* [AUTO-TRANSLATED:f80c6974]
*/
bool delCookie(const HttpServerCookie::Ptr &cookie);
private:
HttpCookieManager();
void onManager();
/**
* cookie对象时触发cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param cookie cookie随机字符串
* Triggered when constructing a cookie object, the purpose is to record multiple cookies under a certain account
* @param cookie_name cookie name, such as MY_SESSION
* @param uid user id
* @param cookie cookie random string
* [AUTO-TRANSLATED:bb2bb670]
*/
void onAddCookie(const std::string &cookie_name, const std::string &uid, const std::string &cookie);
/**
* cookie对象时触发
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param cookie cookie随机字符串
* Triggered when destructing a cookie object
* @param cookie_name cookie name, such as MY_SESSION
* @param uid user id
* @param cookie cookie random string
* [AUTO-TRANSLATED:bdf9cce5]
*/
void onDelCookie(const std::string &cookie_name, const std::string &uid, const std::string &cookie);
/**
* cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param max_client
* @return cookie随机字符串
* Get the cookie that logged in first under a certain username, the purpose is to implement the function that at most several devices can log in under a certain user
* @param cookie_name cookie name, such as MY_SESSION
* @param uid user id
* @param max_client the maximum number of devices that can log in
* @return the earliest cookie random string
* [AUTO-TRANSLATED:431b0732]
*/
std::string getOldestCookie(const std::string &cookie_name, const std::string &uid, int max_client = 1);
/**
* cookie
* @param cookie_name cookie名MY_SESSION
* @param cookie cookie随机字符串
* @return true
* Delete cookie
* @param cookie_name cookie name, such as MY_SESSION
* @param cookie cookie random string
* @return success true
* [AUTO-TRANSLATED:09fa1e44]
*/
bool delCookie(const std::string &cookie_name, const std::string &cookie);
private:
std::unordered_map<
std::string /*cookie_name*/, std::unordered_map<std::string /*cookie*/, HttpServerCookie::Ptr /*cookie_data*/>>
_map_cookie;
std::unordered_map<
std::string /*cookie_name*/,
std::unordered_map<std::string /*uid*/, std::map<uint64_t /*cookie time stamp*/, std::string /*cookie*/>>>
_map_uid_to_cookie;
std::recursive_mutex _mtx_cookie;
toolkit::Timer::Ptr _timer;
RandStrGenerator _generator;
};
} // namespace mediakit
#endif // SRC_HTTP_COOKIEMANAGER_H

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HttpDownloader.h"
#include "Util/File.h"
#include "Util/MD5.h"
using namespace toolkit;
using namespace std;
namespace mediakit {
HttpDownloader::~HttpDownloader() {
closeFile();
}
void HttpDownloader::startDownload(const string &url, const string &file_path, bool append) {
_file_path = file_path;
if (_file_path.empty()) {
_file_path = exeDir() + "HttpDownloader/" + MD5(url).hexdigest();
}
_save_file = File::create_file(_file_path, append ? "ab" : "wb");
if (!_save_file) {
auto strErr = StrPrinter << "打开文件失败:" << file_path << endl;
throw std::runtime_error(strErr);
}
if (append) {
auto currentLen = ftell(_save_file);
if (currentLen) {
// 最少续传一个字节怕遇到http 416的错误 [AUTO-TRANSLATED:8a3c5303]
// Resume downloading at least one byte to avoid encountering a http 416 error
currentLen -= 1;
fseek(_save_file, -1, SEEK_CUR);
}
addHeader("Range", StrPrinter << "bytes=" << currentLen << "-" << endl);
}
setMethod("GET");
sendRequest(url);
}
void HttpDownloader::onResponseHeader(const string &status, const HttpHeader &headers) {
if (status != "200" && status != "206") {
// 失败 [AUTO-TRANSLATED:27ec5fb1]
// Failure
throw std::invalid_argument("bad http status: " + status);
}
}
void HttpDownloader::onResponseBody(const char *buf, size_t size) {
if (_save_file) {
fwrite(buf, size, 1, _save_file);
}
}
void HttpDownloader::onResponseCompleted(const SockException &ex) {
closeFile();
if (_on_result) {
_on_result(ex, _file_path);
_on_result = nullptr;
}
}
void HttpDownloader::closeFile() {
if (_save_file) {
fflush(_save_file);
fclose(_save_file);
_save_file = nullptr;
}
}
} /* namespace mediakit */

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_HTTP_HTTPDOWNLOADER_H_
#define SRC_HTTP_HTTPDOWNLOADER_H_
#include "HttpClientImp.h"
namespace mediakit {
class HttpDownloader : public HttpClientImp {
public:
using Ptr = std::shared_ptr<HttpDownloader>;
using onDownloadResult = std::function<void(const toolkit::SockException &ex, const std::string &filePath)>;
~HttpDownloader() override;
/**
* ,
* @param url http url
* @param file_path
* @param append
* Start downloading the file, default to resume download
* @param url Download http url
* @param file_path File save address, leave blank to choose the default file path
* @param append If the file already exists, whether to download in resume mode
* [AUTO-TRANSLATED:6f651882]
*/
void startDownload(const std::string &url, const std::string &file_path = "", bool append = false);
void startDownload(const std::string &url, const onDownloadResult &cb) {
setOnResult(cb);
startDownload(url, "", false);
}
void setOnResult(const onDownloadResult &cb) { _on_result = cb; }
protected:
void onResponseBody(const char *buf, size_t size) override;
void onResponseHeader(const std::string &status, const HttpHeader &headers) override;
void onResponseCompleted(const toolkit::SockException &ex) override;
private:
void closeFile();
private:
FILE *_save_file = nullptr;
std::string _file_path;
onDownloadResult _on_result;
};
} /* namespace mediakit */
#endif /* SRC_HTTP_HTTPDOWNLOADER_H_ */

View File

@ -0,0 +1,847 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <iomanip>
#include "Util/File.h"
#include "Common/Parser.h"
#include "Common/config.h"
#include "Common/strCoding.h"
#include "Record/HlsMediaSource.h"
#include "HttpConst.h"
#include "HttpSession.h"
#include "HttpFileManager.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
// hls的播放cookie缓存时间默认60秒 [AUTO-TRANSLATED:88198dfa]
// The default cache time for the hls playback cookie is 60 seconds.
// 每次访问一次该cookie那么将重新刷新cookie有效期 [AUTO-TRANSLATED:a1b76209]
// Each time this cookie is accessed, the cookie's validity period will be refreshed.
// 假如播放器在60秒内都未访问该cookie那么将重新触发hls播放鉴权 [AUTO-TRANSLATED:55000c94]
// If the player does not access the cookie within 60 seconds, the hls playback authentication will be triggered again.
static size_t kHlsCookieSecond = 60;
static size_t kFindSrcIntervalSecond = 3;
static const string kCookieName = "ZL_COOKIE";
static const string kHlsSuffix = "/hls.m3u8";
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
struct HttpCookieAttachment {
// 是否已经查找到过MediaSource [AUTO-TRANSLATED:b5b9922a]
// Whether the MediaSource has been found
bool _find_src = false;
// 查找MediaSource计时 [AUTO-TRANSLATED:39904ba9]
// MediaSource search timing
Ticker _find_src_ticker;
// cookie生效作用域本cookie只对该目录下的文件生效 [AUTO-TRANSLATED:7a59ad9a]
// Cookie effective scope, this cookie only takes effect for files under this directory
string _path;
// 上次鉴权失败信息,为空则上次鉴权成功 [AUTO-TRANSLATED:de48b753]
// Last authentication failure information, empty means last authentication succeeded
string _err_msg;
// hls直播时的其他一些信息主要用于播放器个数计数以及流量计数 [AUTO-TRANSLATED:790de53a]
// Other information during hls live broadcast, mainly used for player count and traffic count
HlsCookieData::Ptr _hls_data;
};
const string &HttpFileManager::getContentType(const char *name) {
return HttpConst::getHttpContentType(name);
}
namespace {
class UInt128 {
public:
UInt128() = default;
UInt128(const struct sockaddr_storage &storage) {
_family = storage.ss_family;
memset(_bytes, 0, 16);
switch (storage.ss_family) {
case AF_INET: {
memcpy(_bytes, &(reinterpret_cast<const struct sockaddr_in &>(storage).sin_addr), 4);
break;
}
case AF_INET6: {
memcpy(_bytes, &(reinterpret_cast<const struct sockaddr_in6 &>(storage).sin6_addr), 16);
break;
}
default: CHECK(false, "Invalid socket family"); break;
}
}
bool operator==(const UInt128 &that) const { return _family == that._family && !memcmp(_bytes, that._bytes, 16); }
bool operator<=(const UInt128 &that) const { return *this < that || *this == that; }
bool operator>=(const UInt128 &that) const { return *this > that || *this == that; }
bool operator>(const UInt128 &that) const { return that < *this; }
bool operator<(const UInt128 &that) const {
auto sz = _family == AF_INET ? 4 : 16;
for (int i = 0; i < sz; ++i) {
if (_bytes[i] < that._bytes[i]) {
return true;
} else if (_bytes[i] > that._bytes[i]) {
return false;
}
}
return false;
}
operator bool() const { return _family != -1; }
bool same_type(const UInt128 &that) const { return _family == that._family; }
private:
int _family = -1;
uint8_t _bytes[16];
};
}
static UInt128 get_ip_uint64(const std::string &ip) {
try {
return UInt128(SockUtil::make_sockaddr(ip.data(), 0));
} catch (std::exception &ex) {
WarnL << ex.what();
}
return UInt128();
}
bool HttpFileManager::isIPAllowed(const std::string &ip) {
using IPRangs = std::vector<std::pair<UInt128 /*min_ip*/, UInt128 /*max_ip*/>>;
GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs {
IPRangs ret;
auto vec = split(str, ",");
for (auto &item : vec) {
if (trim(item).empty()) {
continue;
}
auto range = split(item, "-");
if (range.size() == 2) {
auto ip_min = get_ip_uint64(trim(range[0]));
auto ip_max = get_ip_uint64(trim(range[1]));
if (ip_min && ip_max && ip_min.same_type(ip_max)) {
ret.emplace_back(ip_min, ip_max);
} else {
WarnL << "Invalid ip range or family: " << item;
}
} else if (range.size() == 1) {
auto ip = get_ip_uint64(trim(range[0]));
if (ip) {
ret.emplace_back(ip, ip);
} else {
WarnL << "Invalid ip: " << item;
}
} else {
WarnL << "Invalid ip range: " << item;
}
}
return ret;
});
if (allow_ip_range.empty()) {
return true;
}
auto ip_int = get_ip_uint64(ip);
for (auto &range : allow_ip_range) {
if (ip_int.same_type(range.first) && ip_int >= range.first && ip_int <= range.second) {
return true;
}
}
return false;
}
static std::string fileName(const string &dir, const string &path) {
auto ret = path.substr(dir.size());
if (ret.front() == '/') {
ret.erase(0, 1);
}
return ret;
}
static string searchIndexFile(const string &dir) {
std::string ret;
static set<std::string, StrCaseCompare> indexSet = { "index.html", "index.htm" };
File::scanDir(dir, [&](const string &path, bool is_dir) {
if (is_dir) {
return true;
}
auto name = fileName(dir, path);
if (indexSet.find(name) == indexSet.end()) {
return true;
}
ret = std::move(name);
return false;
});
return ret;
}
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
GET_CONFIG(bool, dirMenu, Http::kDirMenu);
if (!dirMenu) {
// 不允许浏览文件夹 [AUTO-TRANSLATED:a0c30a94]
// Not allowed to browse folders
return false;
}
string strPathPrefix(strFullPath);
// url后缀有没有'/'访问文件夹,处理逻辑不一致 [AUTO-TRANSLATED:39c6a933]
// Whether the url suffix has '/' to access the folder, the processing logic is inconsistent
string last_dir_name;
if (strPathPrefix.back() == '/') {
strPathPrefix.pop_back();
} else {
last_dir_name = split(strPathPrefix, "/").back();
}
if (!File::is_dir(strPathPrefix)) {
return false;
}
stringstream ss;
ss << "<html>\r\n"
"<head>\r\n"
"<title>File Index</title>\r\n"
"</head>\r\n"
"<body>\r\n"
"<h1>Index of ";
ss << httpPath;
ss << "</h1>\r\n";
if (httpPath != "/") {
ss << "<li><a href=\"";
ss << "/";
ss << "\">";
ss << "root";
ss << "</a></li>\r\n";
ss << "<li><a href=\"";
if (!last_dir_name.empty()) {
ss << "./";
} else {
ss << "../";
}
ss << "\">";
ss << "../";
ss << "</a></li>\r\n";
}
multimap<string/*url name*/, std::pair<string/*note name*/, string/*file path*/> > file_map;
File::scanDir(strPathPrefix, [&](const std::string &path, bool isDir) {
auto name = fileName(strPathPrefix, path);
file_map.emplace(strCoding::UrlEncodePath(name), std::make_pair(name, path));
return true;
});
// 如果是root目录添加虚拟目录 [AUTO-TRANSLATED:3149d7f9]
// If it is the root directory, add a virtual directory
if (httpPath == "/") {
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
return Parser::parseArgs(str, ";", ",");
});
for (auto &pr : virtualPathMap) {
file_map.emplace(pr.first, std::make_pair(string("virtual path: ") + pr.first, File::absolutePath("", pr.second)));
}
}
int i = 0;
for (auto &pr :file_map) {
auto &strAbsolutePath = pr.second.second;
bool isDir = File::is_dir(strAbsolutePath);
ss << "<li><span>" << i++ << "</span>\t";
ss << "<a href=\"";
// 路径链接地址 [AUTO-TRANSLATED:33bc5f41]
// Path link address
if (!last_dir_name.empty()) {
ss << last_dir_name << "/" << pr.first;
} else {
ss << pr.first;
}
if (isDir) {
ss << "/";
}
ss << "\">";
// 路径名称 [AUTO-TRANSLATED:4dae8790]
// Path name
ss << pr.second.first;
if (isDir) {
ss << "/</a></li>\r\n";
continue;
}
// 是文件 [AUTO-TRANSLATED:70473f2f]
// It's a file
auto fileSize = File::fileSize(strAbsolutePath);
if (fileSize < 1024) {
ss << " (" << fileSize << "B)" << endl;
} else if (fileSize < 1024 * 1024) {
ss << fixed << setprecision(2) << " (" << fileSize / 1024.0 << "KB)";
} else if (fileSize < 1024 * 1024 * 1024) {
ss << fixed << setprecision(2) << " (" << fileSize / 1024 / 1024.0 << "MB)";
} else {
ss << fixed << setprecision(2) << " (" << fileSize / 1024 / 1024 / 1024.0 << "GB)";
}
ss << "</a></li>\r\n";
}
ss << "<ul>\r\n";
ss << "</ul>\r\n</body></html>";
ss.str().swap(strRet);
return true;
}
// 拦截hls的播放请求 [AUTO-TRANSLATED:dd1bbeec]
// Intercept the hls playback request
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, const HttpSession::HttpAccessPathInvoker &invoker,Session &sender){
// 访问的hls.m3u8结尾我们转换成kBroadcastMediaPlayed事件 [AUTO-TRANSLATED:b7a67c84]
// The hls.m3u8 ending of the access, we convert it to the kBroadcastMediaPlayed event
Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
// cookie有效期为kHlsCookieSecond [AUTO-TRANSLATED:a0026dcd]
// The cookie validity period is kHlsCookieSecond
invoker(err, "", kHlsCookieSecond);
};
bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender);
if (!flag) {
// 未开启鉴权,那么允许播放 [AUTO-TRANSLATED:077feed1]
// Authentication is not enabled, so playback is allowed
auth_invoker("");
}
return flag;
}
class SockInfoImp : public SockInfo{
public:
using Ptr = std::shared_ptr<SockInfoImp>;
string get_local_ip() override {
return _local_ip;
}
uint16_t get_local_port() override {
return _local_port;
}
string get_peer_ip() override {
return _peer_ip;
}
uint16_t get_peer_port() override {
return _peer_port;
}
string getIdentifier() const override {
return _identifier;
}
string _local_ip;
string _peer_ip;
string _identifier;
uint16_t _local_port;
uint16_t _peer_port;
};
/**
* http客户端是否有权限访问文件的逻辑步骤
* 1http请求头查找cookie3
* 2http url参数查找cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
* The logical steps to determine whether the http client has permission to access the file
* 1. Find the cookie according to the http request header, find it and enter step 3
* 2. Find the cookie according to the http url parameter, if the cookie is still not found, enter step 5
* 3. Whether the cookie mark has permission to access the file, if it has permission, return the file directly
* 4. Whether the url parameter recorded in the cookie is consistent with the current url parameter, if it is consistent, return the client error code directly
* 5. Trigger the kBroadcastHttpAccess event
* [AUTO-TRANSLATED:dfc0f15f]
*/
static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir,
const function<void(const string &err_msg, const HttpServerCookie::Ptr &cookie)> &callback) {
// 获取用户唯一id [AUTO-TRANSLATED:5b1cf4bf]
// Get the user's unique id
auto uid = parser.params();
auto path = parser.url();
// 先根据http头中的cookie字段获取cookie [AUTO-TRANSLATED:155cf682]
// First get the cookie according to the cookie field in the http header
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
// 是否需要更新cookie [AUTO-TRANSLATED:b95121d5]
// Whether to update the cookie
bool update_cookie = false;
if (!cookie && !uid.empty()) {
// 客户端请求中无cookie,再根据该用户的用户id获取cookie [AUTO-TRANSLATED:42cb8ade]
// There is no cookie in the client request, then get the cookie according to the user id of the user
cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
update_cookie = true;
}
if (cookie) {
auto& attach = cookie->getAttach<HttpCookieAttachment>();
if (path.find(attach._path) == 0) {
// 上次cookie是限定本目录 [AUTO-TRANSLATED:a5c40abf]
// The last cookie is limited to this directory
if (attach._err_msg.empty()) {
// 上次鉴权成功 [AUTO-TRANSLATED:1a23f781]
// Last authentication succeeded
if (attach._hls_data) {
// 如果播放的是hls那么刷新hls的cookie(获取ts文件也会刷新) [AUTO-TRANSLATED:02acac59]
// If the playback is hls, then refresh the hls cookie (getting the ts file will also refresh)
cookie->updateTime();
update_cookie = true;
}
callback("", update_cookie ? cookie : nullptr);
return;
}
// 上次鉴权失败但是如果url参数发生变更那么也重新鉴权下 [AUTO-TRANSLATED:df9bd345]
// Last authentication failed, but if the url parameter changes, then re-authenticate
if (parser.params().empty() || parser.params() == cookie->getUid()) {
// url参数未变或者本来就没有url参数那么判断本次请求为重复请求无访问权限 [AUTO-TRANSLATED:f46b4fca]
// The url parameter has not changed, or there is no url parameter at all, then determine that the current request is a duplicate request and has no access permission
callback(attach._err_msg, update_cookie ? cookie : nullptr);
return;
}
}
// 如果url参数变了或者不是限定本目录那么旧cookie失效重新鉴权 [AUTO-TRANSLATED:acf6d49e]
// If the url parameter changes or is not limited to this directory, then the old cookie expires and re-authentication is required
HttpCookieManager::Instance().delCookie(cookie);
}
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();
info->_peer_ip = sender.get_peer_ip();
info->_peer_port = sender.get_peer_port();
info->_local_ip = sender.get_local_ip();
info->_local_port = sender.get_local_port();
// 该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录 [AUTO-TRANSLATED:8f4b3dd2]
// This user has never obtained a cookie, at this time we broadcast whether to allow this user to access this http directory
HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, media_info, info]
(const string &err_msg, const string &cookie_path_in, int life_second) {
HttpServerCookie::Ptr cookie;
if (life_second) {
// 本次鉴权设置了有效期我们把鉴权结果缓存在cookie中 [AUTO-TRANSLATED:5a12f48e]
// This authentication has an expiration date, we cache the authentication result in the cookie
string cookie_path = cookie_path_in;
if (cookie_path.empty()) {
// 如果未设置鉴权目录,那么我们采用当前目录 [AUTO-TRANSLATED:701ada2d]
// If no authentication directory is set, we use the current directory
cookie_path = is_dir ? path : path.substr(0, path.rfind("/") + 1);
}
auto attach = std::make_shared<HttpCookieAttachment>();
// 记录用户能访问的路径 [AUTO-TRANSLATED:80a2ba33]
// Record the paths that the user can access
attach->_path = cookie_path;
// 记录能否访问 [AUTO-TRANSLATED:972f6fc5]
// Record whether access is allowed
attach->_err_msg = err_msg;
if (is_hls) {
// hls相关信息 [AUTO-TRANSLATED:37893a71]
// hls related information
attach->_hls_data = std::make_shared<HlsCookieData>(media_info, info);
}
toolkit::Any any;
any.set(std::move(attach));
callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, std::move(any)));
} else {
callback(err_msg, nullptr);
}
};
if (is_hls) {
// 是hls的播放鉴权,拦截之 [AUTO-TRANSLATED:c5ba86bb]
// This is hls playback authentication, intercept it
emitHlsPlayed(parser, media_info, accessPathInvoker, sender);
return;
}
// 事件未被拦截则认为是http下载请求 [AUTO-TRANSLATED:7d449ccc]
// The event was not intercepted, it is considered an http download request
bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
if (!flag) {
// 此事件无人监听,我们默认都有权限访问 [AUTO-TRANSLATED:e1524c0f]
// No one is listening to this event, we assume that everyone has permission to access it by default
callback("", nullptr);
}
}
/**
* 404 Not Found
* Send 404 Not Found
* [AUTO-TRANSLATED:1297f2e7]
*/
static void sendNotFound(const HttpFileManager::invoker &cb) {
GET_CONFIG(string, notFound, Http::kNotFound);
cb(404, "text/html", StrCaseMap(), std::make_shared<HttpStringBody>(notFound));
}
/**
*
* Concatenate the file path
* [AUTO-TRANSLATED:cf6f5c53]
*/
static string pathCat(const string &a, const string &b){
if (a.back() == '/') {
return a + b;
}
return a + '/' + b;
}
/**
* 访
* @param sender
* @param parser http请求
* @param media_info http url信息
* @param file_path
* @param cb
* Access the file
* @param sender Event trigger
* @param parser http request
* @param media_info http url information
* @param file_path Absolute file path
* @param cb Callback object
* [AUTO-TRANSLATED:2d840fe6]
*/
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) || end_with(file_path, kHlsFMP4Suffix);
if (!is_hls && !File::fileExist(file_path)) {
// 文件不存在且不是hls,那么直接返回404 [AUTO-TRANSLATED:7aae578b]
// The file does not exist and is not hls, so directly return 404
sendNotFound(cb);
return;
}
if (is_hls) {
// hls那么移除掉后缀获取真实的stream_id并且修改协议为HLS [AUTO-TRANSLATED:94b5818a]
// hls, then remove the suffix to get the real stream_id and change the protocol to HLS
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());
// 判断是否有权限访问该文件 [AUTO-TRANSLATED:b7f595f5]
// Determine whether you have permission to access this file
canAccessPath(sender, parser, media_info, false, [cb, file_path, parser, is_hls, media_info, weakSession](const string &err_msg, const HttpServerCookie::Ptr &cookie) {
auto strongSession = weakSession.lock();
if (!strongSession) {
// http客户端已经断开不需要回复 [AUTO-TRANSLATED:9a252e21]
// The http client has disconnected and does not need to reply
return;
}
if (!err_msg.empty()) {
// 文件鉴权失败 [AUTO-TRANSLATED:0feb8885]
// File authentication failed
StrCaseMap headerOut;
if (cookie) {
headerOut["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
}
cb(401, "text/html", headerOut, std::make_shared<HttpStringBody>(err_msg));
return;
}
auto response_file = [is_hls](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &file_path, const Parser &parser, const string &file_content = "") {
StrCaseMap httpHeader;
if (cookie) {
httpHeader["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
}
HttpSession::HttpResponseInvoker invoker = [&](int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
if (cookie && body) {
auto& attach = cookie->getAttach<HttpCookieAttachment>();
if (attach._hls_data) {
attach._hls_data->addByteUsage(body->remainSize());
}
}
cb(code, HttpFileManager::getContentType(file_path.data()), headerOut, body);
};
GET_CONFIG_FUNC(vector<string>, forbidCacheSuffix, Http::kForbidCacheSuffix, [](const string &str) {
return split(str, ",");
});
bool is_forbid_cache = false;
for (auto &suffix : forbidCacheSuffix) {
if (suffix != "" && end_with(file_path, suffix)) {
is_forbid_cache = true;
break;
}
}
invoker.responseFile(parser.getHeader(), httpHeader, file_content.empty() ? file_path : file_content, !is_hls && !is_forbid_cache, file_content.empty());
};
if (!is_hls || !cookie) {
// 不是hls或访问m3u8文件不带cookie, 直接回复文件或404 [AUTO-TRANSLATED:64e5d19b]
// Not hls or accessing m3u8 files without cookies, directly reply to the file or 404
response_file(cookie, cb, file_path, parser);
if (is_hls) {
WarnL << "access m3u8 file without cookie:" << file_path;
}
return;
}
auto &attach = cookie->getAttach<HttpCookieAttachment>();
auto src = attach._hls_data->getMediaSource();
if (src) {
// 直接从内存获取m3u8索引文件(而不是从文件系统) [AUTO-TRANSLATED:c772e342]
// Get the m3u8 index file directly from memory (instead of from the file system)
response_file(cookie, cb, file_path, parser, src->getIndexFile());
return;
}
if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) {
// 最近已经查找过MediaSource了为了防止频繁查找导致占用全局互斥锁的问题我们尝试直接从磁盘返回hls索引文件 [AUTO-TRANSLATED:a33d5e4d]
// MediaSource has been searched recently, in order to prevent frequent searches from occupying the global mutex, we try to return the hls index file directly from the disk
response_file(cookie, cb, file_path, parser);
return;
}
// hls流可能未注册MediaSource::findAsync可以触发not_found事件然后再按需推拉流 [AUTO-TRANSLATED:f4acd717]
// The hls stream may not be registered, MediaSource::findAsync can trigger the not_found event, and then push and pull the stream on demand
MediaSource::findAsync(media_info, strongSession, [response_file, cookie, cb, file_path, parser](const MediaSource::Ptr &src) {
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
if (!hls) {
// 流不在线 [AUTO-TRANSLATED:5a6a5695]
// The stream is not online
response_file(cookie, cb, file_path, parser);
return;
}
auto &attach = cookie->getAttach<HttpCookieAttachment>();
attach._hls_data->setMediaSource(hls);
// 添加HlsMediaSource的观看人数(HLS是按需生成的这样可以触发HLS文件的生成) [AUTO-TRANSLATED:bd98e100]
// Add the number of viewers of HlsMediaSource (HLS is generated on demand, so this can trigger the generation of HLS files)
attach._hls_data->addByteUsage(0);
// 标记找到MediaSource [AUTO-TRANSLATED:1e298005]
// Mark that MediaSource has been found
attach._find_src = true;
// 重置查找MediaSource计时 [AUTO-TRANSLATED:d1e47e07]
// Reset the MediaSource search timer
attach._find_src_ticker.resetTime();
// m3u8文件可能不存在, 等待m3u8索引文件按需生成 [AUTO-TRANSLATED:0dbd4df2]
// The m3u8 file may not exist, wait for the m3u8 index file to be generated on demand
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
response_file(cookie, cb, file_path, parser, file);
});
});
});
}
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) {
return Parser::parseArgs(str, ";", ",");
});
string url, path, virtual_app;
auto it = virtualPathMap.find(media_info.app);
if (it != virtualPathMap.end()) {
// 访问的是virtualPath [AUTO-TRANSLATED:a36c7b20]
// Accessing virtualPath
path = it->second;
url = parser.url().substr(1 + media_info.app.size());
virtual_app = media_info.app + "/";
} else {
// 访问的是rootPath [AUTO-TRANSLATED:600765f0]
// Accessing rootPath
path = rootPath;
url = parser.url();
}
for (auto &ch : url) {
if (ch == '\\') {
// 如果url中存在"\"这种目录是Windows样式的需要批量转换为标准的"/"; 防止访问目录权限外的文件 [AUTO-TRANSLATED:fd6b5900]
// If the url contains "\", this directory is in Windows style; it needs to be converted to standard "/" in batches; prevent access to files outside the directory permissions
ch = '/';
}
}
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path);
if (!start_with(ret, http_root)) {
// 访问的http文件不得在http根目录之外 [AUTO-TRANSLATED:7d85a8f9]
// The accessed http file must not be outside the http root directory
throw std::runtime_error("Attempting to access files outside of the http root directory");
}
// 替换url防止返回的目录索引网页被注入非法内容 [AUTO-TRANSLATED:463ad1b1]
// Replace the url to prevent the returned directory index page from being injected with illegal content
const_cast<Parser&>(parser).setUrl("/" + virtual_app + ret.substr(http_root.size()));
NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
return ret;
}
/**
* 访
* @param sender
* @param parser http请求
* @param cb
* Access file or folder
* @param sender Event trigger
* @param parser http request
* @param cb Callback object
* [AUTO-TRANSLATED:a79c824d]
*/
void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) {
auto fullUrl = "http://" + parser["Host"] + parser.fullUrl();
MediaInfo media_info(fullUrl);
auto file_path = getFilePath(parser, media_info, sender);
if (file_path.size() == 0) {
sendNotFound(cb);
return;
}
// 访问的是文件夹 [AUTO-TRANSLATED:279974bb]
// Accessing a folder
if (File::is_dir(file_path)) {
auto indexFile = searchIndexFile(file_path);
if (!indexFile.empty()) {
// 发现该文件夹下有index文件 [AUTO-TRANSLATED:4a697758]
// Found index file in this folder
file_path = pathCat(file_path, indexFile);
if (!File::is_dir(file_path)) {
// 不是文件夹 [AUTO-TRANSLATED:af893469]
// Not a folder
parser.setUrl(pathCat(parser.url(), indexFile));
accessFile(sender, parser, media_info, file_path, cb);
return;
}
}
string strMenu;
// 生成文件夹菜单索引 [AUTO-TRANSLATED:04150cc8]
// Generate folder menu index
if (!makeFolderMenu(parser.url(), file_path, strMenu)) {
// 文件夹不存在 [AUTO-TRANSLATED:a2dc6c89]
// Folder does not exist
sendNotFound(cb);
return;
}
// 判断是否有权限访问该目录 [AUTO-TRANSLATED:963d02a6]
// Determine if there is permission to access this directory
canAccessPath(sender, parser, media_info, true, [strMenu, cb](const string &err_msg, const HttpServerCookie::Ptr &cookie) mutable{
if (!err_msg.empty()) {
strMenu = err_msg;
}
StrCaseMap headerOut;
if (cookie) {
headerOut["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
}
cb(err_msg.empty() ? 200 : 401, "text/html", headerOut, std::make_shared<HttpStringBody>(strMenu));
});
return;
}
// 访问的是文件 [AUTO-TRANSLATED:7a400b3c]
// Accessing a file
accessFile(sender, parser, media_info, file_path, cb);
};
////////////////////////////////////HttpResponseInvokerImp//////////////////////////////////////
void HttpResponseInvokerImp::operator()(int code, const StrCaseMap &headerOut, const Buffer::Ptr &body) const {
return operator()(code, headerOut, std::make_shared<HttpBufferBody>(body));
}
void HttpResponseInvokerImp::operator()(int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
if (_lambad) {
_lambad(code, headerOut, body);
}
}
void HttpResponseInvokerImp::operator()(int code, const StrCaseMap &headerOut, const string &body) const{
this->operator()(code, headerOut, std::make_shared<HttpStringBody>(body));
}
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
_lambad = lambda;
}
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
if (!lambda) {
_lambad = nullptr;
return;
}
_lambad = [lambda](int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
string str;
if (body && body->remainSize()) {
str = body->readData(body->remainSize())->toString();
}
lambda(code, headerOut, str);
};
}
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
const StrCaseMap &responseHeader,
const string &file,
bool use_mmap,
bool is_path) const {
if (!is_path) {
// file是文件内容 [AUTO-TRANSLATED:61d0be82]
// file is the file content
(*this)(200, responseHeader, std::make_shared<HttpStringBody>(file));
return;
}
// file是文件路径 [AUTO-TRANSLATED:28dcac38]
// file is the file path
GET_CONFIG(string, charSet, Http::kCharSet);
StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
auto fileBody = std::make_shared<HttpFileBody>(file, use_mmap);
if (fileBody->remainSize() < 0) {
// 打开文件失败 [AUTO-TRANSLATED:1f0405cb]
// Failed to open file
GET_CONFIG(string, notFound, Http::kNotFound);
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
httpHeader["Content-Type"] = strContentType;
(*this)(404, httpHeader, notFound);
return;
}
// 尝试添加Content-Type [AUTO-TRANSLATED:2c08b371]
// Try to add Content-Type
httpHeader.emplace("Content-Type", HttpConst::getHttpContentType(file.data()) + "; charset=" + charSet);
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
int code = 200;
if (!strRange.empty()) {
// 分节下载 [AUTO-TRANSLATED:01920230]
// Segmented download
code = 206;
auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data());
auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data());
auto fileSize = fileBody->remainSize();
if (iRangeEnd == 0) {
iRangeEnd = fileSize - 1;
}
// 设置文件范围 [AUTO-TRANSLATED:aa51fd28]
// Set file range
fileBody->setRange(iRangeStart, iRangeEnd - iRangeStart + 1);
// 分节下载返回Content-Range头 [AUTO-TRANSLATED:4b78e7b6]
// Segmented download returns Content-Range header
httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl);
}
// 回复文件 [AUTO-TRANSLATED:5d91a916]
// Reply file
(*this)(code, httpHeader, fileBody);
}
HttpResponseInvokerImp::operator bool(){
return _lambad.operator bool();
}
}//namespace mediakit

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_HTTPFILEMANAGER_H
#define ZLMEDIAKIT_HTTPFILEMANAGER_H
#include "HttpBody.h"
#include "HttpCookie.h"
#include "Common/Parser.h"
#include "Network/Session.h"
#include "Util/function_traits.h"
namespace mediakit {
class HttpResponseInvokerImp{
public:
typedef std::function<void(int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body)> HttpResponseInvokerLambda0;
typedef std::function<void(int code, const StrCaseMap &headerOut, const std::string &body)> HttpResponseInvokerLambda1;
template<typename C>
HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename toolkit::function_traits<C>::stl_function_type(c)) {}
HttpResponseInvokerImp(const HttpResponseInvokerLambda0 &lambda);
HttpResponseInvokerImp(const HttpResponseInvokerLambda1 &lambda);
void operator()(int code, const StrCaseMap &headerOut, const toolkit::Buffer::Ptr &body) const;
void operator()(int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const;
void operator()(int code, const StrCaseMap &headerOut, const std::string &body) const;
void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const std::string &file, bool use_mmap = true, bool is_path = true) const;
operator bool();
private:
HttpResponseInvokerLambda0 _lambad;
};
/**
* http静态文件夹服务器的访问权限
* This object is used to control access permissions for the http static folder server.
* [AUTO-TRANSLATED:2eb7e5f2]
*/
class HttpFileManager {
public:
typedef std::function<void(int code, const std::string &content_type, const StrCaseMap &responseHeader, const HttpBody::Ptr &body)> invoker;
/**
* 访
* @param sender
* @param parser http请求
* @param cb
* Access files or folders
* @param sender Event trigger
* @param parser http request
* @param cb Callback object
* [AUTO-TRANSLATED:669301a8]
*/
static void onAccessPath(toolkit::Session &sender, Parser &parser, const invoker &cb);
/**
* mime值
* @param name
* @return mime值
* Get mime value
* @param name File suffix
* @return mime value
* [AUTO-TRANSLATED:f1d25b59]
*/
static const std::string &getContentType(const char *name);
/**
* ip是否再白名单中
* @param ip ipv4和ipv6
* Whether this ip is in the whitelist
* @param ip Supports ipv4 and ipv6
* [AUTO-TRANSLATED:4c7756c3]
*/
static bool isIPAllowed(const std::string &ip);
private:
HttpFileManager() = delete;
~HttpFileManager() = delete;
};
}
#endif //ZLMEDIAKIT_HTTPFILEMANAGER_H

View File

@ -0,0 +1,181 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HttpRequestSplitter.h"
#include "Util/logger.h"
#include "Util/util.h"
using namespace toolkit;
using namespace std;
// 协议解析最大缓存4兆数据 [AUTO-TRANSLATED:75159526]
// Protocol parsing maximum cache 4MB data
static constexpr size_t kMaxCacheSize = 4 * 1024 * 1024;
namespace mediakit {
void HttpRequestSplitter::input(const char *data,size_t len) {
{
auto size = remainDataSize();
if (size > _max_cache_size) {
// 缓存太多数据无法处理则上抛异常 [AUTO-TRANSLATED:30e48e9e]
// If too much data is cached and cannot be processed, throw an exception
reset();
throw std::out_of_range("remain data size is too huge, now cleared:" + to_string(size));
}
}
const char *ptr = data;
if(!_remain_data.empty()){
_remain_data.append(data,len);
data = ptr = _remain_data.data();
len = _remain_data.size();
}
splitPacket:
/*确保ptr最后一个字节是0防止strstr越界
*ZLToolKit确保内存最后一个字节是保留未使用字节并置0
*0
*0
*Ensure the last byte of ptr is 0 to prevent strstr from going out of bounds
* Since ZLToolKit ensures that the last byte of memory is a reserved unused byte and set to 0,
* so there is no need to set it to 0 again here
* But the upper layer data may come from other channels, so it is better to set it to 0 for safety
* [AUTO-TRANSLATED:28ff47a5]
*/
char &tail_ref = ((char *) ptr)[len];
char tail_tmp = tail_ref;
tail_ref = 0;
// 数据按照请求头处理 [AUTO-TRANSLATED:e7a0dbb4]
// Data is processed according to the request header
const char *index = nullptr;
_remain_data_size = len;
while (_content_len == 0 && _remain_data_size > 0 && (index = onSearchPacketTail(ptr,_remain_data_size)) != nullptr) {
if (index == ptr) {
break;
}
if (index < ptr || index > ptr + _remain_data_size) {
throw std::out_of_range("上层分包逻辑异常");
}
// _content_len == 0这是请求头 [AUTO-TRANSLATED:32af637b]
// _content_len == 0, this is the request header
const char *header_ptr = ptr;
ssize_t header_size = index - ptr;
ptr = index;
_remain_data_size = len - (ptr - data);
_content_len = onRecvHeader(header_ptr, header_size);
}
/*
*
* HttpRequestSplitter::reset()
/*
* Restore the last byte
* Move it here to prevent HttpRequestSplitter::reset() from causing memory failure
* [AUTO-TRANSLATED:9c3e0597]
*/
tail_ref = tail_tmp;
if(_remain_data_size <= 0){
// 没有剩余数据,清空缓存 [AUTO-TRANSLATED:16613daa]
// No remaining data, clear the cache
_remain_data.clear();
return;
}
if(_content_len == 0){
// 尚未找到http头缓存定位到剩余数据部分 [AUTO-TRANSLATED:7a9d6205]
// HTTP header not found yet, cache is located at the remaining data part
_remain_data.assign(ptr,_remain_data_size);
return;
}
// 已经找到http头了 [AUTO-TRANSLATED:df166db7]
// HTTP header has been found
if(_content_len > 0){
// 数据按照固定长度content处理 [AUTO-TRANSLATED:7272b7e7]
// Data is processed according to fixed length content
if(_remain_data_size < (size_t)_content_len){
// 数据不够,缓存定位到剩余数据部分 [AUTO-TRANSLATED:61c32f5c]
// Insufficient data, cache is located at the remaining data part
_remain_data.assign(ptr, _remain_data_size);
return;
}
// 收到content数据并且接收content完毕 [AUTO-TRANSLATED:0342dc0e]
// Content data received and content reception completed
onRecvContent(ptr,_content_len);
_remain_data_size -= _content_len;
ptr += _content_len;
// content处理完毕,后面数据当做请求头处理 [AUTO-TRANSLATED:d268dfe4]
// Content processing completed, subsequent data is treated as request header
_content_len = 0;
if(_remain_data_size > 0){
// 还有数据没有处理完毕 [AUTO-TRANSLATED:1cac6727]
// There is still data that has not been processed
_remain_data.assign(ptr,_remain_data_size);
data = ptr = (char *)_remain_data.data();
len = _remain_data.size();
goto splitPacket;
}
_remain_data.clear();
return;
}
// _content_len < 0;数据按照不固定长度content处理 [AUTO-TRANSLATED:68d6a4d0]
// _content_len < 0; Data is processed according to variable length content
onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据
_remain_data.clear();
}
void HttpRequestSplitter::setContentLen(ssize_t content_len) {
_content_len = content_len;
}
void HttpRequestSplitter::reset() {
_content_len = 0;
_remain_data_size = 0;
_remain_data.clear();
}
const char *HttpRequestSplitter::onSearchPacketTail(const char *data,size_t len) {
auto pos = strstr(data,"\r\n\r\n");
if(pos == nullptr){
return nullptr;
}
return pos + 4;
}
size_t HttpRequestSplitter::remainDataSize() {
return _remain_data_size;
}
const char *HttpRequestSplitter::remainData() const {
return _remain_data.data();
}
void HttpRequestSplitter::setMaxCacheSize(size_t max_cache_size) {
if (!max_cache_size) {
max_cache_size = kMaxCacheSize;
}
_max_cache_size = max_cache_size;
}
HttpRequestSplitter::HttpRequestSplitter() {
setMaxCacheSize(0);
}
} /* namespace mediakit */

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_HTTPREQUESTSPLITTER_H
#define ZLMEDIAKIT_HTTPREQUESTSPLITTER_H
#include <string>
#include "Network/Buffer.h"
namespace mediakit {
class HttpRequestSplitter {
public:
HttpRequestSplitter();
virtual ~HttpRequestSplitter() = default;
/**
*
* @param data
* @param len
* @warning len + 1, 使 strstr , , @p len + 1 '\0' .
* Add data
* @param data Data to be added
* @param len Data length
* @warning Actual memory must be no less than len + 1. strstr is used internally for searching. To prevent out-of-bounds search, a '\0' terminator is set at the @p len + 1 position.
* [AUTO-TRANSLATED:3bbfc2ab]
*/
virtual void input(const char *data, size_t len);
/**
*
* Restore initial settings
* [AUTO-TRANSLATED:f797ec5a]
*/
void reset();
/**
*
* Remaining data size
* [AUTO-TRANSLATED:808a9399]
*/
size_t remainDataSize();
/**
*
* Get remaining data pointer
* [AUTO-TRANSLATED:e419f28a]
*/
const char *remainData() const;
/**
*
* Set maximum cache size
* [AUTO-TRANSLATED:19333c32]
*/
void setMaxCacheSize(size_t max_cache_size);
protected:
/**
*
* @param data
* @param len
*
* @return content长度,
* <0 : contentcontent将分段通过onRecvContent函数回调出去
* 0 : ,
* >0 : content,content并等到所有content接收完毕一次性通过onRecvContent函数回调出去
* Receive request header
* @param data Request header data
* @param len Request header length
*
* @return Content length after request header,
* <0 : Represents that all subsequent data is content, in which case the subsequent content will be called back in segments through the onRecvContent function
* 0 : Represents that the subsequent data is still the request header,
* >0 : Represents that the subsequent data is fixed-length content, in which case the content will be cached and called back through the onRecvContent function once all content is received
* [AUTO-TRANSLATED:f185e6c5]
*/
virtual ssize_t onRecvHeader(const char *data,size_t len) = 0;
/**
* content分片或全部数据
* onRecvHeader函数返回>0,
* @param data content分片或全部数据
* @param len
* Receive content fragments or all data
* onRecvHeader function returns >0, then it is all data
* @param data Content fragments or all data
* @param len Data length
* [AUTO-TRANSLATED:2ef280fb]
*/
virtual void onRecvContent(const char *data,size_t len) {};
/**
*
* @param data
* @param len
* @return nullptr代表未找到包位
* Determine if there is a packet tail in the data
* @param data Data pointer
* @param len Data length
* @return nullptr represents that the packet position is not found, otherwise returns the packet tail pointer
* [AUTO-TRANSLATED:f7190dec]
*/
virtual const char *onSearchPacketTail(const char *data, size_t len);
/**
* content len
* Set content len
* [AUTO-TRANSLATED:6dce48f8]
*/
void setContentLen(ssize_t content_len);
private:
ssize_t _content_len = 0;
size_t _max_cache_size = 0;
size_t _remain_data_size = 0;
toolkit::BufferLikeString _remain_data;
};
} /* namespace mediakit */
#endif //ZLMEDIAKIT_HTTPREQUESTSPLITTER_H

View File

@ -0,0 +1,291 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HttpRequester.h"
#include "Util/onceToken.h"
#include "Util/NoticeCenter.h"
#include <memory>
using namespace std;
using namespace toolkit;
namespace mediakit {
void HttpRequester::onResponseHeader(const string &status, const HttpHeader &headers) {
_res_body.clear();
}
void HttpRequester::onResponseBody(const char *buf, size_t size) {
_res_body.append(buf, size);
}
void HttpRequester::onResponseCompleted(const SockException &ex) {
if (ex && _retry++ < _max_retry) {
std::weak_ptr<HttpRequester> weak_self = std::static_pointer_cast<HttpRequester>(shared_from_this());
getPoller()->doDelayTask(_retry_delay, [weak_self](){
if (auto self = weak_self.lock()) {
InfoL << "resend request " << self->getUrl() << " with retry " << self->getRetry();
self->sendRequest(self->getUrl());
}
return 0;
});
return ;
}
const_cast<Parser &>(response()).setContent(std::move(_res_body));
if (_on_result) {
_on_result(ex, response());
_on_result = nullptr;
}
}
void HttpRequester::setRetry(size_t count, size_t delay) {
InfoL << "setRetry max=" << count << ", delay=" << delay;
_max_retry = count;
_retry_delay = delay;
}
void HttpRequester::startRequester(const string &url, const HttpRequesterResult &on_result, float timeout_sec) {
_on_result = on_result;
_retry = 0;
setCompleteTimeout(timeout_sec * 1000);
sendRequest(url);
}
void HttpRequester::clear() {
HttpClientImp::clear();
_res_body.clear();
_on_result = nullptr;
}
void HttpRequester::setOnResult(const HttpRequesterResult &onResult) {
_on_result = onResult;
}
////////////////////////////////////////////////////////////////////////
#if !defined(DISABLE_REPORT)
static constexpr auto s_report_url = "http://report.zlmediakit.com:8888/index/api/report";
extern const char kServerName[];
static std::string httpBody() {
HttpArgs args;
auto &os = args["os"];
#if defined(_WIN32)
os = "windows";
#elif defined(__ANDROID__)
os = "android";
#elif defined(__linux__)
os = "linux";
#elif defined(OS_IPHONE)
os = "ios";
#elif defined(__MACH__)
os = "macos";
#else
os = "unknow";
#endif
#if (defined(_WIN32) && !defined(WIN32))
#define WIN32 _WIN32
#elif (defined(WIN32) && !defined(_WIN32))
#define _WIN32 WIN32
#endif
#if (defined(_WIN32) && !defined(_MSC_VER) && !defined(_WIN64))
#ifndef __i386__
#define __i386__
#endif
#elif defined(_MSC_VER)
#if (defined(_M_IX86) && !defined(__i386__))
#define __i386__
#endif
#endif
#ifndef __i386__
#if (defined(__386__) || defined(__I386__) || _M_IX86)
#define __i386__
#endif
#endif
#if (defined(__i386__) && !defined(__I386__))
#define __I386__
#endif
#if (defined(__x86_64__) && !defined(__x86_64))
#define __x86_64
#endif
#if (defined(__x86_64) && !defined(__x86_64__))
#define __x86_64__
#endif
#if (defined(_M_AMD64)) && (!defined(__amd64__))
#define __amd64__
#endif
#if (defined(__amd64) && !defined(__amd64__))
#define __amd64__
#endif
#if (defined(__amd64__) && !defined(__amd64))
#define __amd64
#endif
#if (defined(_M_ARM64) && !defined(__arm64__))
#define __arm64__
#endif
#if (defined(_M_X64) && !defined(__x86_64__))
#define __x86_64__
#endif
#if (defined(_M_ARM) && !defined(__arm__))
#define __arm__
#endif
#if (defined(__i386__) || defined(__amd64__)) && (!defined(__x86__))
#if !(defined(_MSC_VER) && defined(__amd64__))
#define __x86__ // MSVC doesn't support inline assembly in x64
#endif
#endif
auto &arch = args["arch"];
#if defined(__x86_64__) || defined(__amd64__)
arch = "x86_64";
#elif defined(__x86__) || defined(__X86__) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__)
arch = "x86";
#elif defined(__arm64__) || defined(__aarch64__)
arch = "arm64";
#elif defined(__arm__)
arch = "arm";
#elif defined(__loognarch__)
arch = "loognarch";
#elif defined(__riscv) || defined(__riscv__)
arch = "riscv";
#elif defined(__mipsl__) || defined(__mips__)
arch = "mipsl";
#else
arch = "unknow";
#endif
auto &compiler = args["compiler"];
#if defined(__clang__)
compiler = "clang";
#elif defined(_MSC_VER)
compiler = "msvc";
#elif defined(__MINGW32__)
compiler = "mingw";
#elif defined(__CYGWIN__)
compiler = "cygwin";
#elif defined(__GNUC__)
compiler = "gcc";
#elif defined(__ICC__)
compiler = "icc";
#else
compiler = "unknow";
#endif
args["cplusplus"] = __cplusplus;
args["build_date"] = __DATE__;
args["version"] = kServerName;
args["exe_name"] = exeName();
args["start_time"] = getTimeStr("%Y-%m-%d %H:%M:%S");
#if NDEBUG
args["release"] = 1;
#else
args["release"] = 0;
#endif
#if ENABLE_RTPPROXY
args["rtp_proxy"] = 1;
#else
args["rtp_proxy"] = 0;
#endif
#if ENABLE_HLS
args["hls"] = 1;
#else
args["hls"] = 0;
#endif
#if ENABLE_WEBRTC
args["webrtc"] = 1;
#else
args["webrtc"] = 0;
#endif
#if ENABLE_SCTP
args["sctp"] = 1;
#else
args["sctp"] = 0;
#endif
#if ENABLE_SRT
args["srt"] = 1;
#else
args["srt"] = 0;
#endif
#if ENABLE_MP4
args["mp4"] = 1;
#else
args["mp4"] = 0;
#endif
#if ENABLE_OPENSSL
args["openssl"] = 1;
#else
args["openssl"] = 0;
#endif
#if ENABLE_FFMPEG
args["ffmpeg"] = 1;
#else
args["ffmpeg"] = 0;
#endif
args["rand_str"] = makeRandStr(32);
for (auto &pr : mINI::Instance()) {
// 只获取转协议相关配置 [AUTO-TRANSLATED:4e1e5840]
// Only get the configuration related to protocol conversion
if (pr.first.find("protocol.") == 0) {
args[pr.first] = pr.second;
}
}
return args.make();
}
static void sendReport() {
static HttpRequester::Ptr requester = std::make_shared<HttpRequester>();
// 获取一次静态信息,定时上报主要方便统计在线实例个数 [AUTO-TRANSLATED:1612d609]
// Get static information once, and report it regularly to facilitate statistics of the number of online instances
static auto body = httpBody();
requester->setMethod("POST");
requester->setBody(body);
// http超时时间设置为30秒 [AUTO-TRANSLATED:466f9b71]
// Set the http timeout to 30 seconds
requester->startRequester(s_report_url, nullptr, 30);
}
static toolkit::onceToken s_token([]() {
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) {
// 第一次汇报在程序启动后5分钟 [AUTO-TRANSLATED:02e6c508]
// The first report is 5 minutes after the program starts
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
sendReport();
// 后续每一个小时汇报一次 [AUTO-TRANSLATED:0322b8aa]
// Report every hour afterwards
return 60 * 60 * 1000;
});
});
});
#endif
} // namespace mediakit

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef Htt_HttpRequester_h
#define Htt_HttpRequester_h
#include "HttpClientImp.h"
namespace mediakit {
class HttpRequester : public HttpClientImp {
public:
using Ptr = std::shared_ptr<HttpRequester>;
using HttpRequesterResult = std::function<void(const toolkit::SockException &ex, const Parser &response)>;
void setOnResult(const HttpRequesterResult &onResult);
void startRequester(const std::string &url, const HttpRequesterResult &on_result, float timeout_sec = 10);
void setRetry(size_t count, size_t delay);
size_t getRetry() const { return _retry; }
size_t getRetryDelay() const { return _retry_delay; }
size_t getMaxRetry() const { return _max_retry; }
void clear() override;
private:
void onResponseHeader(const std::string &status, const HttpHeader &headers) override;
void onResponseBody(const char *buf, size_t size) override;
void onResponseCompleted(const toolkit::SockException &ex) override;
private:
size_t _retry = 0;
size_t _max_retry = 0;
size_t _retry_delay = 2000; // ms
std::string _res_body;
HttpRequesterResult _on_result;
};
}//namespace mediakit
#endif /* Htt_HttpRequester_h */

View File

@ -0,0 +1,891 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <stdio.h>
#include <sys/stat.h>
#include <algorithm>
#include "Common/config.h"
#include "Common/strCoding.h"
#include "HttpSession.h"
#include "HttpConst.h"
#include "Util/base64.h"
#include "Util/SHA1.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) {
// 设置默认参数 [AUTO-TRANSLATED:ae5b72e6]
// Set default parameters
setMaxReqSize(0);
setTimeoutSec(0);
}
void HttpSession::onHttpRequest_HEAD() {
// 暂时全部返回200 OK因为HTTP GET存在按需生成流的操作所以不能按照HTTP GET的流程返回 [AUTO-TRANSLATED:0ce05db5]
// Temporarily return 200 OK for all, because HTTP GET has on-demand generation stream operations, so it cannot return according to the HTTP GET process
// 如果直接返回404那么又会导致按需生成流的逻辑失效所以HTTP HEAD在静态文件或者已存在资源时才有效 [AUTO-TRANSLATED:ea2b6faa]
// If you return 404 directly, it will also cause the on-demand generation stream logic to fail, so HTTP HEAD is only valid for static files or existing resources
// 对于按需生成流的直播场景并不适用 [AUTO-TRANSLATED:5a47bf00]
// Not applicable to live streaming scenarios that generate streams on demand
sendResponse(200, false);
}
void HttpSession::onHttpRequest_OPTIONS() {
KeyValue header;
header.emplace("Allow", "GET, POST, HEAD, OPTIONS");
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
if (allow_cross_domains) {
header.emplace("Access-Control-Allow-Origin", "*");
header.emplace("Access-Control-Allow-Headers", "*");
header.emplace("Access-Control-Allow-Methods", "GET, POST, HEAD, OPTIONS");
}
header.emplace("Access-Control-Allow-Credentials", "true");
header.emplace("Access-Control-Request-Methods", "GET, POST, OPTIONS");
header.emplace("Access-Control-Request-Headers", "Accept,Accept-Language,Content-Language,Content-Type");
sendResponse(200, true, nullptr, header);
}
ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
using func_type = void (HttpSession::*)();
static unordered_map<string, func_type> s_func_map;
static onceToken token([]() {
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
// DELETE命令用于whip/whep用只用于触发http api [AUTO-TRANSLATED:f3b7aaea]
// DELETE command is used for whip/whep, only used to trigger http api
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD);
s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS);
});
_parser.parse(header, len);
CHECK(_parser.url()[0] == '/');
_origin = _parser["Origin"];
urlDecode(_parser);
auto &cmd = _parser.method();
auto it = s_func_map.find(cmd);
if (it == s_func_map.end()) {
WarnP(this) << "Http method not supported: " << cmd;
sendResponse(405, true);
return 0;
}
size_t content_len;
auto &content_len_str = _parser["Content-Length"];
if (content_len_str.empty()) {
if (it->first == "POST") {
// Http post未指定长度我们认为是不定长的body [AUTO-TRANSLATED:3578206b]
// Http post does not specify length, we consider it to be an indefinite length body
WarnL << "Received http post request without content-length, consider it to be unlimited length";
content_len = SIZE_MAX;
} else {
content_len = 0;
}
} else {
// 已经指定长度 [AUTO-TRANSLATED:a360c374]
// Length has been specified
content_len = atoll(content_len_str.data());
}
if (content_len == 0) {
// // 没有body的情况直接触发回调 //// [AUTO-TRANSLATED:f2988336]
// // No body case, trigger callback directly ////
(this->*(it->second))();
_parser.clear();
// 如果设置了_on_recv_body, 那么说明后续要处理body [AUTO-TRANSLATED:2dac5fc2]
// If _on_recv_body is set, it means that the body will be processed later
return _on_recv_body ? -1 : 0;
}
if (content_len > _max_req_size) {
// // 不定长body或超大body //// [AUTO-TRANSLATED:8d66ee77]
// // Indefinite length body or oversized body ////
if (content_len != SIZE_MAX) {
WarnL << "Http body size is too huge: " << content_len << " > " << _max_req_size
<< ", please set " << Http::kMaxReqSize << " in config.ini file.";
}
size_t received = 0;
auto parser = std::move(_parser);
_on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable {
received += len;
onRecvUnlimitedContent(parser, data, len, content_len, received);
if (received < content_len) {
// 还没收满 [AUTO-TRANSLATED:cecc867e]
// Not yet received
return true;
}
// 收满了 [AUTO-TRANSLATED:0c9cebd7]
// Received full
setContentLen(0);
return false;
};
// 声明后续都是bodyHttp body在本对象缓冲不通过HttpRequestSplitter保存 [AUTO-TRANSLATED:0012b6c1]
// Declare that the following is all body; Http body is buffered in this object, not saved through HttpRequestSplitter
return -1;
}
// // body size明确指定且小于最大值的情况 //// [AUTO-TRANSLATED:f1f1ee5d]
// // Body size is explicitly specified and less than the maximum value ////
_on_recv_body = [this, it](const char *data, size_t len) mutable {
// 收集body完毕 [AUTO-TRANSLATED:981ad2c8]
// Body collection complete
_parser.setContent(std::string(data, len));
(this->*(it->second))();
_parser.clear();
// _on_recv_body置空 [AUTO-TRANSLATED:437a201a]
// _on_recv_body is cleared
return false;
};
// 声明body长度通过HttpRequestSplitter缓存然后一次性回调到_on_recv_body [AUTO-TRANSLATED:3b11cfb7]
// Declare the body length, cache it through HttpRequestSplitter and then callback to _on_recv_body at once
return content_len;
}
void HttpSession::onRecvContent(const char *data, size_t len) {
if (_on_recv_body && !_on_recv_body(data, len)) {
_on_recv_body = nullptr;
}
}
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
_ticker.resetTime();
input(pBuf->data(), pBuf->size());
}
void HttpSession::onError(const SockException &err) {
if (_is_live_stream) {
// flv/ts播放器 [AUTO-TRANSLATED:5b444fd9]
// flv/ts player
uint64_t duration = _ticker.createdTime() / 1000;
WarnP(this) << "FLV/TS/FMP4播放器(" << _media_info.shortUrl() << ")断开:" << err << ",耗时(s):" << duration;
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_total_bytes_usage >= iFlowThreshold * 1024) {
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes_usage, duration, true, *this);
}
return;
}
}
void HttpSession::setTimeoutSec(size_t keep_alive_sec) {
if (!keep_alive_sec) {
GET_CONFIG(size_t, s_keep_alive_sec, Http::kKeepAliveSecond);
keep_alive_sec = s_keep_alive_sec;
}
_keep_alive_sec = keep_alive_sec;
getSock()->setSendTimeOutSecond(keep_alive_sec);
}
void HttpSession::setMaxReqSize(size_t max_req_size) {
if (!max_req_size) {
GET_CONFIG(size_t, s_max_req_size, Http::kMaxReqSize);
max_req_size = s_max_req_size;
}
_max_req_size = max_req_size;
setMaxCacheSize(max_req_size);
}
void HttpSession::onManager() {
if (_ticker.elapsedTime() > _keep_alive_sec * 1000) {
// http超时 [AUTO-TRANSLATED:6f2fdd1f]
// http timeout
shutdown(SockException(Err_timeout, "session timeout"));
}
}
bool HttpSession::checkWebSocket() {
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
if (Sec_WebSocket_Key.empty()) {
return false;
}
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
KeyValue headerOut;
headerOut["Upgrade"] = "websocket";
headerOut["Connection"] = "Upgrade";
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
if (!_parser["Sec-WebSocket-Protocol"].empty()) {
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
}
auto res_cb = [this, headerOut]() {
_live_over_websocket = true;
sendResponse(101, false, nullptr, headerOut, nullptr, true);
};
auto res_cb_flv = [this, headerOut]() mutable {
_live_over_websocket = true;
headerOut.emplace("Cache-Control", "no-store");
sendResponse(101, false, nullptr, headerOut, nullptr, true);
};
// 判断是否为websocket-flv [AUTO-TRANSLATED:31682d7a]
// Determine whether it is websocket-flv
if (checkLiveStreamFlv(res_cb_flv)) {
// 这里是websocket-flv直播请求 [AUTO-TRANSLATED:4bea5956]
// This is a websocket-flv live request
return true;
}
// 判断是否为websocket-ts [AUTO-TRANSLATED:9e8eb374]
// Determine whether it is websocket-ts
if (checkLiveStreamTS(res_cb)) {
// 这里是websocket-ts直播请求 [AUTO-TRANSLATED:8ab08dd6]
// This is a websocket-ts live request
return true;
}
// 判断是否为websocket-fmp4 [AUTO-TRANSLATED:318f793f]
// Determine whether it is websocket-fmp4
if (checkLiveStreamFMP4(res_cb)) {
// 这里是websocket-fmp4直播请求 [AUTO-TRANSLATED:ccf0c1e2]
// This is a websocket-fmp4 live request
return true;
}
// 这是普通的websocket连接 [AUTO-TRANSLATED:754721f8]
// This is a normal websocket connection
if (!onWebSocketConnect(_parser)) {
sendResponse(501, true, nullptr, headerOut);
return true;
}
sendResponse(101, false, nullptr, headerOut, nullptr, true);
return true;
}
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb) {
std::string url = _parser.url();
auto it = _parser.getUrlArgs().find("schema");
if (it != _parser.getUrlArgs().end()) {
if (strcasecmp(it->second.c_str(), schema.c_str())) {
// unsupported schema
return false;
}
} else {
auto prefix_size = url_suffix.size();
if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) {
// 未找到后缀 [AUTO-TRANSLATED:6635499a]
// Suffix not found
return false;
}
// url去除特殊后缀 [AUTO-TRANSLATED:31c0c080]
// Remove special suffix from url
url.resize(url.size() - prefix_size);
}
// 带参数的url [AUTO-TRANSLATED:074764b0]
// Url with parameters
if (!_parser.params().empty()) {
url += "?";
url += _parser.params();
}
// 解析带上协议+参数完整的url [AUTO-TRANSLATED:5cdc7e68]
// Parse the complete url with protocol + parameters
_media_info.parse(schema + "://" + _parser["Host"] + url);
if (_media_info.app.empty() || _media_info.stream.empty()) {
// url不合法 [AUTO-TRANSLATED:9aad134e]
// URL is invalid
return false;
}
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
// 鉴权结果回调 [AUTO-TRANSLATED:021df191]
// Authentication result callback
auto onRes = [cb, weak_self, close_flag](const string &err) {
auto strong_self = weak_self.lock();
if (!strong_self) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
if (!err.empty()) {
// 播放鉴权失败 [AUTO-TRANSLATED:64f99eeb]
// Playback authentication failed
strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
return;
}
// 异步查找直播流 [AUTO-TRANSLATED:7cde5dac]
// Asynchronously find live stream
MediaSource::findAsync(strong_self->_media_info, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
auto strong_self = weak_self.lock();
if (!strong_self) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
if (!src) {
// 未找到该流 [AUTO-TRANSLATED:2699ef82]
// Stream not found
strong_self->sendNotFound(close_flag);
} else {
strong_self->_is_live_stream = true;
// 触发回调 [AUTO-TRANSLATED:ae2ff258]
// Trigger callback
cb(src);
}
});
};
Broadcast::AuthInvoker invoker = [weak_self, onRes](const string &err) {
if (auto strong_self = weak_self.lock()) {
strong_self->async([onRes, err]() { onRes(err); });
}
};
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this);
if (!flag) {
// 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae]
// No one is listening to this event, no authentication by default
onRes("");
}
return true;
}
// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 [AUTO-TRANSLATED:c0174f8f]
// http-fmp4 link format: http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(src);
assert(fmp4_src);
if (!cb) {
// 找到源发送http头负载后续发送 [AUTO-TRANSLATED:ac272410]
// Found the source, send the http header, and send the load later
sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
} else {
// 自定义发送http头 [AUTO-TRANSLATED:b8a8f683]
// Custom send http header
cb();
}
// 直播牺牲延时提升发送性能 [AUTO-TRANSLATED:7c6616c9]
// Live streaming sacrifices delay to improve sending performance
setSocketFlags();
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
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]() {
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) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached"));
});
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
auto strong_self = weak_self.lock();
if (!strong_self) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
size_t i = 0;
auto size = fmp4_list->size();
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
});
});
}
// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 [AUTO-TRANSLATED:aa1a9151]
// http-ts link format: http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
assert(ts_src);
if (!cb) {
// 找到源发送http头负载后续发送 [AUTO-TRANSLATED:ac272410]
// Found the source, send the http header, and send the load later
sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
} else {
// 自定义发送http头 [AUTO-TRANSLATED:b8a8f683]
// Custom send http header
cb();
}
// 直播牺牲延时提升发送性能 [AUTO-TRANSLATED:7c6616c9]
// Live streaming sacrifices delay to improve sending performance
setSocketFlags();
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]() {
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) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
strong_self->shutdown(SockException(Err_shutdown, "ts ring buffer detached"));
});
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
auto strong_self = weak_self.lock();
if (!strong_self) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
size_t i = 0;
auto size = ts_list->size();
ts_list->for_each([&](const TSPacket::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
});
});
}
// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2 [AUTO-TRANSLATED:7e78aa20]
// http-flv link format: http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb) {
auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data());
return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) {
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
assert(rtmp_src);
if (!cb) {
// 找到源发送http头负载后续发送 [AUTO-TRANSLATED:ac272410]
// Found the source, send the http header, and send the load later
KeyValue headerOut;
headerOut["Cache-Control"] = "no-store";
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true);
} else {
// 自定义发送http头 [AUTO-TRANSLATED:b8a8f683]
// Custom send http header
cb();
}
// 直播牺牲延时提升发送性能 [AUTO-TRANSLATED:7c6616c9]
// Live streaming sacrifices delay to improve sending performance
setSocketFlags();
// 非H264/AAC时打印警告日志防止用户提无效问题 [AUTO-TRANSLATED:59ee60df]
// Print warning log when it is not H264/AAC, to prevent users from raising invalid issues
auto tracks = src->getTracks(false);
for (auto &track : tracks) {
switch (track->getCodecId()) {
case CodecH264:
case CodecAAC: break;
default: {
WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName();
break;
}
}
}
start(getPoller(), rtmp_src, start_pts);
});
}
void HttpSession::onHttpRequest_GET() {
// 先看看是否为WebSocket请求 [AUTO-TRANSLATED:98cd3a86]
// First check if it is a WebSocket request
if (checkWebSocket()) {
// 后续都是websocket body数据 [AUTO-TRANSLATED:c4fcbdcf]
// The following are all websocket body data
_on_recv_body = [this](const char *data, size_t len) {
WebSocketSplitter::decode((uint8_t *)data, len);
// _contentCallBack是可持续的后面还要处理后续数据 [AUTO-TRANSLATED:920e8c23]
// _contentCallBack is sustainable, and subsequent data needs to be processed later
return true;
};
return;
}
if (emitHttpEvent(false)) {
// 拦截http api事件 [AUTO-TRANSLATED:2f5e319d]
// Intercept http api events
return;
}
if (checkLiveStreamFlv()) {
// 拦截http-flv播放器 [AUTO-TRANSLATED:299f6449]
// Intercept http-flv player
return;
}
if (checkLiveStreamTS()) {
// 拦截http-ts播放器 [AUTO-TRANSLATED:d9e303e4]
// Intercept http-ts player
return;
}
if (checkLiveStreamFMP4()) {
// 拦截http-fmp4播放器 [AUTO-TRANSLATED:78cdf3a1]
// Intercept http-fmp4 player
return;
}
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
HttpFileManager::onAccessPath(*this, _parser, [weak_self, bClose](int code, const string &content_type,
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strong_self->async([weak_self, bClose, code, content_type, responseHeader, body]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strong_self->sendResponse(code, bClose, content_type.data(), responseHeader, body);
});
});
}
static string dateStr() {
char buf[64];
time_t tt = time(NULL);
strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
return buf;
}
class AsyncSenderData {
public:
friend class AsyncSender;
using Ptr = std::shared_ptr<AsyncSenderData>;
AsyncSenderData(HttpSession::Ptr session, const HttpBody::Ptr &body, bool close_when_complete) {
_session = std::move(session);
_body = body;
_close_when_complete = close_when_complete;
}
private:
std::weak_ptr<HttpSession> _session;
HttpBody::Ptr _body;
bool _close_when_complete;
bool _read_complete = false;
};
class AsyncSender {
public:
using Ptr = std::shared_ptr<AsyncSender>;
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
if (data->_read_complete) {
if (data->_close_when_complete) {
// 发送完毕需要关闭socket [AUTO-TRANSLATED:fe660e55]
// Close socket after sending is complete
shutdown(data->_session.lock());
}
return false;
}
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
auto session = data->_session.lock();
if (!session) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
session->async([data, sendBuf]() {
auto session = data->_session.lock();
if (!session) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
onRequestData(data, session, sendBuf);
}, false);
});
return true;
}
private:
static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
session->_ticker.resetTime();
if (sendBuf && session->send(sendBuf) != -1) {
// 文件还未读完,还需要继续发送 [AUTO-TRANSLATED:c454ca1a]
// The file has not been read completely, and needs to be sent continuously
if (!session->isSocketBusy()) {
// socket还可写继续请求数据 [AUTO-TRANSLATED:041df414]
// Socket can still write, continue to request data
onSocketFlushed(data);
}
return;
}
// 文件写完了 [AUTO-TRANSLATED:a9f8c117]
// The file is written
data->_read_complete = true;
if (!session->isSocketBusy() && data->_close_when_complete) {
shutdown(session);
}
}
static void shutdown(const std::shared_ptr<HttpSession> &session) {
if (session) {
session->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed."));
}
}
};
void HttpSession::sendResponse(int code,
bool bClose,
const char *pcContentType,
const HttpSession::KeyValue &header,
const HttpBody::Ptr &body,
bool no_content_length) {
GET_CONFIG(string, charSet, Http::kCharSet);
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
// body默认为空 [AUTO-TRANSLATED:527ccb6f]
// Body defaults to empty
int64_t size = 0;
if (body && body->remainSize()) {
// 有body获取body大小 [AUTO-TRANSLATED:0d5f4b9a]
// There is a body, get the body size
size = body->remainSize();
}
if (no_content_length) {
// http-flv直播是Keep-Alive类型 [AUTO-TRANSLATED:0ef3adfe]
// Http-flv live broadcast is Keep-Alive type
bClose = false;
} else if ((size_t)size >= SIZE_MAX || size < 0) {
// 不固定长度的body那么发送完body后应该关闭socket以便浏览器做下载完毕的判断 [AUTO-TRANSLATED:fc714997]
// If the body is not fixed length, then the socket should be closed after sending the body, so that the browser can judge the download completion
bClose = true;
}
HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
headerOut.emplace("Date", dateStr());
headerOut.emplace("Server", kServerName);
headerOut.emplace("Connection", bClose ? "close" : "keep-alive");
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
if (allow_cross_domains && !_origin.empty()) {
headerOut.emplace("Access-Control-Allow-Origin", _origin);
headerOut.emplace("Access-Control-Allow-Credentials", "true");
}
if (!bClose) {
string keepAliveString = "timeout=";
keepAliveString += to_string(keepAliveSec);
keepAliveString += ", max=100";
headerOut.emplace("Keep-Alive", std::move(keepAliveString));
}
if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) {
// 文件长度为固定值,且不是http-flv强制设置Content-Length [AUTO-TRANSLATED:185c02a8]
// The file length is a fixed value, and it is not http-flv that forcibly sets Content-Length
headerOut["Content-Length"] = to_string(size);
}
if (size && !pcContentType) {
// 有body时设置缺省类型 [AUTO-TRANSLATED:21c9b233]
// When there is a body, set the default type
pcContentType = "text/plain";
}
if ((size || no_content_length) && pcContentType) {
// 有body时设置文件类型 [AUTO-TRANSLATED:0dcbeecc]
// When there is a body, set the file type
string strContentType = pcContentType;
strContentType += "; charset=";
strContentType += charSet;
headerOut.emplace("Content-Type", std::move(strContentType));
}
// 发送http头 [AUTO-TRANSLATED:cca51598]
// Send http header
string str;
str.reserve(256);
str += "HTTP/1.1 ";
str += to_string(code);
str += ' ';
str += HttpConst::getHttpStatusMessage(code);
str += "\r\n";
for (auto &pr : header) {
str += pr.first;
str += ": ";
str += pr.second;
str += "\r\n";
}
str += "\r\n";
SockSender::send(std::move(str));
_ticker.resetTime();
if (!size) {
// 没有body [AUTO-TRANSLATED:bf891e3a]
// No body
if (bClose) {
shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http header completed with status code:" << code));
}
return;
}
#if 0
// sendfile跟共享mmap相比并没有性能上的优势相反sendfile还有功能上的缺陷先屏蔽 [AUTO-TRANSLATED:4de77827]
// Sendfile has no performance advantage over shared mmap, on the contrary, sendfile also has functional defects, so it is blocked first
if (typeid(*this) == typeid(HttpSession) && !body->sendFile(getSock()->rawFD())) {
// http支持sendfile优化 [AUTO-TRANSLATED:04f691f1]
// Http supports sendfile optimization
return;
}
#endif
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
if (body->remainSize() > sendBufSize) {
// 文件下载提升发送性能 [AUTO-TRANSLATED:500922cc]
// File download improves sending performance
setSocketFlags();
}
// 发送http body [AUTO-TRANSLATED:e9fc35d6]
// Send http body
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(static_pointer_cast<HttpSession>(shared_from_this()), body, bClose);
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
AsyncSender::onSocketFlushed(data);
}
void HttpSession::urlDecode(Parser &parser) {
parser.setUrl(strCoding::UrlDecodePath(parser.url()));
for (auto &pr : _parser.getUrlArgs()) {
const_cast<string &>(pr.second) = strCoding::UrlDecodeComponent(pr.second);
}
}
bool HttpSession::emitHttpEvent(bool doInvoke) {
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
// ///////////////////异步回复Invoker/////////////////////////////// [AUTO-TRANSLATED:6d0c5fda]
// ///////////////////Asynchronous reply Invoker///////////////////////////////
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
HttpResponseInvoker invoker = [weak_self, bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strong_self->async([weak_self, bClose, code, headerOut, body]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
// This object has been destroyed
return;
}
strong_self->sendResponse(code, bClose, nullptr, headerOut, body);
});
};
// /////////////////广播HTTP事件/////////////////////////// [AUTO-TRANSLATED:fff9769c]
// /////////////////Broadcast HTTP event///////////////////////////
bool consumed = false; // 该事件是否被消费
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
if (!consumed && doInvoke) {
// 该事件无人消费所以返回404 [AUTO-TRANSLATED:8a890dec]
// This event is not consumed, so return 404
invoker(404, KeyValue(), HttpBody::Ptr());
}
return consumed;
}
std::string HttpSession::get_peer_ip() {
GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader);
if (!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()) {
return _parser.getHeader()[forwarded_ip_header];
}
return Session::get_peer_ip();
}
void HttpSession::onHttpRequest_POST() {
emitHttpEvent(true);
}
void HttpSession::sendNotFound(bool bClose) {
GET_CONFIG(string, notFound, Http::kNotFound);
sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared<HttpStringBody>(notFound));
}
void HttpSession::setSocketFlags() {
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
if (mergeWriteMS > 0) {
// 推流模式下关闭TCP_NODELAY会增加推流端的延时但是服务器性能将提高 [AUTO-TRANSLATED:c8ec8fb8]
// In push mode, closing TCP_NODELAY will increase the delay of the push end, but the server performance will be improved
SockUtil::setNoDelay(getSock()->rawFD(), false);
// 播放模式下开启MSG_MORE会增加延时但是能提高发送性能 [AUTO-TRANSLATED:7b558ab9]
// In playback mode, enabling MSG_MORE will increase the delay, but it can improve sending performance
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
}
}
void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
if (flush) {
// 需要flush那么一次刷新缓存 [AUTO-TRANSLATED:8d1ec961]
// Need to flush, then flush the cache once
HttpSession::setSendFlushFlag(true);
}
_ticker.resetTime();
if (!_live_over_websocket) {
_total_bytes_usage += buffer->size();
send(buffer);
} else {
WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = WebSocketHeader::BINARY;
header._mask_flag = false;
WebSocketSplitter::encode(header, buffer);
}
if (flush) {
// 本次刷新缓存后,下次不用刷新缓存 [AUTO-TRANSLATED:f56139f7]
// After this cache flush, the next time you don't need to flush the cache
HttpSession::setSendFlushFlag(false);
}
}
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer) {
_total_bytes_usage += buffer->size();
send(std::move(buffer));
}
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in) {
WebSocketHeader &header = const_cast<WebSocketHeader &>(header_in);
header._mask_flag = false;
switch (header._opcode) {
case WebSocketHeader::CLOSE: {
encode(header, nullptr);
shutdown(SockException(Err_shutdown, "recv close request from client"));
break;
}
default: break;
}
}
void HttpSession::onDetach() {
shutdown(SockException(Err_shutdown, "rtmp ring buffer detached"));
}
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr() {
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
}
} /* namespace mediakit */

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_HTTP_HTTPSESSION_H_
#define SRC_HTTP_HTTPSESSION_H_
#include <functional>
#include "Network/Session.h"
#include "Rtmp/FlvMuxer.h"
#include "HttpRequestSplitter.h"
#include "WebSocketSplitter.h"
#include "HttpCookieManager.h"
#include "HttpFileManager.h"
#include "TS/TSMediaSource.h"
#include "FMP4/FMP4MediaSource.h"
namespace mediakit {
class HttpSession: public toolkit::Session,
public FlvMuxer,
public HttpRequestSplitter,
public WebSocketSplitter {
public:
using Ptr = std::shared_ptr<HttpSession>;
using KeyValue = StrCaseMap;
using HttpResponseInvoker = HttpResponseInvokerImp ;
friend class AsyncSender;
/**
* @param errMsg
* @param accessPath 访
* @param cookieLifeSecond cookie有效期
* @param errMsg If empty, it means authentication passed, otherwise it is an error message
* @param accessPath The root directory to run or prohibit access
* @param cookieLifeSecond Authentication cookie validity period
*
* [AUTO-TRANSLATED:2e733a35]
**/
using HttpAccessPathInvoker = std::function<void(const std::string &errMsg,const std::string &accessPath, int cookieLifeSecond)>;
HttpSession(const toolkit::Socket::Ptr &pSock);
void onRecv(const toolkit::Buffer::Ptr &) override;
void onError(const toolkit::SockException &err) override;
void onManager() override;
void setTimeoutSec(size_t second);
void setMaxReqSize(size_t max_req_size);
protected:
//FlvMuxer override
void onWrite(const toolkit::Buffer::Ptr &data, bool flush) override ;
void onDetach() override;
std::shared_ptr<FlvMuxer> getSharedPtr() override;
//HttpRequestSplitter override
ssize_t onRecvHeader(const char *data,size_t len) override;
void onRecvContent(const char *data,size_t len) override;
/**
* content
* http-flv推流
* @param header http请求头
* @param data content分片数据
* @param len content分片数据大小
* @param totalSize content总大小,0content
* @param recvedSize
* Overload for handling indefinite length content
* This function can be used to handle large file uploads, http-flv streaming
* @param header http request header
* @param data content fragment data
* @param len content fragment data size
* @param totalSize total content size, if 0, it is unlimited length content
* @param recvedSize received data size
* [AUTO-TRANSLATED:ee75080d]
*/
virtual void onRecvUnlimitedContent(const Parser &header,
const char *data,
size_t len,
size_t totalSize,
size_t recvedSize){
shutdown(toolkit::SockException(toolkit::Err_shutdown,"http post content is too huge,default closed"));
}
/**
* websocket客户端连接上事件
* @param header http头
* @return true代表允许websocket连接
* websocket client connection event
* @param header http header
* @return true means allow websocket connection, otherwise refuse
* [AUTO-TRANSLATED:d857fb0f]
*/
virtual bool onWebSocketConnect(const Parser &header){
WarnP(this) << "http server do not support websocket default";
return false;
}
//WebSocketSplitter override
/**
* websocket协议打包后回调
* @param buffer websocket协议数据
* Callback after sending data for websocket protocol packaging
* @param buffer websocket protocol data
* [AUTO-TRANSLATED:48b3b028]
*/
void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer) override;
/**
* webSocket数据包后回调
* @param header
* Callback after receiving a complete webSocket data packet
* @param header data packet header
* [AUTO-TRANSLATED:f506a7c5]
*/
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override;
// 重载获取客户端ip [AUTO-TRANSLATED:6e497ea4]
// Overload to get client ip
std::string get_peer_ip() override;
private:
void onHttpRequest_GET();
void onHttpRequest_POST();
void onHttpRequest_HEAD();
void onHttpRequest_OPTIONS();
bool checkLiveStream(const std::string &schema, const std::string &url_suffix, const std::function<void(const MediaSource::Ptr &src)> &cb);
bool checkLiveStreamFlv(const std::function<void()> &cb = nullptr);
bool checkLiveStreamTS(const std::function<void()> &cb = nullptr);
bool checkLiveStreamFMP4(const std::function<void()> &fmp4_list = nullptr);
bool checkWebSocket();
bool emitHttpEvent(bool doInvoke);
void urlDecode(Parser &parser);
void sendNotFound(bool bClose);
void sendResponse(int code, bool bClose, const char *pcContentType = nullptr,
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
const HttpBody::Ptr &body = nullptr, bool no_content_length = false);
// 设置socket标志 [AUTO-TRANSLATED:4086e686]
// Set socket flag
void setSocketFlags();
protected:
MediaInfo _media_info;
private:
bool _is_live_stream = false;
bool _live_over_websocket = false;
// 超时时间 [AUTO-TRANSLATED:f15e2672]
// Timeout
size_t _keep_alive_sec = 0;
// 最大http请求字节大小 [AUTO-TRANSLATED:c1fbc8e5]
// Maximum http request byte size
size_t _max_req_size = 0;
// 消耗的总流量 [AUTO-TRANSLATED:45ad2785]
// Total traffic consumed
uint64_t _total_bytes_usage = 0;
// http请求中的 Origin字段 [AUTO-TRANSLATED:7b8dd2c0]
// Origin field in http request
std::string _origin;
Parser _parser;
toolkit::Ticker _ticker;
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
// 处理content数据的callback [AUTO-TRANSLATED:38890e8d]
// Callback to handle content data
std::function<bool (const char *data,size_t len) > _on_recv_body;
};
using HttpsSession = toolkit::SessionWithSSL<HttpSession>;
} /* namespace mediakit */
#endif /* SRC_HTTP_HTTPSESSION_H_ */

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HttpTSPlayer.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
HttpTSPlayer::HttpTSPlayer(const EventPoller::Ptr &poller) {
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
}
void HttpTSPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) {
if (status != "200" && status != "206") {
// http状态码不符合预期 [AUTO-TRANSLATED:2b6996f7]
// HTTP status code is not as expected
throw invalid_argument("bad http status code:" + status);
}
auto content_type = strToLower(const_cast<HttpClient::HttpHeader &>(header)["Content-Type"]);
if (content_type.find("video/mp2t") != 0 && content_type.find("video/mpeg") != 0 && content_type.find("application/octet-stream") != 0) {
WarnL << "may not a mpeg-ts video: " << content_type << ", url: " << getUrl();
}
}
void HttpTSPlayer::onResponseBody(const char *buf, size_t size) {
if (_on_segment) {
_on_segment(buf, size);
}
}
void HttpTSPlayer::onResponseCompleted(const SockException &ex) {
emitOnComplete(ex);
}
void HttpTSPlayer::emitOnComplete(const SockException &ex) {
if (_on_complete) {
_on_complete(ex);
_on_complete = nullptr;
}
}
void HttpTSPlayer::setOnComplete(onComplete cb) {
_on_complete = std::move(cb);
}
void HttpTSPlayer::setOnPacket(TSSegment::onSegment cb) {
_on_segment = std::move(cb);
}
} // namespace mediakit

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HTTP_HTTPTSPLAYER_H
#define HTTP_HTTPTSPLAYER_H
#include "Http/HttpDownloader.h"
#include "Player/MediaPlayer.h"
#include "Rtp/TSDecoder.h"
namespace mediakit {
// http-ts播发器未实现ts解复用 [AUTO-TRANSLATED:cecbd6e7]
// http-ts broadcaster, ts demultiplexing not implemented
class HttpTSPlayer : public HttpClientImp {
public:
using Ptr = std::shared_ptr<HttpTSPlayer>;
using onComplete = std::function<void(const toolkit::SockException &)>;
HttpTSPlayer(const toolkit::EventPoller::Ptr &poller = nullptr);
/**
*
* Set the callback for download completion or abnormal disconnection
* [AUTO-TRANSLATED:4f25d583]
*/
void setOnComplete(onComplete cb);
/**
* ts包回调
* Set the callback for receiving ts packets
* [AUTO-TRANSLATED:af3044a1]
*/
void setOnPacket(TSSegment::onSegment cb);
protected:
///HttpClient override///
void onResponseHeader(const std::string &status, const HttpHeader &header) override;
void onResponseBody(const char *buf, size_t size) override;
void onResponseCompleted(const toolkit::SockException &ex) override;
private:
void emitOnComplete(const toolkit::SockException &ex);
private:
onComplete _on_complete;
TSSegment::onSegment _on_segment;
};
}//namespace mediakit
#endif //HTTP_HTTPTSPLAYER_H

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
* Created by alex on 2021/4/6.
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "TsPlayer.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
TsPlayer::TsPlayer(const EventPoller::Ptr &poller) : HttpTSPlayer(poller) {}
void TsPlayer::play(const string &url) {
TraceL << "play http-ts: " << url;
_play_result = false;
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
setProxyUrl((*this)[Client::kProxyUrl]);
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
setMethod("GET");
sendRequest(url);
}
void TsPlayer::teardown() {
shutdown(SockException(Err_shutdown, "teardown"));
}
void TsPlayer::onResponseCompleted(const SockException &ex) {
if (!_play_result) {
_play_result = true;
if (!ex && responseBodyTotalSize() == 0 && responseBodySize() == 0) {
//if the server does not return any data, it is considered a failure
onShutdown(ex);
} else {
onPlayResult(ex);
}
} else {
onShutdown(ex);
}
HttpTSPlayer::onResponseCompleted(ex);
}
void TsPlayer::onResponseBody(const char *buf, size_t size) {
if (!_play_result) {
_play_result = true;
onPlayResult(SockException(Err_success, "read http-ts stream successfully"));
}
if (!_benchmark_mode) {
HttpTSPlayer::onResponseBody(buf, size);
}
}
} // namespace mediakit

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
* Created by alex on 2021/4/6.
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HTTP_TSPLAYER_H
#define HTTP_TSPLAYER_H
#include "HttpTSPlayer.h"
#include "Player/PlayerBase.h"
namespace mediakit {
class TsPlayer : public HttpTSPlayer, public PlayerBase {
public:
TsPlayer(const toolkit::EventPoller::Ptr &poller);
/**
*
* Start playing
* [AUTO-TRANSLATED:53a212c5]
*/
void play(const std::string &url) override;
/**
*
* Stop playing
* [AUTO-TRANSLATED:db52bf15]
*/
void teardown() override;
protected:
void onResponseBody(const char *buf, size_t size) override;
void onResponseCompleted(const toolkit::SockException &ex) override;
private:
bool _play_result = true;
bool _benchmark_mode = false;
};
} // namespace mediakit
#endif // HTTP_TSPLAYER_H

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
* Created by alex on 2021/4/6.
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HTTP_TSPLAYERIMP_H
#define HTTP_TSPLAYERIMP_H
#include <unordered_set>
#include "TsPlayer.h"
namespace mediakit {
class TsPlayerImp : public PlayerImp<TsPlayer, PlayerBase>, private TrackListener {
public:
using Ptr = std::shared_ptr<TsPlayerImp>;
TsPlayerImp(const toolkit::EventPoller::Ptr &poller = nullptr);
private:
//// TsPlayer override////
void onResponseBody(const char *buf, size_t size) override;
private:
//// PlayerBase override////
void onPlayResult(const toolkit::SockException &ex) override;
std::vector<Track::Ptr> getTracks(bool ready = true) const override;
void onShutdown(const toolkit::SockException &ex) override;
private:
//// TrackListener override////
bool addTrack(const Track::Ptr &track) override { return true; };
void addTrackCompleted() override;
private:
DecoderImp::Ptr _decoder;
MediaSinkInterface::Ptr _demuxer;
};
}//namespace mediakit
#endif //HTTP_TSPLAYERIMP_H

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
* Created by alex on 2021/4/6.
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "TsPlayerImp.h"
#include "HlsPlayer.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
TsPlayerImp::TsPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<TsPlayer, PlayerBase>(poller) {}
void TsPlayerImp::onResponseBody(const char *data, size_t len) {
TsPlayer::onResponseBody(data, len);
if (!_decoder && _demuxer) {
_decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _demuxer.get());
}
if (_decoder && _demuxer) {
_decoder->input((uint8_t *) data, len);
}
}
void TsPlayerImp::addTrackCompleted() {
PlayerImp<TsPlayer, PlayerBase>::onPlayResult(SockException(Err_success, "play http-ts success"));
}
void TsPlayerImp::onPlayResult(const SockException &ex) {
auto benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
if (ex || benchmark_mode) {
PlayerImp<TsPlayer, PlayerBase>::onPlayResult(ex);
} else {
auto demuxer = std::make_shared<HlsDemuxer>();
demuxer->start(getPoller(), this);
_demuxer = std::move(demuxer);
}
}
void TsPlayerImp::onShutdown(const SockException &ex) {
while (_demuxer) {
try {
// shared_from_this()可能抛异常 [AUTO-TRANSLATED:6af9bd3c]
// shared_from_this() may throw an exception
std::weak_ptr<TsPlayerImp> weak_self = static_pointer_cast<TsPlayerImp>(shared_from_this());
if (_decoder) {
_decoder->flush();
}
// 等待所有frame flush输出后再触发onShutdown事件 [AUTO-TRANSLATED:93982eb3]
// Wait for all frame flush output before triggering the onShutdown event
static_pointer_cast<HlsDemuxer>(_demuxer)->pushTask([weak_self, ex]() {
if (auto strong_self = weak_self.lock()) {
strong_self->_demuxer = nullptr;
strong_self->onShutdown(ex);
}
});
return;
} catch (...) {
break;
}
}
PlayerImp<TsPlayer, PlayerBase>::onShutdown(ex);
}
vector<Track::Ptr> TsPlayerImp::getTracks(bool ready) const {
if (!_demuxer) {
return vector<Track::Ptr>();
}
return static_pointer_cast<HlsDemuxer>(_demuxer)->getTracks(ready);
}
}//namespace mediakit

View File

@ -0,0 +1,534 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WebSocketClient_H
#define ZLMEDIAKIT_WebSocketClient_H
#include "Util/util.h"
#include "Util/base64.h"
#include "Util/SHA1.h"
#include "Network/TcpClient.h"
#include "HttpClientImp.h"
#include "WebSocketSplitter.h"
namespace mediakit {
template <typename ClientType, WebSocketHeader::Type DataType>
class HttpWsClient;
/**
* ,TcpClient数据发送前的拦截
* @tparam ClientType TcpClient派生类
* @tparam DataType ,
* Helper class for intercepting data sent by TcpClient before sending
* @tparam ClientType TcpClient derived class
* @tparam DataType This is useless, used for declaring friends
* [AUTO-TRANSLATED:02cc7424]
*/
template <typename ClientType, WebSocketHeader::Type DataType>
class ClientTypeImp : public ClientType {
public:
friend class HttpWsClient<ClientType, DataType>;
using onBeforeSendCB = std::function<ssize_t(const toolkit::Buffer::Ptr &buf)>;
template <typename... ArgsType>
ClientTypeImp(ArgsType &&...args) : ClientType(std::forward<ArgsType>(args)...) {}
/**
* websocket协议
* Intercept before sending and package it into websocket protocol
* [AUTO-TRANSLATED:b43b6169]
*/
ssize_t send(toolkit::Buffer::Ptr buf) override {
if (_beforeSendCB) {
return _beforeSendCB(buf);
}
return ClientType::send(std::move(buf));
}
protected:
/**
*
* @param cb
* Set the data interception callback function
* @param cb Interception callback function
* [AUTO-TRANSLATED:3e74fcdd]
*/
void setOnBeforeSendCB(const onBeforeSendCB &cb) { _beforeSendCB = cb; }
private:
onBeforeSendCB _beforeSendCB;
};
/**
* weksocket TcpClient派生类事件的桥接
* @tparam ClientType TcpClient派生类
* @tparam DataType websocket负载类型TEXT还是BINARY类型
* This object completes the weksocket client handshake protocol and bridges to the TcpClient derived class events
* @tparam ClientType TcpClient derived class
* @tparam DataType websocket payload type, TEXT or BINARY type
* [AUTO-TRANSLATED:912c15f6]
*/
template <typename ClientType, WebSocketHeader::Type DataType = WebSocketHeader::TEXT>
class HttpWsClient : public HttpClientImp, public WebSocketSplitter {
public:
using Ptr = std::shared_ptr<HttpWsClient>;
HttpWsClient(const std::shared_ptr<ClientTypeImp<ClientType, DataType>> &delegate) : _weak_delegate(delegate) {
_Sec_WebSocket_Key = encodeBase64(toolkit::makeRandStr(16, false));
setPoller(delegate->getPoller());
}
/**
* ws握手
* @param ws_url ws连接url
* @param fTimeOutSec
* Initiate ws handshake
* @param ws_url ws connection url
* @param fTimeOutSec Timeout time
* [AUTO-TRANSLATED:453c027c]
*/
void startWsClient(const std::string &ws_url, float fTimeOutSec) {
std::string http_url = ws_url;
toolkit::replace(http_url, "ws://", "http://");
toolkit::replace(http_url, "wss://", "https://");
setMethod("GET");
addHeader("Upgrade", "websocket");
addHeader("Connection", "Upgrade");
addHeader("Sec-WebSocket-Version", "13");
addHeader("Sec-WebSocket-Key", _Sec_WebSocket_Key);
_onRecv = nullptr;
setHeaderTimeout(fTimeOutSec * 1000);
sendRequest(http_url);
}
void closeWsClient() {
if (!_onRecv) {
// 未连接 [AUTO-TRANSLATED:94510177]
// Not connected
return;
}
WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = CLOSE;
// 客户端需要加密 [AUTO-TRANSLATED:d6958acf]
// Client needs encryption
header._mask_flag = true;
WebSocketSplitter::encode(header, nullptr);
}
protected:
// HttpClientImp override
/**
* http回复头
* @param status :200 OK
* @param headers http头
* Receive http response header
* @param status Status code, such as: 200 OK
* @param headers http header
* [AUTO-TRANSLATED:a685f8ef]
*/
void onResponseHeader(const std::string &status, const HttpHeader &headers) override {
if (status == "101") {
auto Sec_WebSocket_Accept = encodeBase64(toolkit::SHA1::encode_bin(_Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
if (Sec_WebSocket_Accept == const_cast<HttpHeader &>(headers)["Sec-WebSocket-Accept"]) {
// success
onWebSocketException(toolkit::SockException());
// 防止ws服务器返回Content-Length [AUTO-TRANSLATED:f4454ae6]
// Prevent ws server from returning Content-Length
const_cast<HttpHeader &>(headers).erase("Content-Length");
return;
}
shutdown(toolkit::SockException(toolkit::Err_shutdown, StrPrinter << "Sec-WebSocket-Accept mismatch"));
return;
}
shutdown(toolkit::SockException(toolkit::Err_shutdown, StrPrinter << "bad http status code:" << status));
};
/**
* http回复完毕,
* Receive http response complete,
* [AUTO-TRANSLATED:b96ed715]
*/
void onResponseCompleted(const toolkit::SockException &ex) override {}
/**
* websocket负载数据
* Receive websocket payload data
* [AUTO-TRANSLATED:55d403d9]
*/
void onResponseBody(const char *buf, size_t size) override {
if (_onRecv) {
// 完成websocket握手后拦截websocket数据并解析 [AUTO-TRANSLATED:734280fe]
// After completing the websocket handshake, intercept the websocket data and parse it
_onRecv(buf, size);
}
};
// TcpClient override
void onRecv(const toolkit::Buffer::Ptr &buf) override {
HttpClientImp::onRecv(buf);
}
/**
*
* Triggered periodically
* [AUTO-TRANSLATED:2a75dbf6]
*/
void onManager() override {
if (_onRecv) {
// websocket连接成功了 [AUTO-TRANSLATED:45a9e005]
// websocket connection succeeded
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->onManager();
}
} else {
// websocket连接中... [AUTO-TRANSLATED:861cb158]
// websocket connecting...
HttpClientImp::onManager();
}
if (!_onRecv) {
// websocket尚未链接 [AUTO-TRANSLATED:164129da]
// websocket not yet connected
return;
}
if (_recv_ticker.elapsedTime() > 30 * 1000) {
shutdown(toolkit::SockException(toolkit::Err_timeout, "websocket timeout"));
} else if (_recv_ticker.elapsedTime() > 10 * 1000) {
// 没收到回复每10秒发送次ping 包 [AUTO-TRANSLATED:31b4dc13]
// No response received, send a ping packet every 10 seconds
WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = PING;
header._mask_flag = true;
WebSocketSplitter::encode(header, nullptr);
}
}
/**
*
* Callback after all data has been sent
* [AUTO-TRANSLATED:8b2ba800]
*/
void onFlush() override {
if (_onRecv) {
// websocket连接成功了 [AUTO-TRANSLATED:45a9e005]
// websocket connection succeeded
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->onFlush();
}
} else {
// websocket连接中... [AUTO-TRANSLATED:861cb158]
// websocket connecting...
HttpClientImp::onFlush();
}
}
/**
* tcp连接结果
* tcp connection result
* [AUTO-TRANSLATED:eaca9fcc]
*/
void onConnect(const toolkit::SockException &ex) override {
if (ex) {
// tcp连接失败直接返回失败 [AUTO-TRANSLATED:dcd81b67]
// tcp connection failed, return failure directly
onWebSocketException(ex);
return;
}
// 开始websocket握手 [AUTO-TRANSLATED:544a5ba3]
// Start websocket handshake
HttpClientImp::onConnect(ex);
}
/**
* tcp连接断开
* tcp connection disconnected
* [AUTO-TRANSLATED:732b0740]
*/
void onError(const toolkit::SockException &ex) override {
// tcp断开或者shutdown导致的断开 [AUTO-TRANSLATED:5b6b7ad4]
// Disconnection caused by tcp disconnection or shutdown
onWebSocketException(ex);
}
// WebSocketSplitter override
/**
* webSocket数据包包头onWebSocketDecodePayload回调
* @param header
* Receive a webSocket data packet header, and then continue to trigger the onWebSocketDecodePayload callback
* @param header Data packet header
* [AUTO-TRANSLATED:7bc6b7c6]
*/
void onWebSocketDecodeHeader(const WebSocketHeader &header) override { _payload_section.clear(); }
/**
* webSocket数据包负载
* @param header
* @param ptr
* @param len
* @param recved ()header._payload_len时则接受完毕
* Receive webSocket data packet payload
* @param header Data packet header
* @param ptr Payload data pointer
* @param len Payload data length
* @param recved Received data length (including the current data length), equal to header._payload_len when the reception is complete
* [AUTO-TRANSLATED:ca056d2e]
*/
void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, size_t len, size_t recved) override {
_payload_section.append((char *)ptr, len);
}
/**
* webSocket数据包后回调
* @param header
* Callback after receiving a complete webSocket data packet
* @param header Data packet header
* [AUTO-TRANSLATED:f506a7c5]
*/
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override {
WebSocketHeader &header = const_cast<WebSocketHeader &>(header_in);
auto flag = header._mask_flag;
// websocket客户端发送数据需要加密 [AUTO-TRANSLATED:2bbbb390]
// websocket client needs to encrypt data sent
header._mask_flag = true;
_recv_ticker.resetTime();
switch (header._opcode) {
case WebSocketHeader::CLOSE: {
// 服务器主动关闭 [AUTO-TRANSLATED:5a59e1bf]
// Server actively closes
WebSocketSplitter::encode(header, nullptr);
shutdown(toolkit::SockException(toolkit::Err_eof, "websocket server close the connection"));
break;
}
case WebSocketHeader::PING: {
// 心跳包 [AUTO-TRANSLATED:1b4b9ae4]
// Heartbeat packet
header._opcode = WebSocketHeader::PONG;
WebSocketSplitter::encode(header, std::make_shared<toolkit::BufferString>(std::move(_payload_section)));
break;
}
case WebSocketHeader::CONTINUATION:
case WebSocketHeader::TEXT:
case WebSocketHeader::BINARY: {
if (!header._fin) {
// 还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出 [AUTO-TRANSLATED:0a237b29]
// There are subsequent fragment data, we cache the data first, and output it all at once after all fragments are collected
_payload_cache.append(std::move(_payload_section));
if (_payload_cache.size() < MAX_WS_PACKET) {
// 还有内存容量缓存分片数据 [AUTO-TRANSLATED:8da8074a]
// There is also memory capacity to cache fragment data
break;
}
// 分片缓存太大,需要清空 [AUTO-TRANSLATED:a0d9c101]
// Fragment cache is too large, need to clear
}
// 最后一个包 [AUTO-TRANSLATED:82e1bf79]
// Last packet
if (_payload_cache.empty()) {
// 这个包是唯一个分片 [AUTO-TRANSLATED:079a9865]
// This packet is the only fragment
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
}
break;
}
// 这个包由多个分片组成 [AUTO-TRANSLATED:27fd75df]
// This packet consists of multiple fragments
_payload_cache.append(std::move(_payload_section));
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
}
_payload_cache.clear();
break;
}
default: break;
}
_payload_section.clear();
header._mask_flag = flag;
}
/**
* websocket数据编码回调
* @param ptr
* @param len
* websocket data encoding callback
* @param ptr data pointer
* @param len data pointer length
* [AUTO-TRANSLATED:7c940c67]
*/
void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer) override { HttpClientImp::send(std::move(buffer)); }
private:
void onWebSocketException(const toolkit::SockException &ex) {
if (!ex) {
// websocket握手成功 [AUTO-TRANSLATED:bceba441]
// websocket handshake successful
// 此处截取TcpClient派生类发送的数据并进行websocket协议打包 [AUTO-TRANSLATED:8cae42cd]
// Here, the data sent by the TcpClient derived class is intercepted and packaged into the websocket protocol
std::weak_ptr<HttpWsClient> weakSelf = std::static_pointer_cast<HttpWsClient>(shared_from_this());
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->setOnBeforeSendCB([weakSelf](const toolkit::Buffer::Ptr &buf) {
auto strong_self = weakSelf.lock();
if (strong_self) {
WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = DataType;
// 客户端需要加密 [AUTO-TRANSLATED:d6958acf]
// Client needs encryption
header._mask_flag = true;
strong_self->WebSocketSplitter::encode(header, buf);
}
return buf->size();
});
// 设置sock否则shutdown等接口都无效 [AUTO-TRANSLATED:4586b98b]
// Set sock, otherwise shutdown and other interfaces are invalid
strong_ref->setSock(HttpClientImp::getSock());
// 触发连接成功事件 [AUTO-TRANSLATED:0459f68f]
// Trigger connection success event
strong_ref->onConnect(ex);
}
// 拦截websocket数据接收 [AUTO-TRANSLATED:fb93bbe9]
// Intercept websocket data reception
_onRecv = [this](const char *data, size_t len) {
// 解析websocket数据包 [AUTO-TRANSLATED:656b8c89]
// Parse websocket data packet
this->WebSocketSplitter::decode((uint8_t *)data, len);
};
return;
}
// websocket握手失败或者tcp连接失败或者中途断开 [AUTO-TRANSLATED:acf8d1ff]
// websocket handshake failed or tcp connection failed or disconnected in the middle
if (_onRecv) {
// 握手成功之后的中途断开 [AUTO-TRANSLATED:dd5d412c]
// Disconnected in the middle after handshake success
_onRecv = nullptr;
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->onError(ex);
}
return;
}
// websocket握手失败或者tcp连接失败 [AUTO-TRANSLATED:3f03cf1f]
// websocket handshake failed or tcp connection failed
if (auto strong_ref = _weak_delegate.lock()) {
strong_ref->onConnect(ex);
}
}
private:
std::string _Sec_WebSocket_Key;
std::function<void(const char *data, size_t len)> _onRecv;
std::weak_ptr<ClientTypeImp<ClientType, DataType>> _weak_delegate;
std::string _payload_section;
std::string _payload_cache;
toolkit::Ticker _recv_ticker;
};
/**
* Tcp客户端转WebSocket客户端模板
* TcpClient派生类任何代码的情况下快速实现WebSocket协议的包装
* @tparam ClientType TcpClient派生类
* @tparam DataType websocket负载类型TEXT还是BINARY类型
* @tparam useWSS 使ws还是wss连接
* Tcp client to WebSocket client template,
* Through this template, developers can quickly implement WebSocket protocol packaging without modifying any code of the TcpClient derived class
* @tparam ClientType TcpClient derived class
* @tparam DataType websocket payload type, is it TEXT or BINARY type
* @tparam useWSS Whether to use ws or wss connection
* [AUTO-TRANSLATED:ac1516b8]
*/
template <typename ClientType, WebSocketHeader::Type DataType = WebSocketHeader::TEXT, bool useWSS = false>
class WebSocketClient : public ClientTypeImp<ClientType, DataType> {
public:
using Ptr = std::shared_ptr<WebSocketClient>;
template <typename... ArgsType>
WebSocketClient(ArgsType &&...args) : ClientTypeImp<ClientType, DataType>(std::forward<ArgsType>(args)...) {}
~WebSocketClient() override { _wsClient->closeWsClient(); }
/**
* startConnect方法
* TcpClient的连接服务器行为使WebSocket握手
* @param host websocket服务器ip或域名
* @param iPort websocket服务器端口
* @param timeout_sec
* @param local_port
* Overload the startConnect method,
* The purpose is to replace the TcpClient's connection server behavior, so that it completes the WebSocket handshake first
* @param host websocket server ip or domain name
* @param iPort websocket server port
* @param timeout_sec timeout time
* @param local_port local listening port, which does not work here
* [AUTO-TRANSLATED:1aed295d]
*/
void startConnect(const std::string &host, uint16_t port, float timeout_sec = 3, uint16_t local_port = 0) override {
std::string ws_url;
if (useWSS) {
// 加密的ws [AUTO-TRANSLATED:d1385825]
// Encrypted ws
ws_url = StrPrinter << "wss://" + host << ":" << port << "/";
} else {
// 明文ws [AUTO-TRANSLATED:71aa82d1]
// Plaintext ws
ws_url = StrPrinter << "ws://" + host << ":" << port << "/";
}
startWebSocket(ws_url, timeout_sec);
}
void startWebSocket(const std::string &ws_url, float fTimeOutSec = 3) {
_wsClient = std::make_shared<HttpWsClient<ClientType, DataType>>(std::static_pointer_cast<WebSocketClient>(this->shared_from_this()));
_wsClient->setOnCreateSocket([this](const toolkit::EventPoller::Ptr &) { return this->createSocket(); });
_wsClient->startWsClient(ws_url, fTimeOutSec);
}
HttpClient &getHttpClient() { return *_wsClient; }
private:
typename HttpWsClient<ClientType, DataType>::Ptr _wsClient;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_WebSocketClient_H

View File

@ -0,0 +1,304 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBSOCKETSESSION_H
#define ZLMEDIAKIT_WEBSOCKETSESSION_H
#include "HttpSession.h"
#include "Network/TcpServer.h"
/**
*
* Data Send Interceptor
* [AUTO-TRANSLATED:5eaf7060]
*/
class SendInterceptor{
public:
using onBeforeSendCB =std::function<ssize_t (const toolkit::Buffer::Ptr &buf)>;
virtual ~SendInterceptor() = default;
virtual void setOnBeforeSendCB(const onBeforeSendCB &cb) = 0;
};
/**
* Session派生类发送数据的截取
* websocket协议的打包
* This class implements the interception of data sent by the Session derived class.
* The purpose is to package the websocket protocol before sending business data.
* [AUTO-TRANSLATED:15c96e5f]
*/
template <typename SessionType>
class SessionTypeImp : public SessionType, public SendInterceptor{
public:
using Ptr = std::shared_ptr<SessionTypeImp>;
SessionTypeImp(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock) :
SessionType(pSock) {}
/**
*
* @param cb
* Set the send data interception callback function
* @param cb Interception callback function
* [AUTO-TRANSLATED:3e74fcdd]
*/
void setOnBeforeSendCB(const onBeforeSendCB &cb) override {
_beforeSendCB = cb;
}
protected:
/**
* send函数截取数据
* @param buf
* @return
* Overload the send function to intercept data
* @param buf Data to be intercepted
* @return Number of data bytes
* [AUTO-TRANSLATED:d3304949]
*/
ssize_t send(toolkit::Buffer::Ptr buf) override {
if (_beforeSendCB) {
return _beforeSendCB(buf);
}
return SessionType::send(std::move(buf));
}
private:
onBeforeSendCB _beforeSendCB;
};
template <typename SessionType>
class SessionCreator {
public:
// 返回的Session必须派生于SendInterceptor可以返回null [AUTO-TRANSLATED:6cc95812]
// The returned Session must be derived from SendInterceptor, and can return null
toolkit::Session::Ptr operator()(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock, mediakit::WebSocketHeader::Type &data_type){
return std::make_shared<SessionTypeImp<SessionType> >(header,parent,pSock);
}
};
/**
* WebSocket协议
* WebSock协议下的具体业务协议WebSocket协议的Rtmp协议等
* Through this template class, the WebSocket protocol can be transparently implemented.
* Users only need to implement specific business protocols under the WebSock protocol, such as the Rtmp protocol based on the WebSocket protocol.
* [AUTO-TRANSLATED:07e2e8a5]
*/
template<typename Creator, typename HttpSessionType = mediakit::HttpSession, mediakit::WebSocketHeader::Type DataType = mediakit::WebSocketHeader::TEXT>
class WebSocketSessionBase : public HttpSessionType {
public:
WebSocketSessionBase(const toolkit::Socket::Ptr &pSock) : HttpSessionType(pSock){}
// 收到eof或其他导致脱离TcpServer事件的回调 [AUTO-TRANSLATED:6d48b35c]
// Callback when receiving eof or other events that cause disconnection from TcpServer
void onError(const toolkit::SockException &err) override{
HttpSessionType::onError(err);
if(_session){
_session->onError(err);
}
}
// 每隔一段时间触发,用来做超时管理 [AUTO-TRANSLATED:823ffe1f]
// Triggered every period of time, used for timeout management
void onManager() override{
if (_session) {
_session->onManager();
} else {
HttpSessionType::onManager();
}
if (!_session) {
// websocket尚未链接 [AUTO-TRANSLATED:164129da]
// websocket is not yet connected
return;
}
if (_recv_ticker.elapsedTime() > 30 * 1000) {
HttpSessionType::shutdown(toolkit::SockException(toolkit::Err_timeout, "websocket timeout"));
} else if (_recv_ticker.elapsedTime() > 10 * 1000) {
// 没收到回复每10秒发送次ping 包 [AUTO-TRANSLATED:31b4dc13]
// No reply received, send a ping packet every 10 seconds
mediakit::WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = mediakit::WebSocketHeader::PING;
header._mask_flag = false;
HttpSessionType::encode(header, nullptr);
}
}
void attachServer(const toolkit::Server &server) override{
HttpSessionType::attachServer(server);
_weak_server = const_cast<toolkit::Server &>(server).shared_from_this();
}
protected:
/**
* websocket客户端连接上事件
* @param header http头
* @return true代表允许websocket连接
* websocket client connection event
* @param header http header
* @return true means allowing websocket connection, otherwise refuse
* [AUTO-TRANSLATED:d857fb0f]
*/
bool onWebSocketConnect(const mediakit::Parser &header) override{
// 创建websocket session类 [AUTO-TRANSLATED:099f6963]
// Create websocket session class
auto data_type = DataType;
_session = _creator(header, *this, HttpSessionType::getSock(), data_type);
if (!_session) {
// 此url不允许创建websocket连接 [AUTO-TRANSLATED:47480366]
// This url is not allowed to create websocket connection
return false;
}
auto strongServer = _weak_server.lock();
if (strongServer) {
_session->attachServer(*strongServer);
}
// 此处截取数据并进行websocket协议打包 [AUTO-TRANSLATED:89053032]
// Intercept data here and package it with websocket protocol
std::weak_ptr<WebSocketSessionBase> weakSelf = std::static_pointer_cast<WebSocketSessionBase>(HttpSessionType::shared_from_this());
std::dynamic_pointer_cast<SendInterceptor>(_session)->setOnBeforeSendCB([weakSelf, data_type](const toolkit::Buffer::Ptr &buf) {
auto strongSelf = weakSelf.lock();
if (strongSelf) {
mediakit::WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = data_type;
header._mask_flag = false;
strongSelf->HttpSessionType::encode(header, buf);
}
return buf->size();
});
// 允许websocket客户端 [AUTO-TRANSLATED:3a06f181]
// Allow websocket client
return true;
}
/**
* webSocket数据包
* Start receiving a webSocket data packet
* [AUTO-TRANSLATED:0f16a5b5]
*/
void onWebSocketDecodeHeader(const mediakit::WebSocketHeader &packet) override{
// 新包,原来的包残余数据清空掉 [AUTO-TRANSLATED:0fd23412]
// New package, the residual data of the original package is cleared
_payload_section.clear();
}
/**
* websocket数据包负载
* Receive websocket data packet payload
* [AUTO-TRANSLATED:b317988d]
*/
void onWebSocketDecodePayload(const mediakit::WebSocketHeader &packet,const uint8_t *ptr,size_t len,size_t recved) override {
_payload_section.append((char *)ptr,len);
}
/**
* webSocket数据包后回调
* @param header
* Callback after receiving a complete webSocket data packet
* @param header Data packet header
* [AUTO-TRANSLATED:f506a7c5]
*/
void onWebSocketDecodeComplete(const mediakit::WebSocketHeader &header_in) override {
auto header = const_cast<mediakit::WebSocketHeader&>(header_in);
auto flag = header._mask_flag;
header._mask_flag = false;
_recv_ticker.resetTime();
switch (header._opcode){
case mediakit::WebSocketHeader::CLOSE:{
HttpSessionType::encode(header,nullptr);
HttpSessionType::shutdown(toolkit::SockException(toolkit::Err_shutdown, "recv close request from client"));
break;
}
case mediakit::WebSocketHeader::PING:{
header._opcode = mediakit::WebSocketHeader::PONG;
HttpSessionType::encode(header,std::make_shared<toolkit::BufferString>(_payload_section));
break;
}
case mediakit::WebSocketHeader::CONTINUATION:
case mediakit::WebSocketHeader::TEXT:
case mediakit::WebSocketHeader::BINARY:{
if (!header._fin) {
// 还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出 [AUTO-TRANSLATED:75d21e17]
// There is subsequent fragment data, we cache the data first, and output it all at once after all fragments are collected
_payload_cache.append(std::move(_payload_section));
if (_payload_cache.size() < MAX_WS_PACKET) {
// 还有内存容量缓存分片数据 [AUTO-TRANSLATED:621da1f9]
// There is memory capacity to cache fragment data
break;
}
// 分片缓存太大,需要清空 [AUTO-TRANSLATED:98882d1f]
// Fragment cache is too large, need to be cleared
}
// 最后一个包 [AUTO-TRANSLATED:dcf860cf]
// Last package
if (_payload_cache.empty()) {
// 这个包是唯一个分片 [AUTO-TRANSLATED:94802e24]
// This package is the only fragment
_session->onRecv(std::make_shared<mediakit::WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
break;
}
// 这个包由多个分片组成 [AUTO-TRANSLATED:044123f1]
// This package consists of multiple fragments
_payload_cache.append(std::move(_payload_section));
_session->onRecv(std::make_shared<mediakit::WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
_payload_cache.clear();
break;
}
default: break;
}
_payload_section.clear();
header._mask_flag = flag;
}
/**
* websocket协议打包后回调
* Callback after sending data and packaging it with websocket protocol
* [AUTO-TRANSLATED:3327ce78]
*/
void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer) override{
HttpSessionType::send(std::move(buffer));
}
private:
std::string _payload_cache;
std::string _payload_section;
std::weak_ptr<toolkit::Server> _weak_server;
toolkit::Session::Ptr _session;
Creator _creator;
toolkit::Ticker _recv_ticker;
};
template<typename SessionType,typename HttpSessionType = mediakit::HttpSession, mediakit::WebSocketHeader::Type DataType = mediakit::WebSocketHeader::TEXT>
class WebSocketSession : public WebSocketSessionBase<SessionCreator<SessionType>,HttpSessionType,DataType>{
public:
WebSocketSession(const toolkit::Socket::Ptr &pSock) : WebSocketSessionBase<SessionCreator<SessionType>,HttpSessionType,DataType>(pSock){}
};
#endif //ZLMEDIAKIT_WEBSOCKETSESSION_H

View File

@ -0,0 +1,208 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "WebSocketSplitter.h"
#include <sys/types.h>
#if !defined(_WIN32)
#include <sys/socket.h>
#include <arpa/inet.h>
#endif //!defined(_WIN32)
#include "Util/logger.h"
#include "Util/util.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
/**
*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
*/
#define CHECK_LEN(size) \
do{ \
if(len - (ptr - data) < size){ \
if(_remain_data.empty()){ \
_remain_data.assign((char *)data,len); \
} \
return ; \
} \
}while(0) \
void WebSocketSplitter::decode(uint8_t *data, size_t len) {
uint8_t *ptr = data;
if(!_got_header) {
// 还没有获取数据头 [AUTO-TRANSLATED:2b50f282]
// No data header has been obtained yet
if(!_remain_data.empty()){
_remain_data.append((char *)data,len);
data = ptr = (uint8_t *)_remain_data.data();
len = _remain_data.size();
}
begin_decode:
CHECK_LEN(1);
_fin = (*ptr & 0x80) >> 7;
_reserved = (*ptr & 0x70) >> 4;
_opcode = (WebSocketHeader::Type) (*ptr & 0x0F);
ptr += 1;
CHECK_LEN(1);
_mask_flag = (*ptr & 0x80) >> 7;
_payload_len = (*ptr & 0x7F);
ptr += 1;
if (_payload_len == 126) {
CHECK_LEN(2);
_payload_len = (*ptr << 8) | *(ptr + 1);
ptr += 2;
} else if (_payload_len == 127) {
CHECK_LEN(8);
_payload_len = ((uint64_t) ptr[0] << (8 * 7)) |
((uint64_t) ptr[1] << (8 * 6)) |
((uint64_t) ptr[2] << (8 * 5)) |
((uint64_t) ptr[3] << (8 * 4)) |
((uint64_t) ptr[4] << (8 * 3)) |
((uint64_t) ptr[5] << (8 * 2)) |
((uint64_t) ptr[6] << (8 * 1)) |
((uint64_t) ptr[7] << (8 * 0));
ptr += 8;
}
if (_mask_flag) {
CHECK_LEN(4);
_mask.assign(ptr, ptr + 4);
ptr += 4;
}
_got_header = true;
_mask_offset = 0;
_payload_offset = 0;
onWebSocketDecodeHeader(*this);
if(_payload_len == 0){
onWebSocketDecodeComplete(*this);
}
}
// 进入后面逻辑代表已经获取到了webSocket协议头 [AUTO-TRANSLATED:e6bd2556]
// Entering the following logic means that the webSocket protocol header has been obtained,
auto remain = len - (ptr - data);
if(remain > 0){
auto payload_slice_len = remain;
if(payload_slice_len + _payload_offset > _payload_len){
payload_slice_len = _payload_len - _payload_offset;
}
_payload_offset += payload_slice_len;
onPayloadData(ptr, payload_slice_len);
if(_payload_offset == _payload_len){
onWebSocketDecodeComplete(*this);
// 这是下一个包 [AUTO-TRANSLATED:bf657413]
// This is the next package
remain -= payload_slice_len;
ptr += payload_slice_len;
_got_header = false;
if(remain > 0){
// 剩余数据是下一个包,把它的数据放置在缓存中 [AUTO-TRANSLATED:7b2ebfad]
// The remaining data is the next package, place its data in the cache
string str((char *)ptr,remain);
_remain_data = str;
data = ptr = (uint8_t *)_remain_data.data();
len = _remain_data.size();
goto begin_decode;
}
}
}
_remain_data.clear();
}
void WebSocketSplitter::onPayloadData(uint8_t *data, size_t len) {
if(_mask_flag){
for(size_t i = 0; i < len ; ++i,++data){
*(data) ^= _mask[(i + _mask_offset) % 4];
}
_mask_offset = (_mask_offset + len) % 4;
}
onWebSocketDecodePayload(*this, _mask_flag ? data - len : data, len, _payload_offset);
}
void WebSocketSplitter::encode(const WebSocketHeader &header,const Buffer::Ptr &buffer) {
string ret;
uint64_t len = buffer ? buffer->size() : 0;
uint8_t byte = header._fin << 7 | ((header._reserved & 0x07) << 4) | (header._opcode & 0x0F) ;
ret.push_back(byte);
auto mask_flag = (header._mask_flag && header._mask.size() >= 4);
byte = mask_flag << 7;
if(len < 126){
byte |= len;
ret.push_back(byte);
}else if(len <= 0xFFFF){
byte |= 126;
ret.push_back(byte);
uint16_t len_low = htons((uint16_t)len);
ret.append((char *)&len_low,2);
}else{
byte |= 127;
ret.push_back(byte);
uint32_t len_high = htonl(len >> 32) ;
uint32_t len_low = htonl(len & 0xFFFFFFFF);
ret.append((char *)&len_high,4);
ret.append((char *)&len_low,4);
}
if(mask_flag){
ret.append((char *)header._mask.data(),4);
}
onWebSocketEncodeData(std::make_shared<BufferString>(std::move(ret)));
if(len > 0){
if(mask_flag){
uint8_t *ptr = (uint8_t*)buffer->data();
for(size_t i = 0; i < len ; ++i,++ptr){
*(ptr) ^= header._mask[i % 4];
}
}
onWebSocketEncodeData(buffer);
}
}
} /* namespace mediakit */

View File

@ -0,0 +1,181 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBSOCKETSPLITTER_H
#define ZLMEDIAKIT_WEBSOCKETSPLITTER_H
#include <cstdint>
#include <string>
#include <vector>
#include <memory>
#include "Network/Buffer.h"
// websocket组合包最大不得超过4MB(防止内存爆炸) [AUTO-TRANSLATED:99c11a1d]
// websocket combined package size must not exceed 4MB (to prevent memory explosion)
#define MAX_WS_PACKET (4 * 1024 * 1024)
namespace mediakit {
class WebSocketHeader {
public:
using Ptr = std::shared_ptr<WebSocketHeader>;
typedef enum {
CONTINUATION = 0x0,
TEXT = 0x1,
BINARY = 0x2,
RSV3 = 0x3,
RSV4 = 0x4,
RSV5 = 0x5,
RSV6 = 0x6,
RSV7 = 0x7,
CLOSE = 0x8,
PING = 0x9,
PONG = 0xA,
CONTROL_RSVB = 0xB,
CONTROL_RSVC = 0xC,
CONTROL_RSVD = 0xD,
CONTROL_RSVE = 0xE,
CONTROL_RSVF = 0xF
} Type;
public:
WebSocketHeader() : _mask(4){
// 获取_mask内部buffer的内存地址该内存是malloc开辟的地址为随机 [AUTO-TRANSLATED:9406f0b6]
// Get the memory address of the internal buffer of _mask, the memory is allocated by malloc, and the address is random
uint64_t ptr = (uint64_t)(&_mask[0]);
// 根据内存地址设置掩码随机数 [AUTO-TRANSLATED:47881295]
// Set the mask random number according to the memory address
_mask.assign((uint8_t*)(&ptr), (uint8_t*)(&ptr) + 4);
}
virtual ~WebSocketHeader() = default;
public:
bool _fin;
uint8_t _reserved;
Type _opcode;
bool _mask_flag;
size_t _payload_len;
std::vector<uint8_t > _mask;
};
// websocket协议收到的字符串类型缓存用户协议层获取该数据传输的方式 [AUTO-TRANSLATED:a66e0177]
// String type cache received by the websocket protocol, the way the user protocol layer obtains this data transmission
class WebSocketBuffer : public toolkit::BufferString {
public:
using Ptr = std::shared_ptr<WebSocketBuffer>;
template<typename ...ARGS>
WebSocketBuffer(WebSocketHeader::Type headType, bool fin, ARGS &&...args)
: toolkit::BufferString(std::forward<ARGS>(args)...), _fin(fin), _head_type(headType){}
WebSocketHeader::Type headType() const { return _head_type; }
bool isFinished() const { return _fin; };
private:
bool _fin;
WebSocketHeader::Type _head_type;
};
class WebSocketSplitter : public WebSocketHeader{
public:
/**
* 便webSocket数据以及处理粘包问题
* onWebSocketDecodeHeader和onWebSocketDecodePayload回调
* @param data
* @param len
* Input data to unpack webSocket data and handle sticky packet problems
* May trigger onWebSocketDecodeHeader and onWebSocketDecodePayload callbacks
* @param data Data to be unpacked, may be incomplete packets or multiple packets
* @param len Data length
* [AUTO-TRANSLATED:e5f2c2c6]
*/
void decode(uint8_t *data, size_t len);
/**
*
* 2onWebSocketEncodeData回调
* @param header
* @param buffer
* Encode a data packet
* Will trigger 2 onWebSocketEncodeData callbacks
* @param header Data header
* @param buffer Payload data
* [AUTO-TRANSLATED:f308e552]
*/
void encode(const WebSocketHeader &header,const toolkit::Buffer::Ptr &buffer);
protected:
/**
* webSocket数据包包头onWebSocketDecodePayload回调
* @param header
* Receive a webSocket data packet header, and will continue to trigger onWebSocketDecodePayload callback
* @param header Data packet header
* [AUTO-TRANSLATED:7bc6b7c6]
*/
virtual void onWebSocketDecodeHeader(const WebSocketHeader &header) {};
/**
* webSocket数据包负载
* @param header
* @param ptr
* @param len
* @param recved ()header._payload_len时则接受完毕
* Receive webSocket data packet payload
* @param header Data packet header
* @param ptr Payload data pointer
* @param len Payload data length
* @param recved Received data length (including the length of this data), equals header._payload_len when the reception is complete
* [AUTO-TRANSLATED:ca056d2e]
*/
virtual void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, size_t len, size_t recved) {};
/**
* webSocket数据包后回调
* @param header
* Callback after receiving a complete webSocket data packet
* @param header Data packet header
* [AUTO-TRANSLATED:f506a7c5]
*/
virtual void onWebSocketDecodeComplete(const WebSocketHeader &header) {};
/**
* websocket数据编码回调
* @param ptr
* @param len
* websocket data encoding callback
* @param ptr Data pointer
* @param len Data pointer length
* [AUTO-TRANSLATED:7c940c67]
*/
virtual void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer){};
private:
void onPayloadData(uint8_t *data, size_t len);
private:
bool _got_header = false;
int _mask_offset = 0;
size_t _payload_offset = 0;
std::string _remain_data;
};
} /* namespace mediakit */
#endif //ZLMEDIAKIT_WEBSOCKETSPLITTER_H

View File

@ -0,0 +1,32 @@
#include "MediaServer.h"
#include "BoostLog.h"
#include "Network/TcpServer.h"
#include "Rtsp/RtspSession.h"
#include <mk_media.h>
class MediaServerPrivate {
public:
toolkit::TcpServer::Ptr rtsp_server[2];
};
MediaServer::MediaServer(uint16_t port, bool ssl) : m_d(new MediaServerPrivate()) {
try {
m_d->rtsp_server[ssl] = std::make_shared<toolkit::TcpServer>();
if (ssl) {
m_d->rtsp_server[ssl]->start<toolkit::SessionWithSSL<mediakit::RtspSession>>(port);
} else {
m_d->rtsp_server[ssl]->start<mediakit::RtspSession>(port);
}
} catch (std::exception &ex) {
m_d->rtsp_server[ssl] = nullptr;
WarnL << ex.what();
}
}
MediaServer::~MediaServer() {
// mk_stop_all_server();
if (m_d != nullptr) {
delete m_d;
}
}

17
MediaServer/MediaServer.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __MEDIASERVER_H__
#define __MEDIASERVER_H__
#include <cstdint>
class MediaServerPrivate;
class MediaServer {
public:
MediaServer(uint16_t port, bool ssl);
~MediaServer();
private:
MediaServerPrivate *m_d = nullptr;
};
#endif // __MEDIASERVER_H__

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "MediaPlayer.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) {
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
}
static void setOnCreateSocket_l(const std::shared_ptr<PlayerBase> &delegate, const Socket::onCreateSocket &cb){
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
if (helper) {
if (cb) {
helper->setOnCreateSocket(cb);
} else {
// 客户端,确保开启互斥锁 [AUTO-TRANSLATED:a75e6e36]
// Client, ensure mutual exclusion lock is enabled
helper->setOnCreateSocket([](const EventPoller::Ptr &poller) {
return Socket::createSocket(poller, true);
});
}
}
}
void MediaPlayer::play(const string &url) {
_delegate = PlayerBase::createPlayer(_poller, url);
assert(_delegate);
setOnCreateSocket_l(_delegate, _on_create_socket);
_delegate->setOnShutdown(_on_shutdown);
_delegate->setOnPlayResult(_on_play_result);
_delegate->setOnResume(_on_resume);
_delegate->setMediaSource(_media_src);
for (auto &pr : *this) {
(*_delegate)[pr.first] = pr.second;
}
_delegate->play(url);
}
EventPoller::Ptr MediaPlayer::getPoller(){
return _poller;
}
void MediaPlayer::setOnCreateSocket(Socket::onCreateSocket cb){
setOnCreateSocket_l(_delegate, cb);
_on_create_socket = std::move(cb);
}
} /* namespace mediakit */

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_PLAYER_MEDIAPLAYER_H_
#define SRC_PLAYER_MEDIAPLAYER_H_
#include <memory>
#include <string>
#include "PlayerBase.h"
namespace mediakit {
class MediaPlayer : public PlayerImp<PlayerBase, PlayerBase> {
public:
using Ptr = std::shared_ptr<MediaPlayer>;
MediaPlayer(const toolkit::EventPoller::Ptr &poller = nullptr);
void play(const std::string &url) override;
toolkit::EventPoller::Ptr getPoller();
void setOnCreateSocket(toolkit::Socket::onCreateSocket cb);
private:
toolkit::EventPoller::Ptr _poller;
toolkit::Socket::onCreateSocket _on_create_socket;
};
} /* namespace mediakit */
#endif /* SRC_PLAYER_MEDIAPLAYER_H_ */

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "PlayerBase.h"
#include "Rtsp/RtspPlayerImp.h"
#include "Rtmp/RtmpPlayerImp.h"
#include "Rtmp/FlvPlayer.h"
#include "Http/HlsPlayer.h"
#include "Http/TsPlayerImp.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) {
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
std::weak_ptr<EventPoller> weak_poller = poller;
auto release_func = [weak_poller](PlayerBase *ptr) {
if (auto poller = weak_poller.lock()) {
poller->async([ptr]() {
onceToken token(nullptr, [&]() { delete ptr; });
ptr->teardown();
});
} else {
delete ptr;
}
};
string url = url_in;
string prefix = findSubString(url.data(), NULL, "://");
auto pos = url.find('?');
if (pos != string::npos) {
// 去除?后面的字符串 [AUTO-TRANSLATED:0ccb41c2]
// Remove the string after the question mark
url = url.substr(0, pos);
}
if (strcasecmp("rtsps", prefix.data()) == 0) {
return PlayerBase::Ptr(new TcpClientWithSSL<RtspPlayerImp>(poller), release_func);
}
if (strcasecmp("rtsp", prefix.data()) == 0) {
return PlayerBase::Ptr(new RtspPlayerImp(poller), release_func);
}
if (strcasecmp("rtmps", prefix.data()) == 0) {
return PlayerBase::Ptr(new TcpClientWithSSL<RtmpPlayerImp>(poller), release_func);
}
if (strcasecmp("rtmp", prefix.data()) == 0) {
return PlayerBase::Ptr(new RtmpPlayerImp(poller), release_func);
}
if ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) {
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
return PlayerBase::Ptr(new HlsPlayerImp(poller), release_func);
}
if (end_with(url, ".ts") || end_with(url_in, ".ts")) {
return PlayerBase::Ptr(new TsPlayerImp(poller), release_func);
}
if (end_with(url, ".flv") || end_with(url_in, ".flv")) {
return PlayerBase::Ptr(new FlvPlayerImp(poller), release_func);
}
}
throw std::invalid_argument("not supported play schema:" + url_in);
}
PlayerBase::PlayerBase() {
this->mINI::operator[](Client::kTimeoutMS) = 10000;
this->mINI::operator[](Client::kMediaTimeoutMS) = 5000;
this->mINI::operator[](Client::kBeatIntervalMS) = 5000;
this->mINI::operator[](Client::kWaitTrackReady) = true;
}
} /* namespace mediakit */

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_PLAYER_PLAYERBASE_H_
#define SRC_PLAYER_PLAYERBASE_H_
#include <map>
#include <memory>
#include <string>
#include <functional>
#include "Network/Socket.h"
#include "Util/mini.h"
#include "Common/MediaSource.h"
#include "Common/MediaSink.h"
#include "Extension/Frame.h"
#include "Extension/Track.h"
namespace mediakit {
class PlayerBase : public TrackSource, public toolkit::mINI {
public:
using Ptr = std::shared_ptr<PlayerBase>;
using Event = std::function<void(const toolkit::SockException &ex)>;
static Ptr createPlayer(const toolkit::EventPoller::Ptr &poller, const std::string &strUrl);
PlayerBase();
/**
*
* @param url urlrtsp/rtmp
* Start playback
* @param url Video url, supports rtsp/rtmp
* [AUTO-TRANSLATED:3871cbee]
*/
virtual void play(const std::string &url) {};
/**
*
* @param flag true:false:
* Pause or resume
* @param flag true: pause, false: resume
* [AUTO-TRANSLATED:2a17eab2]
*/
virtual void pause(bool flag) {};
/**
*
* Get the total duration of the program, in seconds
* [AUTO-TRANSLATED:f3de1631]
*/
virtual float getDuration() const { return 0; };
/**
*
* @param speed 1.0 2.0 0.5
* Playback at a multiple
* @param speed 1.0 2.0 0.5
* [AUTO-TRANSLATED:46bf057e]
*/
virtual void speed(float speed) {};
/**
*
* Interrupt playback
* [AUTO-TRANSLATED:d962e9bc]
*/
virtual void teardown() {};
/**
* 0.0 ~ 1.0
* Get playback progress, value 0.0 ~ 1.0
* [AUTO-TRANSLATED:ba24f450]
*/
virtual float getProgress() const { return 0; };
/**
* pos
* Get playback progress pos, value relative to the start time increment, unit seconds
* [AUTO-TRANSLATED:1eb148ad]
*/
virtual uint32_t getProgressPos() const { return 0; };
/**
*
* @param progress 0.0 ~ 1.0
* Drag the progress bar
* @param progress Progress, value 0.0 ~ 1.0
* [AUTO-TRANSLATED:c4907336]
*/
virtual void seekTo(float progress) {};
/**
*
* @param pos
* Drag the progress bar
* @param pos Progress, value relative to the start time increment, unit seconds
* [AUTO-TRANSLATED:77dab991]
*/
virtual void seekTo(uint32_t pos) {};
/**
* rtsp
* @param type TrackInvalid时为总丢包率
* Get packet loss rate, only supports rtsp
* @param type Audio or video, TrackInvalid for total packet loss rate
* [AUTO-TRANSLATED:aac7f19c]
*/
virtual float getPacketLossRate(TrackType type) const { return -1; };
/**
* track
* Get all tracks
* [AUTO-TRANSLATED:5860aed6]
*/
std::vector<Track::Ptr> getTracks(bool ready = true) const override { return std::vector<Track::Ptr>(); };
/**
* MediaSourcertsp/rtmp代理
* Set a MediaSource, directly produce rtsp/rtmp proxy
* [AUTO-TRANSLATED:dda602c4]
*/
virtual void setMediaSource(const MediaSource::Ptr &src) = 0;
/**
*
* Set exception interrupt callback
* [AUTO-TRANSLATED:d931e70d]
*/
virtual void setOnShutdown(const Event &cb) = 0;
/**
*
* Set playback result callback
* [AUTO-TRANSLATED:f6d73f89]
*/
virtual void setOnPlayResult(const Event &cb) = 0;
/**
*
* Set playback resume callback
* [AUTO-TRANSLATED:8fb31d43]
*/
virtual void setOnResume(const std::function<void()> &cb) = 0;
protected:
virtual void onResume() = 0;
virtual void onShutdown(const toolkit::SockException &ex) = 0;
virtual void onPlayResult(const toolkit::SockException &ex) = 0;
};
template<typename Parent, typename Delegate>
class PlayerImp : public Parent {
public:
using Ptr = std::shared_ptr<PlayerImp>;
template<typename ...ArgsType>
PlayerImp(ArgsType &&...args) : Parent(std::forward<ArgsType>(args)...) {}
void play(const std::string &url) override {
return _delegate ? _delegate->play(url) : Parent::play(url);
}
void pause(bool flag) override {
return _delegate ? _delegate->pause(flag) : Parent::pause(flag);
}
void speed(float speed) override {
return _delegate ? _delegate->speed(speed) : Parent::speed(speed);
}
void teardown() override {
return _delegate ? _delegate->teardown() : Parent::teardown();
}
float getPacketLossRate(TrackType type) const override {
return _delegate ? _delegate->getPacketLossRate(type) : Parent::getPacketLossRate(type);
}
float getDuration() const override {
return _delegate ? _delegate->getDuration() : Parent::getDuration();
}
float getProgress() const override {
return _delegate ? _delegate->getProgress() : Parent::getProgress();
}
uint32_t getProgressPos() const override {
return _delegate ? _delegate->getProgressPos() : Parent::getProgressPos();
}
void seekTo(float progress) override {
return _delegate ? _delegate->seekTo(progress) : Parent::seekTo(progress);
}
void seekTo(uint32_t pos) override {
return _delegate ? _delegate->seekTo(pos) : Parent::seekTo(pos);
}
std::vector<Track::Ptr> getTracks(bool ready = true) const override {
return _delegate ? _delegate->getTracks(ready) : Parent::getTracks(ready);
}
std::shared_ptr<toolkit::SockInfo> getSockInfo() const {
return std::dynamic_pointer_cast<toolkit::SockInfo>(_delegate);
}
void setMediaSource(const MediaSource::Ptr &src) override {
if (_delegate) {
_delegate->setMediaSource(src);
}
_media_src = src;
}
void setOnShutdown(const std::function<void(const toolkit::SockException &)> &cb) override {
if (_delegate) {
_delegate->setOnShutdown(cb);
}
_on_shutdown = cb;
}
void setOnPlayResult(const std::function<void(const toolkit::SockException &ex)> &cb) override {
if (_delegate) {
_delegate->setOnPlayResult(cb);
}
_on_play_result = cb;
}
void setOnResume(const std::function<void()> &cb) override {
if (_delegate) {
_delegate->setOnResume(cb);
}
_on_resume = cb;
}
protected:
void onShutdown(const toolkit::SockException &ex) override {
if (_on_shutdown) {
_on_shutdown(ex);
_on_shutdown = nullptr;
}
}
void onPlayResult(const toolkit::SockException &ex) override {
if (_on_play_result) {
_on_play_result(ex);
_on_play_result = nullptr;
}
}
void onResume() override {
if (_on_resume) {
_on_resume();
}
}
protected:
std::function<void()> _on_resume;
PlayerBase::Event _on_shutdown;
PlayerBase::Event _on_play_result;
MediaSource::Ptr _media_src;
std::shared_ptr<Delegate> _delegate;
};
} /* namespace mediakit */
#endif /* SRC_PLAYER_PLAYERBASE_H_ */

View File

@ -0,0 +1,374 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "PlayerProxy.h"
#include "Common/config.h"
#include "Rtmp/RtmpMediaSource.h"
#include "Rtmp/RtmpPlayer.h"
#include "Rtsp/RtspMediaSource.h"
#include "Rtsp/RtspPlayer.h"
#include "Util/MD5.h"
#include "Util/logger.h"
#include "Util/mini.h"
using namespace toolkit;
using namespace std;
namespace mediakit {
PlayerProxy::PlayerProxy(
const MediaTuple &tuple, const ProtocolOption &option, int retry_count,
const EventPoller::Ptr &poller, int reconnect_delay_min, int reconnect_delay_max, int reconnect_delay_step)
: MediaPlayer(poller), _tuple(tuple), _option(option) {
_retry_count = retry_count;
setOnClose(nullptr);
setOnConnect(nullptr);
setOnDisconnect(nullptr);
_reconnect_delay_min = reconnect_delay_min > 0 ? reconnect_delay_min : 2;
_reconnect_delay_max = reconnect_delay_max > 0 ? reconnect_delay_max : 60;
_reconnect_delay_step = reconnect_delay_step > 0 ? reconnect_delay_step : 3;
_live_secs = 0;
_live_status = 1;
_repull_count = 0;
(*this)[Client::kWaitTrackReady] = false;
}
void PlayerProxy::setPlayCallbackOnce(function<void(const SockException &ex)> cb) {
_on_play = std::move(cb);
}
void PlayerProxy::setOnClose(function<void(const SockException &ex)> cb) {
_on_close = cb ? std::move(cb) : [](const SockException &) {};
}
void PlayerProxy::setOnDisconnect(std::function<void()> cb) {
_on_disconnect = cb ? std::move(cb) : [] () {};
}
void PlayerProxy::setOnConnect(std::function<void(const TranslationInfo&)> cb) {
_on_connect = cb ? std::move(cb) : [](const TranslationInfo&) {};
}
void PlayerProxy::setTranslationInfo()
{
_transtalion_info.byte_speed = _media_src ? _media_src->getBytesSpeed() : -1;
_transtalion_info.start_time_stamp = _media_src ? _media_src->getCreateStamp() : 0;
_transtalion_info.stream_info.clear();
auto tracks = _muxer->getTracks();
for (auto &track : tracks) {
track->update();
_transtalion_info.stream_info.emplace_back();
auto &back = _transtalion_info.stream_info.back();
back.bitrate = track->getBitRate();
back.codec_type = track->getTrackType();
back.codec_name = track->getCodecName();
switch (back.codec_type) {
case TrackAudio : {
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
back.audio_sample_rate = audio_track->getAudioSampleRate();
back.audio_channel = audio_track->getAudioChannel();
back.audio_sample_bit = audio_track->getAudioSampleBit();
break;
}
case TrackVideo : {
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
back.video_width = video_track->getVideoWidth();
back.video_height = video_track->getVideoHeight();
back.video_fps = video_track->getVideoFps();
break;
}
default:
break;
}
}
}
static int getMaxTrackSize(const std::string &url) {
if (url.find(".m3u8") != std::string::npos || url.find(".ts") != std::string::npos) {
// hls和ts协议才开放多track支持 [AUTO-TRANSLATED:6c5f8f04]
// Only hls and ts protocols support multiple tracks
return 16;
}
return 2;
}
void PlayerProxy::play(const string &strUrlTmp) {
_option.max_track = getMaxTrackSize(strUrlTmp);
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
std::shared_ptr<int> piFailedCnt(new int(0)); // 连续播放失败次数
setOnPlayResult([weakSelf, strUrlTmp, piFailedCnt](const SockException &err) {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return;
}
if (strongSelf->_on_play) {
strongSelf->_on_play(err);
strongSelf->_on_play = nullptr;
}
if (!err) {
// 取消定时器,避免hls拉流索引文件因为网络波动失败重连成功后出现循环重试的情况 [AUTO-TRANSLATED:91e5f0c8]
// Cancel the timer to avoid the situation where the hls stream index file fails to reconnect due to network fluctuations and then retries in a loop after successful reconnection
strongSelf->_timer.reset();
strongSelf->_live_ticker.resetTime();
strongSelf->_live_status = 0;
// 播放成功 [AUTO-TRANSLATED:e43f9fb8]
// Play successfully
*piFailedCnt = 0; // 连续播放失败次数清0
strongSelf->onPlaySuccess();
strongSelf->setTranslationInfo();
strongSelf->_on_connect(strongSelf->_transtalion_info);
InfoL << "play " << strUrlTmp << " success";
} else if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
// 播放失败,延时重试播放 [AUTO-TRANSLATED:d7537c9c]
// Play failed, retry playing with delay
strongSelf->_on_disconnect();
strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++);
} else {
// 达到了最大重试次数,回调关闭 [AUTO-TRANSLATED:610f31f3]
// Reached the maximum number of retries, callback to close
strongSelf->_on_close(err);
}
});
setOnShutdown([weakSelf, strUrlTmp, piFailedCnt](const SockException &err) {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return;
}
// 注销直接拉流代理产生的流:#532 [AUTO-TRANSLATED:c6343a3b]
// Unregister the stream generated by the direct stream proxy: #532
strongSelf->setMediaSource(nullptr);
if (strongSelf->_muxer) {
auto tracks = strongSelf->MediaPlayer::getTracks(false);
for (auto &track : tracks) {
track->delDelegate(strongSelf->_muxer.get());
}
GET_CONFIG(bool, reset_when_replay, General::kResetWhenRePlay);
if (reset_when_replay) {
strongSelf->_muxer.reset();
} else {
strongSelf->_muxer->resetTracks();
}
}
if (*piFailedCnt == 0) {
// 第一次重拉更新时长 [AUTO-TRANSLATED:3c414b08]
// Update the duration for the first time
strongSelf->_live_secs += strongSelf->_live_ticker.elapsedTime() / 1000;
strongSelf->_live_ticker.resetTime();
TraceL << " live secs " << strongSelf->_live_secs;
}
// 播放异常中断,延时重试播放 [AUTO-TRANSLATED:fee316b2]
// Play interrupted abnormally, retry playing with delay
if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
strongSelf->_repull_count++;
strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++);
} else {
// 达到了最大重试次数,回调关闭 [AUTO-TRANSLATED:610f31f3]
// Reached the maximum number of retries, callback to close
strongSelf->_on_close(err);
}
});
try {
MediaPlayer::play(strUrlTmp);
} catch (std::exception &ex) {
ErrorL << ex.what();
onPlayResult(SockException(Err_other, ex.what()));
return;
}
_pull_url = strUrlTmp;
setDirectProxy();
}
void PlayerProxy::setDirectProxy() {
MediaSource::Ptr mediaSource;
if (dynamic_pointer_cast<RtspPlayer>(_delegate)) {
// rtsp拉流 [AUTO-TRANSLATED:189cf691]
// Rtsp stream
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
if (directProxy && _option.enable_rtsp) {
mediaSource = std::make_shared<RtspMediaSource>(_tuple);
}
} else if (dynamic_pointer_cast<RtmpPlayer>(_delegate)) {
// rtmp拉流 [AUTO-TRANSLATED:f70a142c]
// Rtmp stream
GET_CONFIG(bool, directProxy, Rtmp::kDirectProxy);
if (directProxy && _option.enable_rtmp) {
mediaSource = std::make_shared<RtmpMediaSource>(_tuple);
}
}
if (mediaSource) {
setMediaSource(mediaSource);
}
}
PlayerProxy::~PlayerProxy() {
_timer.reset();
// 避免析构时, 忘记回调api请求 [AUTO-TRANSLATED:1ad9ad52]
// Avoid forgetting to callback api request when destructing
if (_on_play) {
try {
_on_play(SockException(Err_shutdown, "player proxy close"));
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}
_on_play = nullptr;
}
}
void PlayerProxy::rePlay(const string &strUrl, int iFailedCnt) {
auto iDelay = MAX(_reconnect_delay_min * 1000, MIN(iFailedCnt * _reconnect_delay_step * 1000, _reconnect_delay_max * 1000));
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
_timer = std::make_shared<Timer>(
iDelay / 1000.0f,
[weakSelf, strUrl, iFailedCnt]() {
// 播放失败次数越多,则延时越长 [AUTO-TRANSLATED:5af39264]
// The more times the playback fails, the longer the delay
auto strongPlayer = weakSelf.lock();
if (!strongPlayer) {
return false;
}
WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl;
strongPlayer->MediaPlayer::play(strUrl);
strongPlayer->setDirectProxy();
return false;
},
getPoller());
}
bool PlayerProxy::close(MediaSource &sender) {
// 通知其停止推流 [AUTO-TRANSLATED:d69d10d8]
// Notify it to stop pushing the stream
weak_ptr<PlayerProxy> weakSelf = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
getPoller()->async_first([weakSelf]() {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return;
}
strongSelf->_muxer.reset();
strongSelf->setMediaSource(nullptr);
strongSelf->teardown();
});
_on_close(SockException(Err_shutdown, "closed by user"));
WarnL << "close media: " << sender.getUrl();
return true;
}
int PlayerProxy::totalReaderCount() {
return (_muxer ? _muxer->totalReaderCount() : 0) + (_media_src ? _media_src->readerCount() : 0);
}
int PlayerProxy::totalReaderCount(MediaSource &sender) {
return totalReaderCount();
}
MediaOriginType PlayerProxy::getOriginType(MediaSource &sender) const {
return MediaOriginType::pull;
}
string PlayerProxy::getOriginUrl(MediaSource &sender) const {
return _pull_url;
}
std::shared_ptr<SockInfo> PlayerProxy::getOriginSock(MediaSource &sender) const {
return getSockInfo();
}
float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) {
return getPacketLossRate(type);
}
TranslationInfo PlayerProxy::getTranslationInfo() {
return _transtalion_info;
}
void PlayerProxy::onPlaySuccess() {
GET_CONFIG(bool, reset_when_replay, General::kResetWhenRePlay);
if (dynamic_pointer_cast<RtspMediaSource>(_media_src)) {
// rtsp拉流代理 [AUTO-TRANSLATED:3935cf68]
// Rtsp stream proxy
if (reset_when_replay || !_muxer) {
auto old = _option.enable_rtsp;
_option.enable_rtsp = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
_option.enable_rtsp = old;
}
} else if (dynamic_pointer_cast<RtmpMediaSource>(_media_src)) {
// rtmp拉流代理 [AUTO-TRANSLATED:21173335]
// Rtmp stream proxy
if (reset_when_replay || !_muxer) {
auto old = _option.enable_rtmp;
_option.enable_rtmp = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
_option.enable_rtmp = old;
}
} else {
// 其他拉流代理 [AUTO-TRANSLATED:e5f2e45d]
// Other stream proxies
if (reset_when_replay || !_muxer) {
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
}
}
_muxer->setMediaListener(shared_from_this());
auto videoTrack = getTrack(TrackVideo, false);
if (videoTrack) {
// 添加视频 [AUTO-TRANSLATED:afc7e0f7]
// Add video
_muxer->addTrack(videoTrack);
// 视频数据写入_mediaMuxer [AUTO-TRANSLATED:fc07e1c9]
// Write video data to _mediaMuxer
videoTrack->addDelegate(_muxer);
}
auto audioTrack = getTrack(TrackAudio, false);
if (audioTrack) {
// 添加音频 [AUTO-TRANSLATED:e08e79ce]
// Add audio
_muxer->addTrack(audioTrack);
// 音频数据写入_mediaMuxer [AUTO-TRANSLATED:69911524]
// Write audio data to _mediaMuxer
audioTrack->addDelegate(_muxer);
}
// 添加完毕所有track防止单track情况下最大等待3秒 [AUTO-TRANSLATED:8908bc01]
// After adding all tracks, prevent the maximum waiting time of 3 seconds in the case of a single track
_muxer->addTrackCompleted();
if (_media_src) {
// 让_muxer对象拦截一部分事件(比如说录像相关事件) [AUTO-TRANSLATED:7d27c400]
// Let the _muxer object intercept some events (such as recording related events)
_media_src->setListener(_muxer);
}
}
int PlayerProxy::getStatus() {
return _live_status.load();
}
uint64_t PlayerProxy::getLiveSecs() {
if (_live_status == 0) {
return _live_secs + _live_ticker.elapsedTime() / 1000;
}
return _live_secs;
}
uint64_t PlayerProxy::getRePullCount() {
return _repull_count;
}
} /* namespace mediakit */

View File

@ -0,0 +1,182 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_DEVICE_PLAYERPROXY_H_
#define SRC_DEVICE_PLAYERPROXY_H_
#include "Common/MultiMediaSourceMuxer.h"
#include "Player/MediaPlayer.h"
#include "Util/TimeTicker.h"
#include <memory>
namespace mediakit {
struct StreamInfo
{
TrackType codec_type;
std::string codec_name;
int bitrate;
int audio_sample_rate;
int audio_sample_bit;
int audio_channel;
int video_width;
int video_height;
float video_fps;
StreamInfo()
{
codec_type = TrackInvalid;
codec_name = "none";
bitrate = -1;
audio_sample_rate = -1;
audio_channel = -1;
audio_sample_bit = -1;
video_height = -1;
video_width = -1;
video_fps = -1.0;
}
};
struct TranslationInfo
{
std::vector<StreamInfo> stream_info;
int byte_speed;
uint64_t start_time_stamp;
TranslationInfo()
{
byte_speed = -1;
start_time_stamp = 0;
}
};
class PlayerProxy
: public MediaPlayer
, public MediaSourceEvent
, public std::enable_shared_from_this<PlayerProxy> {
public:
using Ptr = std::shared_ptr<PlayerProxy>;
// 如果retry_count<0,则一直重试播放否则重试retry_count次数 [AUTO-TRANSLATED:5582d53c]
// If retry_count < 0, then retry playing indefinitely; otherwise, retry retry_count times
// 默认一直重试 [AUTO-TRANSLATED:779d3c46]
// Default to retrying indefinitely
PlayerProxy(const MediaTuple &tuple, const ProtocolOption &option, int retry_count = -1,
const toolkit::EventPoller::Ptr &poller = nullptr,
int reconnect_delay_min = 2, int reconnect_delay_max = 60, int reconnect_delay_step = 3);
~PlayerProxy() override;
/**
* play结果回调play执行之前有效
* @param cb
* Set a callback for the play result, triggered only once; effective before play execution
* @param cb Callback object
* [AUTO-TRANSLATED:f34625f6]
*/
void setPlayCallbackOnce(std::function<void(const toolkit::SockException &ex)> cb);
/**
*
* @param cb
* Set a callback for active closure
* @param cb Callback object
* [AUTO-TRANSLATED:83b7700a]
*/
void setOnClose(std::function<void(const toolkit::SockException &ex)> cb);
/**
* Set a callback for failed server connection
* @param cb
* Set a callback for failed server connection
* @param cb Callback object
* [AUTO-TRANSLATED:e7f5e7cc]
*/
void setOnDisconnect(std::function<void()> cb);
/**
* Set a callback for a successful connection to the server
* @param cb
* Set a callback for a successful connection to the server
* @param cb Callback object
* [AUTO-TRANSLATED:b88e0d4c]
*/
void setOnConnect(std::function<void(const TranslationInfo&)> cb);
/**
*
* @param strUrl
* Start streaming playback
* @param strUrl
* [AUTO-TRANSLATED:a2f0e859]
*/
void play(const std::string &strUrl) override;
/**
*
* Get the total number of viewers
* [AUTO-TRANSLATED:6c1b8bf1]
*/
int totalReaderCount();
int getStatus();
uint64_t getLiveSecs();
uint64_t getRePullCount();
// Using this only makes sense after a successful connection to the server
TranslationInfo getTranslationInfo();
private:
// MediaSourceEvent override
bool close(MediaSource &sender) override;
int totalReaderCount(MediaSource &sender) override;
MediaOriginType getOriginType(MediaSource &sender) const override;
std::string getOriginUrl(MediaSource &sender) const override;
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
float getLossRate(MediaSource &sender, TrackType type) override;
void rePlay(const std::string &strUrl, int iFailedCnt);
void onPlaySuccess();
void setDirectProxy();
void setTranslationInfo();
private:
int _retry_count;
int _reconnect_delay_min;
int _reconnect_delay_max;
int _reconnect_delay_step;
MediaTuple _tuple;
ProtocolOption _option;
std::string _pull_url;
toolkit::Timer::Ptr _timer;
std::function<void()> _on_disconnect;
std::function<void(const TranslationInfo &info)> _on_connect;
std::function<void(const toolkit::SockException &ex)> _on_close;
std::function<void(const toolkit::SockException &ex)> _on_play;
TranslationInfo _transtalion_info;
MultiMediaSourceMuxer::Ptr _muxer;
toolkit::Ticker _live_ticker;
// 0 表示正常 1 表示正在尝试拉流 [AUTO-TRANSLATED:2080bedf]
// 0 indicates normal, 1 indicates attempting to stream
std::atomic<int> _live_status;
std::atomic<uint64_t> _live_secs;
std::atomic<uint64_t> _repull_count;
};
} /* namespace mediakit */
#endif /* SRC_DEVICE_PLAYERPROXY_H_ */

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "MediaPusher.h"
#include "PusherBase.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
MediaPusher::MediaPusher(const MediaSource::Ptr &src,
const EventPoller::Ptr &poller) {
_src = src;
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
}
MediaPusher::MediaPusher(const string &schema,
const string &vhost,
const string &app,
const string &stream,
const EventPoller::Ptr &poller) :
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
}
static void setOnCreateSocket_l(const std::shared_ptr<PusherBase> &delegate, const Socket::onCreateSocket &cb){
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
if (helper) {
helper->setOnCreateSocket(cb);
}
}
void MediaPusher::publish(const string &url) {
_delegate = PusherBase::createPusher(_poller, _src.lock(), url);
assert(_delegate);
setOnCreateSocket_l(_delegate, _on_create_socket);
_delegate->setOnShutdown(_on_shutdown);
_delegate->setOnPublished(_on_publish);
_delegate->mINI::operator=(*this);
_delegate->publish(url);
}
EventPoller::Ptr MediaPusher::getPoller(){
return _poller;
}
void MediaPusher::setOnCreateSocket(Socket::onCreateSocket cb){
setOnCreateSocket_l(_delegate, cb);
_on_create_socket = std::move(cb);
}
} /* namespace mediakit */

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_PUSHER_MEDIAPUSHER_H_
#define SRC_PUSHER_MEDIAPUSHER_H_
#include <memory>
#include <string>
#include "PusherBase.h"
namespace mediakit {
class MediaPusher : public PusherImp<PusherBase,PusherBase> {
public:
using Ptr = std::shared_ptr<MediaPusher>;
MediaPusher(const std::string &schema,
const std::string &vhost,
const std::string &app,
const std::string &stream,
const toolkit::EventPoller::Ptr &poller = nullptr);
MediaPusher(const MediaSource::Ptr &src,
const toolkit::EventPoller::Ptr &poller = nullptr);
void publish(const std::string &url) override;
toolkit::EventPoller::Ptr getPoller();
void setOnCreateSocket(toolkit::Socket::onCreateSocket cb);
private:
std::weak_ptr<MediaSource> _src;
toolkit::EventPoller::Ptr _poller;
toolkit::Socket::onCreateSocket _on_create_socket;
};
} /* namespace mediakit */
#endif /* SRC_PUSHER_MEDIAPUSHER_H_ */

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "PusherBase.h"
#include "Rtsp/RtspPusher.h"
#include "Rtmp/RtmpPusher.h"
using namespace toolkit;
namespace mediakit {
PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &in_poller,
const MediaSource::Ptr &src,
const std::string & url) {
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
std::weak_ptr<EventPoller> weak_poller = poller;
static auto release_func = [weak_poller](PusherBase *ptr) {
if (auto poller = weak_poller.lock()) {
poller->async([ptr]() {
onceToken token(nullptr, [&]() { delete ptr; });
ptr->teardown();
});
} else {
delete ptr;
}
};
std::string prefix = findSubString(url.data(), NULL, "://");
if (strcasecmp("rtsps",prefix.data()) == 0) {
return PusherBase::Ptr(new TcpClientWithSSL<RtspPusherImp>(poller, std::dynamic_pointer_cast<RtspMediaSource>(src)), release_func);
}
if (strcasecmp("rtsp",prefix.data()) == 0) {
return PusherBase::Ptr(new RtspPusherImp(poller, std::dynamic_pointer_cast<RtspMediaSource>(src)), release_func);
}
if (strcasecmp("rtmps",prefix.data()) == 0) {
return PusherBase::Ptr(new TcpClientWithSSL<RtmpPusherImp>(poller, std::dynamic_pointer_cast<RtmpMediaSource>(src)), release_func);
}
if (strcasecmp("rtmp",prefix.data()) == 0) {
return PusherBase::Ptr(new RtmpPusherImp(poller, std::dynamic_pointer_cast<RtmpMediaSource>(src)), release_func);
}
throw std::invalid_argument("not supported push schema:" + url);
}
PusherBase::PusherBase() {
this->mINI::operator[](Client::kTimeoutMS) = 10000;
this->mINI::operator[](Client::kBeatIntervalMS) = 5000;
}
} /* namespace mediakit */

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_PUSHER_PUSHERBASE_H_
#define SRC_PUSHER_PUSHERBASE_H_
#include <map>
#include <memory>
#include <string>
#include <functional>
#include "Network/Socket.h"
#include "Util/mini.h"
#include "Common/MediaSource.h"
namespace mediakit {
class PusherBase : public toolkit::mINI {
public:
using Ptr = std::shared_ptr<PusherBase>;
using Event = std::function<void(const toolkit::SockException &ex)>;
static Ptr createPusher(const toolkit::EventPoller::Ptr &poller,
const MediaSource::Ptr &src,
const std::string &strUrl);
PusherBase();
virtual ~PusherBase() = default;
/**
*
* @param strUrl urlrtsp/rtmp
* Start streaming
* @param strUrl Video url, supports rtsp/rtmp
* [AUTO-TRANSLATED:d1decdf6]
*/
virtual void publish(const std::string &strUrl) {};
/**
*
* Stop streaming
* [AUTO-TRANSLATED:db8d228b]
*/
virtual void teardown() {};
/**
*
* Camera streaming result callback
* [AUTO-TRANSLATED:33825a4d]
*/
virtual void setOnPublished(const Event &cb) = 0;
/**
*
* Set disconnect callback
* [AUTO-TRANSLATED:b948082c]
*/
virtual void setOnShutdown(const Event &cb) = 0;
protected:
virtual void onShutdown(const toolkit::SockException &ex) = 0;
virtual void onPublishResult(const toolkit::SockException &ex) = 0;
};
template<typename Parent, typename Delegate>
class PusherImp : public Parent {
public:
using Ptr = std::shared_ptr<PusherImp>;
template<typename ...ArgsType>
PusherImp(ArgsType &&...args) : Parent(std::forward<ArgsType>(args)...) {}
/**
*
* @param url urlrtsp/rtmp
* Start streaming
* @param url Streaming url, supports rtsp/rtmp
* [AUTO-TRANSLATED:ffa95c22]
*/
void publish(const std::string &url) override {
return _delegate ? _delegate->publish(url) : Parent::publish(url);
}
/**
*
* Stop streaming
* [AUTO-TRANSLATED:db8d228b]
*/
void teardown() override {
return _delegate ? _delegate->teardown() : Parent::teardown();
}
std::shared_ptr<toolkit::SockInfo> getSockInfo() const {
return std::dynamic_pointer_cast<toolkit::SockInfo>(_delegate);
}
/**
*
* Camera streaming result callback
* [AUTO-TRANSLATED:33825a4d]
*/
void setOnPublished(const PusherBase::Event &cb) override {
if (_delegate) {
_delegate->setOnPublished(cb);
}
_on_publish = cb;
}
/**
*
* Set disconnect callback
* [AUTO-TRANSLATED:b948082c]
*/
void setOnShutdown(const PusherBase::Event &cb) override {
if (_delegate) {
_delegate->setOnShutdown(cb);
}
_on_shutdown = cb;
}
protected:
void onShutdown(const toolkit::SockException &ex) override {
if (_on_shutdown) {
_on_shutdown(ex);
_on_shutdown = nullptr;
}
}
void onPublishResult(const toolkit::SockException &ex) override {
if (_on_publish) {
_on_publish(ex);
_on_publish = nullptr;
}
}
protected:
PusherBase::Event _on_shutdown;
PusherBase::Event _on_publish;
std::shared_ptr<Delegate> _delegate;
};
} /* namespace mediakit */
#endif /* SRC_PUSHER_PUSHERBASE_H_ */

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "PusherProxy.h"
using namespace toolkit;
using namespace std;
namespace mediakit {
PusherProxy::PusherProxy(const MediaSource::Ptr &src, int retry_count, const EventPoller::Ptr &poller)
: MediaPusher(src, poller) {
_retry_count = retry_count;
_on_close = [](const SockException &) {};
_weak_src = src;
_live_secs = 0;
_live_status = 1;
_republish_count = 0;
}
PusherProxy::~PusherProxy() {
_timer.reset();
}
void PusherProxy::setPushCallbackOnce(const function<void(const SockException &ex)> &cb) {
_on_publish = cb;
}
void PusherProxy::setOnClose(const function<void(const SockException &ex)> &cb) {
_on_close = cb;
}
void PusherProxy::publish(const string &dst_url) {
std::weak_ptr<PusherProxy> weak_self = shared_from_this();
std::shared_ptr<int> failed_cnt(new int(0));
setOnPublished([weak_self, dst_url, failed_cnt](const SockException &err) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
if (strong_self->_on_publish) {
strong_self->_on_publish(err);
strong_self->_on_publish = nullptr;
}
auto src = strong_self->_weak_src.lock();
if (!err) {
// 推流成功 [AUTO-TRANSLATED:28ce6e56]
// Stream successfully pushed
strong_self->_live_ticker.resetTime();
strong_self->_live_status = 0;
*failed_cnt = 0;
InfoL << "Publish " << dst_url << " success";
} else if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) {
// 推流失败,延时重试推送 [AUTO-TRANSLATED:92b094ae]
// Stream failed, retry pushing with delay
strong_self->_republish_count++;
strong_self->_live_status = 1;
strong_self->rePublish(dst_url, (*failed_cnt)++);
} else {
// 如果媒体源已经注销, 或达到了最大重试次数,回调关闭 [AUTO-TRANSLATED:444adf27]
// If the media source has been deregistered, or the maximum retry count has been reached, callback to close
strong_self->_on_close(err);
}
});
setOnShutdown([weak_self, dst_url, failed_cnt](const SockException &err) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
if (*failed_cnt == 0) {
// 第一次重推更新时长 [AUTO-TRANSLATED:5f778703]
// Update duration for the first re-push
strong_self->_live_secs += strong_self->_live_ticker.elapsedTime() / 1000;
strong_self->_live_ticker.resetTime();
TraceL << " live secs " << strong_self->_live_secs;
}
auto src = strong_self->_weak_src.lock();
// 推流异常中断,延时重试播放 [AUTO-TRANSLATED:e69e5a05]
// Stream abnormally interrupted, retry playing with delay
if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) {
strong_self->_republish_count++;
strong_self->rePublish(dst_url, (*failed_cnt)++);
} else {
// 如果媒体源已经注销, 或达到了最大重试次数,回调关闭 [AUTO-TRANSLATED:444adf27]
// If the media source has been deregistered, or the maximum retry count has been reached, callback to close
strong_self->_on_close(err);
}
});
MediaPusher::publish(dst_url);
}
void PusherProxy::rePublish(const string &dst_url, int failed_cnt) {
auto delay = MAX(2 * 1000, MIN(failed_cnt * 3000, 60 * 1000));
weak_ptr<PusherProxy> weak_self = shared_from_this();
_timer = std::make_shared<Timer>(
delay / 1000.0f,
[weak_self, dst_url, failed_cnt]() {
// 推流失败次数越多,则延时越长 [AUTO-TRANSLATED:bda77afe]
// The more times the stream fails, the longer the delay
auto strong_self = weak_self.lock();
if (!strong_self) {
return false;
}
WarnL << "推流重试[" << failed_cnt << "]:" << dst_url;
strong_self->MediaPusher::publish(dst_url);
return false;
},
getPoller());
}
int PusherProxy::getStatus() {
return _live_status.load();
}
uint64_t PusherProxy::getLiveSecs() {
if (_live_status == 0) {
return _live_secs + _live_ticker.elapsedTime() / 1000;
} else {
return _live_secs;
}
}
uint64_t PusherProxy::getRePublishCount() {
return _republish_count;
}
} /* namespace mediakit */

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_DEVICE_PUSHERPROXY_H
#define SRC_DEVICE_PUSHERPROXY_H
#include "Pusher/MediaPusher.h"
#include "Util/TimeTicker.h"
namespace mediakit {
class PusherProxy
: public MediaPusher
, public std::enable_shared_from_this<PusherProxy> {
public:
using Ptr = std::shared_ptr<PusherProxy>;
// 如果retry_count<0,则一直重试播放否则重试retry_count次数 [AUTO-TRANSLATED:5582d53c]
// If retry_count < 0, then retry playback indefinitely; otherwise, retry retry_count times
// 默认一直重试创建此对象时候需要外部保证MediaSource存在 [AUTO-TRANSLATED:c6159497]
// Default is to retry indefinitely. When creating this object, the external environment needs to ensure that MediaSource exists.
PusherProxy(const MediaSource::Ptr &src, int retry_count = -1, const toolkit::EventPoller::Ptr &poller = nullptr);
~PusherProxy() override;
/**
* push结果回调publish执行之前有效
* @param cb
* Set the push result callback, which is triggered only once; it is effective before publish is executed.
* @param cb Callback object
* [AUTO-TRANSLATED:7cd775fb]
*/
void setPushCallbackOnce(const std::function<void(const toolkit::SockException &ex)> &cb);
/**
*
* @param cb
* Set the active close callback
* @param cb Callback object
* [AUTO-TRANSLATED:83b7700a]
*/
void setOnClose(const std::function<void(const toolkit::SockException &ex)> &cb);
/**
*
* @param dstUrl
* Start pulling and playing the stream
* @param dstUrl Target push stream address
* [AUTO-TRANSLATED:a9a5da08]
*/
void publish(const std::string &dstUrl) override;
int getStatus();
uint64_t getLiveSecs();
uint64_t getRePublishCount();
private:
// 重推逻辑函数 [AUTO-TRANSLATED:e0fa273c]
// Repush logic function
void rePublish(const std::string &dstUrl, int iFailedCnt);
private:
int _retry_count;
toolkit::Timer::Ptr _timer;
toolkit::Ticker _live_ticker;
// 0 表示正常 1 表示正在尝试推流 [AUTO-TRANSLATED:acb9835e]
// 0 indicates normal, 1 indicates that the push stream is being attempted
std::atomic<int> _live_status;
std::atomic<uint64_t> _live_secs;
std::atomic<uint64_t> _republish_count;
std::weak_ptr<MediaSource> _weak_src;
std::function<void(const toolkit::SockException &ex)> _on_close;
std::function<void(const toolkit::SockException &ex)> _on_publish;
};
} /* namespace mediakit */
#endif // SRC_DEVICE_PUSHERPROXY_H

View File

@ -0,0 +1,214 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <iomanip>
#include "HlsMaker.h"
#include "Common/config.h"
using namespace std;
namespace mediakit {
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
_is_fmp4 = is_fmp4;
// 最小允许设置为00个切片代表点播 [AUTO-TRANSLATED:19235e8e]
// Minimum allowed setting is 0, 0 slices represent on-demand
_seg_number = seg_number;
_seg_duration = seg_duration;
_seg_keep = seg_keep;
}
void HlsMaker::makeIndexFile(bool include_delay, bool eof) {
GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay);
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
std::deque<std::tuple<int, std::string>> temp(_seg_dur_list);
if (!include_delay && _seg_number) {
while (temp.size() > _seg_number) {
temp.pop_front();
}
}
int maxSegmentDuration = 0;
for (auto &tp : temp) {
int dur = std::get<0>(tp);
if (dur > maxSegmentDuration) {
maxSegmentDuration = dur;
}
}
uint64_t index_seq;
if (_seg_number) {
if (include_delay) {
if (_file_index > _seg_number + segDelay) {
index_seq = _file_index - _seg_number - segDelay;
} else {
index_seq = 0LL;
}
} else {
if (_file_index > _seg_number) {
index_seq = _file_index - _seg_number;
} else {
index_seq = 0LL;
}
}
} else {
index_seq = 0LL;
}
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 {
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";
}
stringstream ss;
for (auto &tp : temp) {
ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n";
}
index_str += ss.str();
if (eof) {
index_str += "#EXT-X-ENDLIST\n";
}
onWriteHls(index_str, include_delay);
}
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(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
if (data && len) {
if (timestamp < _last_timestamp) {
// 时间戳回退了,切片时长重新计时 [AUTO-TRANSLATED:fe91bd7f]
// Timestamp has been rolled back, slice duration is recalculated
WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp;
_last_seg_timestamp = _last_timestamp = timestamp;
}
if (is_idr_fast_packet) {
// 尝试切片ts [AUTO-TRANSLATED:62264109]
// Attempt to slice ts
addNewSegment(timestamp);
}
if (!_last_file_name.empty()) {
// 存在切片才写入ts数据 [AUTO-TRANSLATED:ddd46115]
// Write ts data only if there are slices
onWriteSegment(data, len);
_last_timestamp = timestamp;
}
} else {
// resetTracks时触发此逻辑 [AUTO-TRANSLATED:0ba915ed]
// This logic is triggered when resetTracks is called
flushLastSegment(false);
}
}
void HlsMaker::delOldSegment() {
GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay);
if (_seg_number == 0) {
// 如果设置为保留0个切片则认为是保存为点播 [AUTO-TRANSLATED:5bf20108]
// If set to keep 0 slices, it is considered to be saved as on-demand
return;
}
// 在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 [AUTO-TRANSLATED:b14b5b98]
// In the hls m3u8 index file, the number of slices we save is consistent with the _seg_number setting
if (_file_index > _seg_number + segDelay) {
_seg_dur_list.pop_front();
}
// 如果设置为一直保存,就不删除 [AUTO-TRANSLATED:7c622e24]
// If set to always save, it will not be deleted
if (_seg_keep) {
return;
}
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
// 但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 [AUTO-TRANSLATED:1688f857]
// However, the actual number of slices saved is a few more than what is stated in the m3u8, this is done to prevent the player from downloading the slices before they are deleted
if (_file_index > _seg_number + segDelay + segRetain) {
onDelSegment(_file_index - _seg_number - segDelay - segRetain - 1);
}
}
void HlsMaker::addNewSegment(uint64_t stamp) {
GET_CONFIG(bool, fastRegister, Hls::kFastRegister);
if (_file_index > fastRegister && stamp - _last_seg_timestamp < _seg_duration * 1000) {
// 确保序号为0的切片立即open如果开启快速注册功能序号为1的切片也应该遇到关键帧立即生成否则需要等切片时长够长 [AUTO-TRANSLATED:d81d1a1c]
// Ensure that the slice with sequence number 0 is opened immediately, if the fast registration function is enabled, the slice with sequence number 1 should also be generated immediately when it encounters a keyframe; otherwise, it needs to wait until the slice duration is long enough
return;
}
// 关闭并保存上一个切片如果_seg_number==0,那么是点播。 [AUTO-TRANSLATED:14076b61]
// Close and save the previous slice, if _seg_number==0, then it is on-demand.
flushLastSegment(false);
// 新增切片 [AUTO-TRANSLATED:b8623419]
// Add a new slice
_last_file_name = onOpenSegment(_file_index++);
// 记录本次切片的起始时间戳 [AUTO-TRANSLATED:8eb776e9]
// Record the starting timestamp of this slice
_last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp;
}
void HlsMaker::flushLastSegment(bool eof){
GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay);
if (_last_file_name.empty()) {
// 不存在上个切片 [AUTO-TRANSLATED:d81fe08e]
// There is no previous slice
return;
}
// 文件创建到最后一次数据写入的时间即为切片长度 [AUTO-TRANSLATED:1f85739c]
// The time from file creation to the last data write is the slice length
auto seg_dur = _last_timestamp - _last_seg_timestamp;
if (seg_dur <= 0) {
seg_dur = 100;
}
_seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name));
delOldSegment();
// 先flush ts切片否则可能存在ts文件未写入完毕就被访问的情况 [AUTO-TRANSLATED:f8d6dc87]
// Flush the ts slice first, otherwise there may be a situation where the ts file is not written completely before it is accessed
onFlushLastSegment(seg_dur);
// 然后写m3u8文件 [AUTO-TRANSLATED:67200ce1]
// Then write the m3u8 file
makeIndexFile(false, eof);
// 写入切片延迟的m3u8文件 [AUTO-TRANSLATED:b1f12e43]
// Write the m3u8 file with slice delay
if (segDelay) {
makeIndexFile(true, eof);
}
}
bool HlsMaker::isLive() const {
return _seg_number != 0;
}
bool HlsMaker::isKeep() const {
return _seg_keep;
}
bool HlsMaker::isFmp4() const {
return _is_fmp4;
}
void HlsMaker::clear() {
_file_index = 0;
_last_timestamp = 0;
_last_seg_timestamp = 0;
_seg_dur_list.clear();
_last_file_name.clear();
}
}//namespace mediakit

View File

@ -0,0 +1,215 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HLSMAKER_H
#define HLSMAKER_H
#include <string>
#include <deque>
#include <tuple>
#include <cstdint>
namespace mediakit {
class HlsMaker {
public:
/**
* @param is_fmp4 使fmp4还是mpegts
* @param seg_duration
* @param seg_number
* @param seg_keep
* @param is_fmp4 Use fmp4 or mpegts
* @param seg_duration Segment file length
* @param seg_number Number of segments
* @param seg_keep Whether to keep the segment file
* [AUTO-TRANSLATED:260bbca3]
*/
HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
virtual ~HlsMaker() = default;
/**
* ts数据
* @param data
* @param len
* @param timestamp
* @param is_idr_fast_packet
* Write ts data
* @param data Data
* @param len Data length
* @param timestamp Millisecond timestamp
* @param is_idr_fast_packet Whether it is the first packet of the key frame
* [AUTO-TRANSLATED:b886bbbf]
*/
void inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet);
/**
* fmp4 init segment
* @param data
* @param len
* Input fmp4 init segment
* @param data Data
* @param len Data length
* [AUTO-TRANSLATED:8d613a42]
*/
void inputInitSegment(const char *data, size_t len);
/**
*
* Whether it is live
* [AUTO-TRANSLATED:1dae0496]
*/
bool isLive() const;
/**
*
* Whether to keep the segment file
* [AUTO-TRANSLATED:c2d1bce5]
*/
bool isKeep() const;
/**
* fmp4切片还是mpegts
* Whether to use fmp4 segmentation or mpegts
* [AUTO-TRANSLATED:36763fc8]
*/
bool isFmp4() const;
/**
*
* Clear records
* [AUTO-TRANSLATED:34a4b6cd]
*/
void clear();
protected:
/**
* ts切片文件回调
* @param index
* @return
* Create ts segment file callback
* @param index
* @return
* [AUTO-TRANSLATED:2a3806fc]
*/
virtual std::string onOpenSegment(uint64_t index) = 0;
/**
* ts切片文件回调
* @param index
* Delete ts segment file callback
* @param index
* [AUTO-TRANSLATED:1c0d4397]
*/
virtual void onDelSegment(uint64_t index) = 0;
/**
* init.mp4切片文件回调
* @param data
* @param len
* Write init.mp4 segment file callback
* @param data
* @param len
* [AUTO-TRANSLATED:e0021ec5]
*/
virtual void onWriteInitSegment(const char *data, size_t len) = 0;
/**
* ts切片文件回调
* @param data
* @param len
* Write ts segment file callback
* @param data
* @param len
* [AUTO-TRANSLATED:bb81e206]
*/
virtual void onWriteSegment(const char *data, size_t len) = 0;
/**
* m3u8文件回调
* Write m3u8 file callback
* [AUTO-TRANSLATED:5754525f]
*/
virtual void onWriteHls(const std::string &data, bool include_delay) = 0;
/**
* ts ,
* @param duration_ms ts ,
* The previous ts segment is written, you can notify here
* @param duration_ms The duration of the previous ts segment, in milliseconds
* [AUTO-TRANSLATED:36b42bc0]
*/
virtual void onFlushLastSegment(uint64_t duration_ms) {};
/**
* ts切片并且写入m3u8索引
* @param eof HLS直播是否已结束
* Close the previous ts segment and write the m3u8 index
* @param eof Whether the HLS live broadcast has ended
* [AUTO-TRANSLATED:614b7e14]
*/
void flushLastSegment(bool eof);
private:
/**
* m3u8文件
* @param eof true代表点播
* Generate m3u8 file
* @param eof true represents on-demand
* [AUTO-TRANSLATED:d6c74fb6]
*/
void makeIndexFile(bool include_delay, bool eof = false);
/**
* ts切片
* Delete old ts segments
* [AUTO-TRANSLATED:5da8bd70]
*/
void delOldSegment();
/**
* ts切片
* @param timestamp
* Add new ts segments
* @param timestamp
* [AUTO-TRANSLATED:e321e9f0]
*/
void addNewSegment(uint64_t timestamp);
private:
bool _is_fmp4 = false;
float _seg_duration = 0;
uint32_t _seg_number = 0;
bool _seg_keep = false;
uint64_t _last_timestamp = 0;
uint64_t _last_seg_timestamp = 0;
uint64_t _file_index = 0;
std::string _last_file_name;
std::deque<std::tuple<int,std::string> > _seg_dur_list;
};
}//namespace mediakit
#endif //HLSMAKER_H

View File

@ -0,0 +1,212 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <ctime>
#include <sys/stat.h>
#include "HlsMakerImp.h"
#include "Util/util.h"
#include "Util/uv_errno.h"
#include "Util/File.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
std::string getDelayPath(const std::string& originalPath) {
std::size_t pos = originalPath.find(".m3u8");
if (pos != std::string::npos) {
return originalPath.substr(0, pos) + "_delay.m3u8";
}
return originalPath;
}
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;
_path_hls_delay = getDelayPath(m3u8_file);
_params = params;
_buf_size = bufSize;
_file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; });
_info.folder = _path_prefix;
}
HlsMakerImp::~HlsMakerImp() {
try {
// 可能hls注册时导致抛异常 [AUTO-TRANSLATED:82add30d]
// Possible exception thrown during hls registration
clearCache(false, true);
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
void HlsMakerImp::clearCache() {
clearCache(true, false);
}
static void clearHls(const std::list<std::string> &files) {
for (auto &file : files) {
File::delete_file(file);
}
File::deleteEmptyDir(File::parentDir(files.back()));
}
void HlsMakerImp::clearCache(bool immediately, bool eof) {
// 录制完了 [AUTO-TRANSLATED:5d3bfbeb]
// Recording finished
flushLastSegment(eof);
if (!isLive() || isKeep()) {
return;
}
{
std::list<std::string> lst;
lst.emplace_back(_path_hls);
lst.emplace_back(_path_hls_delay);
if (!_path_init.empty() && eof) {
lst.emplace_back(_path_init);
}
for (auto &pr : _segment_file_paths) {
lst.emplace_back(std::move(pr.second));
}
// hls直播才删除文件 [AUTO-TRANSLATED:81d2aaa5]
// Delete file only after hls live streaming
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
if (!delay || immediately) {
clearHls(lst);
} else {
_poller->doDelayTask(delay * 1000, [lst]() {
clearHls(lst);
return 0;
});
}
}
clear();
_file = nullptr;
_segment_file_paths.clear();
}
string HlsMakerImp::onOpenSegment(uint64_t index) {
string segment_name, segment_path;
{
auto strDate = getTimeStr("%Y-%m-%d");
auto strHour = getTimeStr("%H");
auto strTime = getTimeStr("%M-%S");
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << (isFmp4() ? ".mp4" : ".ts");
segment_path = _path_prefix + "/" + segment_name;
if (isLive()) {
_segment_file_paths.emplace(index, segment_path);
}
}
_file = makeFile(segment_path, true);
// 保存本切片的元数据 [AUTO-TRANSLATED:64e6f692]
// Save metadata for this slice
_info.start_time = ::time(NULL);
_info.file_name = segment_name;
_info.file_path = segment_path;
_info.url = _info.app + "/" + _info.stream + "/" + segment_name;
if (!_file) {
WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg();
}
if (_params.empty()) {
return segment_name;
}
return segment_name + "?" + _params;
}
void HlsMakerImp::onDelSegment(uint64_t index) {
auto it = _segment_file_paths.find(index);
if (it == _segment_file_paths.end()) {
return;
}
File::delete_file(it->second.data(), true);
_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);
if (_file) {
fwrite(data, len, 1, _file.get());
_path_init = std::move(init_seg_path);
_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());
}
if (_media_src) {
_media_src->onSegmentSize(len);
}
}
void HlsMakerImp::onWriteHls(const std::string &data, bool include_delay) {
auto path = include_delay ? _path_hls_delay : _path_hls;
auto hls = makeFile(path);
if (hls) {
fwrite(data.data(), data.size(), 1, hls.get());
hls.reset();
if (_media_src && !include_delay) {
_media_src->setIndexFile(data);
}
} else {
WarnL << "Create hls file failed," << path << " " << get_uv_errmsg();
}
}
void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
// 关闭并flush文件到磁盘 [AUTO-TRANSLATED:9798ec4d]
// Close and flush file to disk
_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());
NOTICE_EMIT(BroadcastRecordTsArgs, Broadcast::kBroadcastRecordTs, _info);
}
}
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) {
auto file_buf = _file_buf;
auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
if (fp) {
fclose(fp);
}
});
if (ret && setbuf) {
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
}
return ret;
}
void HlsMakerImp::setMediaSource(const MediaTuple& tuple) {
static_cast<MediaTuple &>(_info) = tuple;
_media_src = std::make_shared<HlsMediaSource>(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info);
}
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
return _media_src;
}
} // namespace mediakit

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HLSMAKERIMP_H
#define HLSMAKERIMP_H
#include <memory>
#include <string>
#include <stdlib.h>
#include "HlsMaker.h"
#include "HlsMediaSource.h"
namespace mediakit {
class HlsMakerImp : public HlsMaker {
public:
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;
/**
*
* Set media information
* [AUTO-TRANSLATED:d205db9f]
*/
void setMediaSource(const MediaTuple& tuple);
/**
* MediaSource
* @return
* Get MediaSource
* @return
* [AUTO-TRANSLATED:af916433]
*/
HlsMediaSource::Ptr getMediaSource() const;
/**
*
* Clear cache
* [AUTO-TRANSLATED:f872d7e2]
*/
void clearCache();
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, bool include_delay) override;
void onFlushLastSegment(uint64_t duration_ms) override;
private:
std::shared_ptr<FILE> makeFile(const std::string &file,bool setbuf = false);
void clearCache(bool immediately, bool eof);
private:
int _buf_size;
std::string _params;
std::string _path_hls;
std::string _path_hls_delay;
std::string _path_init;
std::string _path_prefix;
RecordInfo _info;
std::shared_ptr<FILE> _file;
std::shared_ptr<char> _file_buf;
HlsMediaSource::Ptr _media_src;
toolkit::EventPoller::Ptr _poller;
std::map<uint64_t/*index*/,std::string/*file_path*/> _segment_file_paths;
};
}//namespace mediakit
#endif //HLSMAKERIMP_H

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "HlsMediaSource.h"
#include "Common/config.h"
using namespace toolkit;
namespace mediakit {
HlsCookieData::HlsCookieData(const MediaInfo &info, const std::shared_ptr<SockInfo> &sock_info) {
_info = info;
_sock_info = sock_info;
_added = std::make_shared<bool>(false);
addReaderCount();
}
void HlsCookieData::addReaderCount() {
if (!*_added) {
auto src = getMediaSource();
if (src) {
*_added = true;
_ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller());
auto added = _added;
_ring_reader->setDetachCB([added]() {
// HlsMediaSource已经销毁 [AUTO-TRANSLATED:bedb0385]
// HlsMediaSource has been destroyed
*added = false;
});
auto info = _sock_info;
_ring_reader->setGetInfoCB([info]() {
Any ret;
ret.set(info);
return ret;
});
}
}
}
HlsCookieData::~HlsCookieData() {
if (*_added) {
uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000;
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port()
<< ") " << "HLS播放器(" << _info.shortUrl() << ")断开,耗时(s):" << duration;
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
uint64_t bytes = _bytes.load();
if (bytes >= iFlowThreshold * 1024) {
try {
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, *_sock_info);
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}
}
}
}
void HlsCookieData::addByteUsage(size_t bytes) {
addReaderCount();
_bytes += bytes;
_ticker.resetTime();
}
void HlsCookieData::setMediaSource(const HlsMediaSource::Ptr &src) {
_src = src;
}
HlsMediaSource::Ptr HlsCookieData::getMediaSource() const {
return _src.lock();
}
void HlsMediaSource::setIndexFile(std::string index_file)
{
if (!_ring) {
std::weak_ptr<HlsMediaSource> weakSelf = std::static_pointer_cast<HlsMediaSource>(shared_from_this());
auto lam = [weakSelf](int size) {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return;
}
strongSelf->onReaderChanged(size);
};
_ring = std::make_shared<RingType>(0, std::move(lam));
regist();
}
// 赋值m3u8索引文件内容 [AUTO-TRANSLATED:c11882b5]
// Assign m3u8 index file content
std::lock_guard<std::mutex> lck(_mtx_index);
_index_file = std::move(index_file);
if (!_index_file.empty()) {
_list_cb.for_each([&](const std::function<void(const std::string& str)>& cb) { cb(_index_file); });
_list_cb.clear();
}
}
void HlsMediaSource::getIndexFile(std::function<void(const std::string& str)> cb)
{
std::lock_guard<std::mutex> lck(_mtx_index);
if (!_index_file.empty()) {
cb(_index_file);
return;
}
// 等待生成m3u8文件 [AUTO-TRANSLATED:c3ae3286]
// Waiting for m3u8 file generation
_list_cb.emplace_back(std::move(cb));
}
} // namespace mediakit

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_HLSMEDIASOURCE_H
#define ZLMEDIAKIT_HLSMEDIASOURCE_H
#include "Common/MediaSource.h"
#include "Util/TimeTicker.h"
#include "Util/RingBuffer.h"
#include <atomic>
namespace mediakit {
class HlsMediaSource : public MediaSource {
public:
friend class HlsCookieData;
using RingType = toolkit::RingBuffer<std::string>;
using Ptr = std::shared_ptr<HlsMediaSource>;
HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {}
/**
*
* Get the circular buffer of the media source
* [AUTO-TRANSLATED:75ac76b6]
*/
const RingType::Ptr &getRing() const { return _ring; }
/**
*
* Get the number of players
* [AUTO-TRANSLATED:a451c846]
*/
int readerCount() override { return _ring ? _ring->readerCount() : 0; }
/**
* m3u8索引文件内容
* Set or clear the m3u8 index file content
* [AUTO-TRANSLATED:71db921d]
*/
void setIndexFile(std::string index_file);
/**
* m3u8文件
* Asynchronously get the m3u8 file
* [AUTO-TRANSLATED:e962b3ad]
*/
void getIndexFile(std::function<void(const std::string &str)> cb);
/**
* m3u8文件
* Synchronously get the m3u8 file
* [AUTO-TRANSLATED:52b228df]
*/
std::string getIndexFile() const {
std::lock_guard<std::mutex> lck(_mtx_index);
return _index_file;
}
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;
mutable std::mutex _mtx_index;
toolkit::List<std::function<void(const std::string &)>> _list_cb;
};
class HlsCookieData {
public:
using Ptr = std::shared_ptr<HlsCookieData>;
HlsCookieData(const MediaInfo &info, const std::shared_ptr<toolkit::SockInfo> &sock_info);
~HlsCookieData();
void addByteUsage(size_t bytes);
void setMediaSource(const HlsMediaSource::Ptr &src);
HlsMediaSource::Ptr getMediaSource() const;
private:
void addReaderCount();
private:
std::atomic<uint64_t> _bytes { 0 };
MediaInfo _info;
std::shared_ptr<bool> _added;
toolkit::Ticker _ticker;
std::weak_ptr<HlsMediaSource> _src;
std::shared_ptr<toolkit::SockInfo> _sock_info;
HlsMediaSource::RingType::RingReader::Ptr _ring_reader;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_HLSMEDIASOURCE_H

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef HLSRECORDER_H
#define HLSRECORDER_H
#include "HlsMakerImp.h"
#include "MPEG.h"
#include "MP4Muxer.h"
#include "Common/config.h"
namespace mediakit {
template <typename Muxer>
class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this<HlsRecorderBase<Muxer> > {
public:
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>(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
// 清空上次的残余文件 [AUTO-TRANSLATED:e16122be]
// Clear the residual files from the last time
_hls->clearCache();
}
void setMediaSource(const MediaTuple& tuple) {
_hls->setMediaSource(tuple);
}
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
setDelegate(listener);
_hls->getMediaSource()->setListener(this->shared_from_this());
}
int readerCount() { return _hls->getMediaSource()->readerCount(); }
void onReaderChanged(MediaSource &sender, int size) override {
// hls保留切片个数为0时代表为hls录制(不删除切片)那么不管有无观看者都一直生成hls [AUTO-TRANSLATED:55709255]
// When the number of hls slices is 0, it means hls recording (not deleting slices), so hls is generated all the time regardless of whether there are viewers
_enabled = _option.hls_demand ? (_hls->isLive() ? size : true) : true;
if (!size && _hls->isLive() && _option.hls_demand) {
// hls直播时如果无人观看就删除视频缓存目的是为了防止视频跳跃 [AUTO-TRANSLATED:1d875c6a]
// When hls is live, if no one is watching, delete the video cache to prevent video jumping
_clear_cache = true;
}
MediaSourceEventInterceptor::onReaderChanged(sender, size);
}
bool inputFrame(const Frame::Ptr &frame) override {
if (_clear_cache && _option.hls_demand) {
_clear_cache = false;
// 清空旧的m3u8索引文件于ts切片 [AUTO-TRANSLATED:a4ce0664]
// Clear the old m3u8 index file and ts slices
_hls->clearCache();
_hls->getMediaSource()->setIndexFile("");
}
if (_enabled || !_option.hls_demand) {
return Muxer::inputFrame(frame);
}
return false;
}
bool isEnabled() {
// 缓存尚未清空时还允许触发inputFrame函数以便及时清空缓存 [AUTO-TRANSLATED:7cfd4d49]
// When the cache has not been cleared, it is still allowed to trigger the inputFrame function to clear the cache in time
return _option.hls_demand ? (_clear_cache ? true : _enabled) : true;
}
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 {
try {
this->flush();
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
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 {
try {
this->flush();
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
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

187
MediaServer/Record/MP4.cpp Normal file
View File

@ -0,0 +1,187 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#if defined(ENABLE_MP4)
#include "MP4.h"
#include "Util/File.h"
#include "Util/logger.h"
#include "Common/config.h"
using namespace toolkit;
using namespace std;
namespace mediakit {
static struct mov_buffer_t s_io = {
[](void *ctx, void *data, uint64_t bytes) {
MP4FileIO *thiz = (MP4FileIO *) ctx;
return thiz->onRead(data, bytes);
},
[](void *ctx, const void *data, uint64_t bytes) {
MP4FileIO *thiz = (MP4FileIO *) ctx;
return thiz->onWrite(data, bytes);
},
[](void *ctx, int64_t offset) {
MP4FileIO *thiz = (MP4FileIO *) ctx;
return thiz->onSeek(offset);
},
[](void *ctx) {
MP4FileIO *thiz = (MP4FileIO *) ctx;
return (int64_t)thiz->onTell();
}
};
MP4FileIO::Writer MP4FileIO::createWriter(int flags, bool is_fmp4){
Writer writer;
Ptr self = shared_from_this();
// 保存自己的强引用,防止提前释放 [AUTO-TRANSLATED:e8e14f60]
// Save a strong reference to itself to prevent premature release
writer.reset(mp4_writer_create(is_fmp4, &s_io,this, flags),[self](mp4_writer_t *ptr){
if(ptr){
mp4_writer_destroy(ptr);
}
});
if(!writer){
throw std::runtime_error("写入mp4文件失败!");
}
return writer;
}
MP4FileIO::Reader MP4FileIO::createReader(){
Reader reader;
Ptr self = shared_from_this();
// 保存自己的强引用,防止提前释放 [AUTO-TRANSLATED:e8e14f60]
// Save a strong reference to itself to prevent premature release
reader.reset(mov_reader_create(&s_io,this),[self](mov_reader_t *ptr){
if(ptr){
mov_reader_destroy(ptr);
}
});
if(!reader){
throw std::runtime_error("读取mp4文件失败!");
}
return reader;
}
/////////////////////////////////////////////////////MP4FileDisk/////////////////////////////////////////////////////////
#if defined(_WIN32) || defined(_WIN64)
#define fseek64 _fseeki64
#define ftell64 _ftelli64
#else
#define fseek64 fseek
#define ftell64 ftell
#endif
void MP4FileDisk::openFile(const char *file, const char *mode) {
// 创建文件 [AUTO-TRANSLATED:bd145ed5]
// Create a file
auto fp = File::create_file(file, mode);
if(!fp){
throw std::runtime_error(string("打开文件失败:") + file);
}
GET_CONFIG(uint32_t,mp4BufSize,Record::kFileBufSize);
// 新建文件io缓存 [AUTO-TRANSLATED:fda9ff47]
// Create a new file io cache
std::shared_ptr<char> file_buf(new char[mp4BufSize],[](char *ptr){
if(ptr){
delete [] ptr;
}
});
if(file_buf){
// 设置文件io缓存 [AUTO-TRANSLATED:0ed9c8ad]
// Set the file io cache
setvbuf(fp, file_buf.get(), _IOFBF, mp4BufSize);
}
// 创建智能指针 [AUTO-TRANSLATED:e7920ab2]
// Create a smart pointer
_file.reset(fp,[file_buf](FILE *fp) {
fflush(fp);
fclose(fp);
});
}
void MP4FileDisk::closeFile() {
_file = nullptr;
}
int MP4FileDisk::onRead(void *data, size_t bytes) {
if (bytes == fread(data, 1, bytes, _file.get())){
return 0;
}
return 0 != ferror(_file.get()) ? ferror(_file.get()) : -1 /*EOF*/;
}
int MP4FileDisk::onWrite(const void *data, size_t bytes) {
return bytes == fwrite(data, 1, bytes, _file.get()) ? 0 : ferror(_file.get());
}
int MP4FileDisk::onSeek(uint64_t offset) {
return fseek64(_file.get(), offset, SEEK_SET);
}
uint64_t MP4FileDisk::onTell() {
return ftell64(_file.get());
}
/////////////////////////////////////////////////////MP4FileMemory/////////////////////////////////////////////////////////
string MP4FileMemory::getAndClearMemory(){
string ret;
ret.swap(_memory);
_offset = 0;
return ret;
}
size_t MP4FileMemory::fileSize() const{
return _memory.size();
}
uint64_t MP4FileMemory::onTell(){
return _offset;
}
int MP4FileMemory::onSeek(uint64_t offset){
if (offset > _memory.size()) {
return -1;
}
_offset = offset;
return 0;
}
int MP4FileMemory::onRead(void *data, size_t bytes){
if (_offset >= _memory.size()) {
//EOF
return -1;
}
bytes = MIN(bytes, _memory.size() - _offset);
memcpy(data, _memory.data(), bytes);
_offset += bytes;
return 0;
}
int MP4FileMemory::onWrite(const void *data, size_t bytes){
if (_offset + bytes > _memory.size()) {
// 需要扩容 [AUTO-TRANSLATED:211c91e3]
// Need to expand
_memory.resize(_offset + bytes);
}
memcpy((uint8_t *) _memory.data() + _offset, data, bytes);
_offset += bytes;
return 0;
}
}//namespace mediakit
#endif // defined(ENABLE_MP4)

182
MediaServer/Record/MP4.h Normal file
View File

@ -0,0 +1,182 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MP4_H
#define ZLMEDIAKIT_MP4_H
#if defined(ENABLE_MP4)
#include <memory>
#include <string>
#include "mp4-writer.h"
#include "mov-writer.h"
#include "mov-reader.h"
#include "mpeg4-hevc.h"
#include "mpeg4-avc.h"
#include "mpeg4-aac.h"
#include "mov-buffer.h"
#include "mov-format.h"
namespace mediakit {
// mp4文件IO的抽象接口类 [AUTO-TRANSLATED:dab24105]
// Abstract interface class for mp4 file IO
class MP4FileIO : public std::enable_shared_from_this<MP4FileIO> {
public:
using Ptr = std::shared_ptr<MP4FileIO>;
using Writer = std::shared_ptr<mp4_writer_t>;
using Reader = std::shared_ptr<mov_reader_t>;
virtual ~MP4FileIO() = default;
/**
* mp4复用器
* @param flags 0MOV_FLAG_FASTSTARTMOV_FLAG_SEGMENT
* @param is_fmp4 fmp4还是普通mp4
* @return mp4复用器
* Create an mp4 muxer
* @param flags Supports 0, MOV_FLAG_FASTSTART, MOV_FLAG_SEGMENT
* @param is_fmp4 Whether it is fmp4 or ordinary mp4
* @return mp4 muxer
* [AUTO-TRANSLATED:97fefe95]
*/
virtual Writer createWriter(int flags, bool is_fmp4 = false);
/**
* mp4解复用器
* @return mp4解复用器
* Create an mp4 demuxer
* @return mp4 demuxer
* [AUTO-TRANSLATED:4a303019]
*/
virtual Reader createReader();
/**
*
* Get the file read/write position
* [AUTO-TRANSLATED:f8a5b290]
*/
virtual uint64_t onTell() = 0;
/**
* seek至文件某处
* @param offset
* @return (0)
* Seek to a certain location in the file
* @param offset File offset
* @return Whether it is successful (0 successful)
* [AUTO-TRANSLATED:936089eb]
*/
virtual int onSeek(uint64_t offset) = 0;
/**
*
* @param data
* @param bytes
* @return (0)
* Read a certain amount of data from the file
* @param data Data storage pointer
* @param bytes Pointer length
* @return Whether it is successful (0 successful)
* [AUTO-TRANSLATED:926bf3f0]
*/
virtual int onRead(void *data, size_t bytes) = 0;
/**
*
* @param data
* @param bytes
* @return (0)
* Write a certain amount of data to the file
* @param data Data pointer
* @param bytes Data length
* @return Whether it is successful (0 successful)
* [AUTO-TRANSLATED:dc0abb95]
*/
virtual int onWrite(const void *data, size_t bytes) = 0;
};
// 磁盘MP4文件类 [AUTO-TRANSLATED:e3f5ac07]
// Disk MP4 file class
class MP4FileDisk : public MP4FileIO {
public:
using Ptr = std::shared_ptr<MP4FileDisk>;
/**
*
* @param file
* @param mode fopen的方式
* Open the disk file
* @param file File path
* @param mode fopen mode
* [AUTO-TRANSLATED:c3144f10]
*/
void openFile(const char *file, const char *mode);
/**
*
* Close the disk file
* [AUTO-TRANSLATED:fc6b4f50]
*/
void closeFile();
protected:
uint64_t onTell() override;
int onSeek(uint64_t offset) override;
int onRead(void *data, size_t bytes) override;
int onWrite(const void *data, size_t bytes) override;
private:
std::shared_ptr<FILE> _file;
};
class MP4FileMemory : public MP4FileIO{
public:
using Ptr = std::shared_ptr<MP4FileMemory>;
/**
*
* Get the file size
* [AUTO-TRANSLATED:3a2b682a]
*/
size_t fileSize() const;
/**
*
* Get and clear the file cache
* [AUTO-TRANSLATED:620d5cf6]
*/
std::string getAndClearMemory();
protected:
uint64_t onTell() override;
int onSeek(uint64_t offset) override;
int onRead(void *data, size_t bytes) override;
int onWrite(const void *data, size_t bytes) override;
private:
uint64_t _offset = 0;
std::string _memory;
};
}//namespace mediakit
#endif //defined(ENABLE_MP4)
#endif //ZLMEDIAKIT_MP4_H

View File

@ -0,0 +1,193 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifdef ENABLE_MP4
#include "MP4Demuxer.h"
#include "Util/logger.h"
#include "Extension/Factory.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
MP4Demuxer::~MP4Demuxer() {
closeMP4();
}
void MP4Demuxer::openMP4(const string &file) {
closeMP4();
_mp4_file = std::make_shared<MP4FileDisk>();
_mp4_file->openFile(file.data(), "rb+");
_mov_reader = _mp4_file->createReader();
getAllTracks();
_duration_ms = mov_reader_getduration(_mov_reader.get());
}
void MP4Demuxer::closeMP4() {
_mov_reader.reset();
_mp4_file.reset();
}
int MP4Demuxer::getAllTracks() {
static mov_reader_trackinfo_t s_on_track = {
[](void *param, uint32_t track, uint8_t object, int width, int height, const void *extra, size_t bytes) {
//onvideo
MP4Demuxer *thiz = (MP4Demuxer *)param;
thiz->onVideoTrack(track,object,width,height,extra,bytes);
},
[](void *param, uint32_t track, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void *extra, size_t bytes) {
//onaudio
MP4Demuxer *thiz = (MP4Demuxer *)param;
thiz->onAudioTrack(track,object,channel_count,bit_per_sample,sample_rate,extra,bytes);
},
[](void *param, uint32_t track, uint8_t object, const void *extra, size_t bytes) {
//onsubtitle, do nothing
}
};
return mov_reader_getinfo(_mov_reader.get(),&s_on_track,this);
}
void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int height, const void *extra, size_t bytes) {
auto video = Factory::getTrackByCodecId(getCodecByMovId(object));
if (!video) {
return;
}
video->setIndex(track);
_tracks.emplace(track, video);
if (extra && bytes) {
video->setExtraData((uint8_t *)extra, bytes);
}
}
void MP4Demuxer::onAudioTrack(uint32_t track, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void *extra, size_t bytes) {
auto audio = Factory::getTrackByCodecId(getCodecByMovId(object), sample_rate, channel_count, bit_per_sample / channel_count);
if (!audio) {
return;
}
audio->setIndex(track);
_tracks.emplace(track, audio);
if (extra && bytes) {
audio->setExtraData((uint8_t *)extra, bytes);
}
}
int64_t MP4Demuxer::seekTo(int64_t stamp_ms) {
if(0 != mov_reader_seek(_mov_reader.get(),&stamp_ms)){
return -1;
}
return stamp_ms;
}
struct Context {
Context(MP4Demuxer *ptr) : thiz(ptr) {}
MP4Demuxer *thiz;
int flags = 0;
int64_t pts = 0;
int64_t dts = 0;
uint32_t track_id = 0;
BufferRaw::Ptr buffer;
};
Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
keyFrame = false;
eof = false;
static mov_reader_onread2 mov_onalloc = [](void *param, uint32_t track_id, size_t bytes, int64_t pts, int64_t dts, int flags) -> void * {
Context *ctx = (Context *) param;
ctx->pts = pts;
ctx->dts = dts;
ctx->flags = flags;
ctx->track_id = track_id;
ctx->buffer = ctx->thiz->_buffer_pool.obtain2();
ctx->buffer->setCapacity(bytes + 1);
ctx->buffer->setSize(bytes);
return ctx->buffer->data();
};
Context ctx(this);
auto ret = mov_reader_read2(_mov_reader.get(), mov_onalloc, &ctx);
switch (ret) {
case 0 : {
eof = true;
return nullptr;
}
case 1 : {
keyFrame = ctx.flags & MOV_AV_FLAG_KEYFREAME;
return makeFrame(ctx.track_id, ctx.buffer, ctx.pts, ctx.dts);
}
default : {
eof = true;
WarnL << "读取mp4文件数据失败:" << ret;
return nullptr;
}
}
}
Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int64_t pts, int64_t dts) {
auto it = _tracks.find(track_id);
if (it == _tracks.end()) {
return nullptr;
}
Frame::Ptr ret;
auto codec = it->second->getCodecId();
switch (codec) {
case CodecH264:
case CodecH265: {
auto bytes = buf->size();
auto data = buf->data();
auto offset = 0u;
while (offset < bytes) {
uint32_t frame_len;
memcpy(&frame_len, data + offset, 4);
frame_len = ntohl(frame_len);
if (frame_len + offset + 4 > bytes) {
return nullptr;
}
memcpy(data + offset, "\x00\x00\x00\x01", 4);
offset += (frame_len + 4);
}
ret = Factory::getFrameFromBuffer(codec, std::move(buf), dts, pts);
break;
}
default: {
ret = Factory::getFrameFromBuffer(codec, std::move(buf), dts, pts);
break;
}
}
if (ret) {
ret->setIndex(track_id);
it->second->inputFrame(ret);
}
return ret;
}
vector<Track::Ptr> MP4Demuxer::getTracks(bool ready) const {
vector<Track::Ptr> ret;
for (auto &pr : _tracks) {
if (ready && !pr.second->ready()) {
continue;
}
ret.push_back(pr.second);
}
return ret;
}
uint64_t MP4Demuxer::getDurationMS() const {
return _duration_ms;
}
}//namespace mediakit
#endif// ENABLE_MP4

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MP4DEMUXER_H
#define ZLMEDIAKIT_MP4DEMUXER_H
#ifdef ENABLE_MP4
#include "MP4.h"
#include "Extension/Track.h"
#include "Util/ResourcePool.h"
namespace mediakit {
class MP4Demuxer : public TrackSource {
public:
using Ptr = std::shared_ptr<MP4Demuxer>;
~MP4Demuxer() override;
/**
*
* @param file mp4文件路径
* Open file
* @param file mp4 file path
* [AUTO-TRANSLATED:a64c5a6b]
*/
void openMP4(const std::string &file);
/**
* @brief mp4
* @brief Close mp4 file
* [AUTO-TRANSLATED:527865d9]
*/
void closeMP4();
/**
*
* @param stamp_ms
* @return
* Move timeline to a specific location
* @param stamp_ms Expected timeline position, in milliseconds
* @return Timeline position
* [AUTO-TRANSLATED:51ce0f6d]
*/
int64_t seekTo(int64_t stamp_ms);
/**
*
* @param keyFrame
* @param eof
* @return ,
* Read a frame of data
* @param keyFrame Whether it is a key frame
* @param eof Whether the file has been read completely
* @return Frame data, may be empty
* [AUTO-TRANSLATED:adf550de]
*/
Frame::Ptr readFrame(bool &keyFrame, bool &eof);
/**
* Track信息
* @param trackReady track为就绪状态
* @return Track
* Get all Track information
* @param trackReady Whether to require the track to be ready
* @return All Tracks
* [AUTO-TRANSLATED:c07ad51a]
*/
std::vector<Track::Ptr> getTracks(bool trackReady) const override;
/**
*
* @return
* Get file length
* @return File length, in milliseconds
* [AUTO-TRANSLATED:dcd865d6]
*/
uint64_t getDurationMS() const;
private:
int getAllTracks();
void onVideoTrack(uint32_t track_id, uint8_t object, int width, int height, const void *extra, size_t bytes);
void onAudioTrack(uint32_t track_id, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void *extra, size_t bytes);
Frame::Ptr makeFrame(uint32_t track_id, const toolkit::Buffer::Ptr &buf, int64_t pts, int64_t dts);
private:
MP4FileDisk::Ptr _mp4_file;
MP4FileDisk::Reader _mov_reader;
uint64_t _duration_ms = 0;
std::unordered_map<int, Track::Ptr> _tracks;
toolkit::ResourcePool<toolkit::BufferRaw> _buffer_pool;
};
}//namespace mediakit
#endif//ENABLE_MP4
#endif //ZLMEDIAKIT_MP4DEMUXER_H

View File

@ -0,0 +1,260 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#if defined(ENABLE_MP4)
#include "MP4Muxer.h"
#include "Common/config.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
MP4Muxer::~MP4Muxer() {
closeMP4();
}
void MP4Muxer::openMP4(const string &file) {
closeMP4();
_file_name = file;
_mp4_file = std::make_shared<MP4FileDisk>();
_mp4_file->openFile(_file_name.data(), "wb+");
}
MP4FileIO::Writer MP4Muxer::createWriter() {
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
GET_CONFIG(bool, recordEnableFmp4, Record::kEnableFmp4);
return _mp4_file->createWriter(mp4FastStart ? MOV_FLAG_FASTSTART : 0, recordEnableFmp4);
}
void MP4Muxer::closeMP4() {
MP4MuxerInterface::resetTracks();
_mp4_file = nullptr;
}
void MP4Muxer::resetTracks() {
MP4MuxerInterface::resetTracks();
openMP4(_file_name);
}
/////////////////////////////////////////// MP4MuxerInterface /////////////////////////////////////////////
void MP4MuxerInterface::saveSegment() {
mp4_writer_save_segment(_mov_writter.get());
}
void MP4MuxerInterface::initSegment() {
mp4_writer_init_segment(_mov_writter.get());
}
bool MP4MuxerInterface::haveVideo() const {
return _have_video;
}
uint64_t MP4MuxerInterface::getDuration() const {
uint64_t ret = 0;
for (auto &pr : _tracks) {
if (pr.second.stamp.getRelativeStamp() > (int64_t)ret) {
ret = pr.second.stamp.getRelativeStamp();
}
}
return ret;
}
void MP4MuxerInterface::resetTracks() {
_started = false;
_have_video = false;
_mov_writter = nullptr;
_tracks.clear();
}
void MP4MuxerInterface::flush() {
for (auto &pr : _tracks) {
pr.second.merger.flush();
}
}
bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) {
auto it = _tracks.find(frame->getIndex());
if (it == _tracks.end()) {
// 该Track不存在或初始化失败 [AUTO-TRANSLATED:316597dc]
// This Track does not exist or initialization failed
return false;
}
if (!_started) {
// 该逻辑确保含有视频时,第一帧为关键帧 [AUTO-TRANSLATED:04f177fb]
// This logic ensures that the first frame is a keyframe when there is video
if (_have_video && !frame->keyFrame()) {
// 含有视频,但是不是关键帧,那么前面的帧丢弃 [AUTO-TRANSLATED:5f0ba99e]
// Contains video, but not a keyframe, then the previous frames are discarded
return false;
}
// 开始写文件 [AUTO-TRANSLATED:bc3f11e2]
// Start writing the file
_started = true;
}
// fmp4封装超过一定I帧间隔强制刷新segment防止内存上涨 [AUTO-TRANSLATED:0be6ef15]
// fmp4 encapsulation exceeds a certain I-frame interval, force refresh segment to prevent memory increase
if (frame->getTrackType() == TrackVideo && _mov_writter->fmp4) {
if (frame->keyFrame()) {
_non_iframe_video_count = 0;
} else {
_non_iframe_video_count++;
}
if (_non_iframe_video_count > 200) {
saveSegment();
_non_iframe_video_count = 0;
}
}
// mp4文件时间戳需要从0开始 [AUTO-TRANSLATED:c963b841]
// The mp4 file timestamp needs to start from 0
auto &track = it->second;
switch (frame->getCodecId()) {
case CodecH264:
case CodecH265: {
// 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理 [AUTO-TRANSLATED:edf57c32]
// The code logic here is to package frames with the same timestamp, such as SPS, PPS, and IDR, as one frame,
track.merger.inputFrame(frame, [this, &track](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) {
int64_t dts_out, pts_out;
track.stamp.revise(dts, pts, dts_out, pts_out);
mp4_writer_write(_mov_writter.get(), track.track_id, buffer->data(), buffer->size(), pts_out, dts_out, have_idr ? MOV_AV_FLAG_KEYFREAME : 0);
});
break;
}
default: {
int64_t dts_out, pts_out;
track.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
mp4_writer_write(_mov_writter.get(), track.track_id, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), pts_out, dts_out, frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
break;
}
}
return true;
}
void MP4MuxerInterface::stampSync() {
Stamp *first = nullptr;
for (auto &pr : _tracks) {
if (!first) {
first = &pr.second.stamp;
} else {
pr.second.stamp.syncTo(*first);
}
}
}
bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
if (!_mov_writter) {
_mov_writter = createWriter();
}
auto mp4_object = getMovIdByCodec(track->getCodecId());
if (mp4_object == MOV_OBJECT_NONE) {
WarnL << "Unsupported codec: " << track->getCodecName();
return false;
}
if (!track->ready()) {
WarnL << "Track " << track->getCodecName() << " unready";
return false;
}
track->update();
auto extra = track->getExtraData();
auto extra_data = extra ? extra->data() : nullptr;
auto extra_size = extra ? extra->size() : 0;
if (track->getTrackType() == TrackVideo) {
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
CHECK(video_track);
auto track_id = mp4_writer_add_video(_mov_writter.get(), mp4_object, video_track->getVideoWidth(), video_track->getVideoHeight(), extra_data, extra_size);
if (track_id < 0) {
WarnL << "mp4_writer_add_video failed: " << video_track->getCodecName();
return false;
}
_tracks[track->getIndex()].track_id = track_id;
_have_video = true;
_non_iframe_video_count = 0;
} else if (track->getTrackType() == TrackAudio) {
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
CHECK(audio_track);
auto track_id = mp4_writer_add_audio(_mov_writter.get(), mp4_object, audio_track->getAudioChannel(), audio_track->getAudioSampleBit() * audio_track->getAudioChannel(), audio_track->getAudioSampleRate(), extra_data, extra_size);
if (track_id < 0) {
WarnL << "mp4_writer_add_audio failed: " << audio_track->getCodecName();
return false;
}
_tracks[track->getIndex()].track_id = track_id;
}
// 尝试音视频同步 [AUTO-TRANSLATED:5f8b8040]
// Try audio and video synchronization
stampSync();
return true;
}
/////////////////////////////////////////// MP4MuxerMemory /////////////////////////////////////////////
MP4MuxerMemory::MP4MuxerMemory() {
_memory_file = std::make_shared<MP4FileMemory>();
}
MP4FileIO::Writer MP4MuxerMemory::createWriter() {
return _memory_file->createWriter(MOV_FLAG_SEGMENT, true);
}
const string &MP4MuxerMemory::getInitSegment() {
if (_init_segment.empty()) {
initSegment();
saveSegment();
_init_segment = _memory_file->getAndClearMemory();
}
return _init_segment;
}
void MP4MuxerMemory::resetTracks() {
MP4MuxerInterface::resetTracks();
_memory_file = std::make_shared<MP4FileMemory>();
_init_segment.clear();
}
bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
if (_init_segment.empty()) {
// 尚未生成init segment [AUTO-TRANSLATED:b4baa65f]
// Init segment has not been generated yet
return false;
}
// flush切片 [AUTO-TRANSLATED:c4358dce]
// Flush segment
saveSegment();
auto data = _memory_file->getAndClearMemory();
if (!data.empty()) {
// 输出切片数据 [AUTO-TRANSLATED:4bc994c9]
// Output segment data
onSegmentData(std::move(data), _last_dst, _key_frame);
_key_frame = false;
}
if (frame->keyFrame()) {
_key_frame = true;
}
if (frame->getTrackType() == TrackVideo || !haveVideo()) {
_last_dst = frame->dts();
}
return MP4MuxerInterface::inputFrame(frame);
}
} // namespace mediakit
#endif // defined(ENABLE_MP4)

View File

@ -0,0 +1,239 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_MP4MUXER_H
#define ZLMEDIAKIT_MP4MUXER_H
#if defined(ENABLE_MP4)
#include "Common/MediaSink.h"
#include "Common/Stamp.h"
#include "MP4.h"
namespace mediakit {
class MP4MuxerInterface : public MediaSinkInterface {
public:
/**
* ready状态的track
* Add tracks that are in ready state
* [AUTO-TRANSLATED:ea4983df]
*/
bool addTrack(const Track::Ptr &track) override;
/**
*
* Input frame
* [AUTO-TRANSLATED:c91b5ec6]
*/
bool inputFrame(const Frame::Ptr &frame) override;
/**
* track
* Reset all tracks
* [AUTO-TRANSLATED:f203fa3e]
*/
void resetTracks() override;
/**
* frame缓存
* Refresh all frame cache output
* [AUTO-TRANSLATED:adaea568]
*/
void flush() override;
/**
*
* Whether it contains video
* [AUTO-TRANSLATED:6d9e1039]
*/
bool haveVideo() const;
/**
* fmp4分片
* Save fmp4 fragment
* [AUTO-TRANSLATED:7b808759]
*/
void saveSegment();
/**
*
* Create new fragment
* [AUTO-TRANSLATED:b27545cf]
*/
void initSegment();
/**
* mp4时长,
* Get mp4 duration, in milliseconds
* [AUTO-TRANSLATED:d87afcfb]
*/
uint64_t getDuration() const;
protected:
virtual MP4FileIO::Writer createWriter() = 0;
private:
void stampSync();
private:
bool _started = false;
bool _have_video = false;
MP4FileIO::Writer _mov_writter;
int _non_iframe_video_count; // 非I帧个数
class FrameMergerImp : public FrameMerger {
public:
FrameMergerImp() : FrameMerger(FrameMerger::mp4_nal_size) {}
};
struct MP4Track {
int track_id = -1;
Stamp stamp;
FrameMergerImp merger;
};
std::unordered_map<int, MP4Track> _tracks;
};
class MP4Muxer : public MP4MuxerInterface{
public:
using Ptr = std::shared_ptr<MP4Muxer>;
~MP4Muxer() override;
/**
* track
* Reset all tracks
* [AUTO-TRANSLATED:f203fa3e]
*/
void resetTracks() override;
/**
* mp4
* @param file
* Open mp4
* @param file Full file path
* [AUTO-TRANSLATED:416892f4]
*/
void openMP4(const std::string &file);
/**
* ()
* Manually close the file (it will be closed automatically when the object is destructed)
* [AUTO-TRANSLATED:9ca68ff9]
*/
void closeMP4();
protected:
MP4FileIO::Writer createWriter() override;
private:
std::string _file_name;
MP4FileDisk::Ptr _mp4_file;
};
class MP4MuxerMemory : public MP4MuxerInterface{
public:
MP4MuxerMemory();
/**
* track
* Reset all tracks
* [AUTO-TRANSLATED:f203fa3e]
*/
void resetTracks() override;
/**
*
* Input frame
* [AUTO-TRANSLATED:c91b5ec6]
*/
bool inputFrame(const Frame::Ptr &frame) override;
/**
* fmp4 init segment
* Get fmp4 init segment
* [AUTO-TRANSLATED:6c704ec9]
*/
const std::string &getInitSegment();
protected:
/**
* fmp4切片回调函数
* @param std::string
* @param stamp
* @param key_frame
* Output fmp4 fragment callback function
* @param std::string Fragment content
* @param stamp Fragment end timestamp
* @param key_frame Whether there is a key frame
* [AUTO-TRANSLATED:dd742da5]
*/
virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0;
protected:
MP4FileIO::Writer createWriter() override;
private:
bool _key_frame = false;
uint64_t _last_dst = 0;
std::string _init_segment;
MP4FileMemory::Ptr _memory_file;
};
} // namespace mediakit
#else
#include "Common/MediaSink.h"
namespace mediakit {
class MP4MuxerMemory : public MediaSinkInterface {
public:
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
* Output fmp4 fragment callback function
* @param std::string Fragment content
* @param stamp Fragment end timestamp
* @param key_frame Whether there is a key frame
* [AUTO-TRANSLATED:dd742da5]
*/
virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0;
};
} // namespace mediakit
#endif //defined(ENABLE_MP4)
#endif //ZLMEDIAKIT_MP4MUXER_H

View File

@ -0,0 +1,291 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifdef ENABLE_MP4
#include "MP4Reader.h"
#include "Common/config.h"
#include "Thread/WorkThreadPool.h"
#include "Util/File.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
MP4Reader::MP4Reader(const MediaTuple &tuple, const string &file_path,
toolkit::EventPoller::Ptr poller) {
ProtocolOption option;
// 读取mp4文件并流化时不重复生成mp4/hls文件 [AUTO-TRANSLATED:5d414546]
// Read mp4 file and stream it, do not regenerate mp4/hls file repeatedly
option.enable_mp4 = false;
option.enable_hls = false;
option.enable_hls_fmp4 = false;
// mp4支持多track [AUTO-TRANSLATED:b9688762]
// mp4 supports multiple tracks
option.max_track = 16;
setup(tuple, file_path, option, std::move(poller));
}
MP4Reader::MP4Reader(const MediaTuple &tuple, const string &file_path, const ProtocolOption &option, toolkit::EventPoller::Ptr poller) {
setup(tuple, file_path, option, std::move(poller));
}
void MP4Reader::setup(const MediaTuple &tuple, const std::string &file_path, const ProtocolOption &option, toolkit::EventPoller::Ptr poller) {
// 读写文件建议放在后台线程 [AUTO-TRANSLATED:6f09ef53]
// It is recommended to read and write files in the background thread
_poller = poller ? std::move(poller) : WorkThreadPool::Instance().getPoller();
_file_path = file_path;
if (_file_path.empty()) {
GET_CONFIG(string, recordPath, Protocol::kMP4SavePath);
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (enableVhost) {
_file_path = tuple.shortUrl();
} else {
_file_path = tuple.app + "/" + tuple.stream;
}
_file_path = File::absolutePath(_file_path, recordPath);
}
_demuxer = std::make_shared<MP4Demuxer>();
_demuxer->openMP4(_file_path);
if (tuple.stream.empty()) {
return;
}
_muxer = std::make_shared<MultiMediaSourceMuxer>(tuple, _demuxer->getDurationMS() / 1000.0f, option);
auto tracks = _demuxer->getTracks(false);
if (tracks.empty()) {
throw std::runtime_error(StrPrinter << "该mp4文件没有有效的track:" << _file_path);
}
for (auto &track : tracks) {
_muxer->addTrack(track);
if (track->getTrackType() == TrackVideo) {
_have_video = true;
}
}
// 添加完毕所有track防止单track情况下最大等待3秒 [AUTO-TRANSLATED:445e3403]
// After all tracks are added, prevent the maximum waiting time of 3 seconds in the case of a single track
_muxer->addTrackCompleted();
}
bool MP4Reader::readSample() {
if (_paused) {
// 确保暂停时,时间轴不走动 [AUTO-TRANSLATED:3d38dd31]
// Ensure that the timeline does not move when paused
_seek_ticker.resetTime();
return true;
}
bool keyFrame = false;
bool eof = false;
while (!eof && _last_dts < getCurrentStamp()) {
auto frame = _demuxer->readFrame(keyFrame, eof);
if (!frame) {
continue;
}
_last_dts = frame->dts();
if (_muxer) {
_muxer->inputFrame(frame);
}
}
GET_CONFIG(bool, file_repeat, Record::kFileRepeat);
if (eof && (file_repeat || _file_repeat)) {
// 需要从头开始看 [AUTO-TRANSLATED:5b563a35]
// Need to start from the beginning
seekTo(0);
return true;
}
return !eof;
}
bool MP4Reader::readNextSample() {
bool keyFrame = false;
bool eof = false;
auto frame = _demuxer->readFrame(keyFrame, eof);
if (!frame) {
return false;
}
if (_muxer) {
_muxer->inputFrame(frame);
}
setCurrentStamp(frame->dts());
return true;
}
void MP4Reader::stopReadMP4() {
_timer = nullptr;
}
void MP4Reader::startReadMP4(uint64_t sample_ms, bool ref_self, bool file_repeat) {
GET_CONFIG(uint32_t, sampleMS, Record::kSampleMS);
setCurrentStamp(0);
auto strong_self = shared_from_this();
if (_muxer) {
// 一直读到所有track就绪为止 [AUTO-TRANSLATED:410f9ecc]
// Keep reading until all tracks are ready
while (!_muxer->isAllTrackReady() && readNextSample());
// 注册后再切换OwnerPoller [AUTO-TRANSLATED:4a483e23]
// Register and then switch OwnerPoller
_muxer->setMediaListener(strong_self);
}
auto timer_sec = (sample_ms ? sample_ms : sampleMS) / 1000.0f;
// 启动定时器 [AUTO-TRANSLATED:0b93ed77]
// Start the timer
if (ref_self) {
_timer = std::make_shared<Timer>(timer_sec, [strong_self]() {
lock_guard<recursive_mutex> lck(strong_self->_mtx);
return strong_self->readSample();
}, _poller);
} else {
weak_ptr<MP4Reader> weak_self = strong_self;
_timer = std::make_shared<Timer>(timer_sec, [weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
return false;
}
lock_guard<recursive_mutex> lck(strong_self->_mtx);
return strong_self->readSample();
}, _poller);
}
_file_repeat = file_repeat;
}
const MP4Demuxer::Ptr &MP4Reader::getDemuxer() const {
return _demuxer;
}
uint32_t MP4Reader::getCurrentStamp() {
return (uint32_t) (_seek_to + !_paused * _speed * _seek_ticker.elapsedTime());
}
void MP4Reader::setCurrentStamp(uint32_t new_stamp) {
auto old_stamp = getCurrentStamp();
_seek_to = new_stamp;
_last_dts = new_stamp;
_seek_ticker.resetTime();
if (old_stamp != new_stamp && _muxer) {
// 时间轴未拖动时不操作 [AUTO-TRANSLATED:c5b53103]
// Do not operate when the timeline is not dragged
_muxer->setTimeStamp(new_stamp);
}
}
bool MP4Reader::seekTo(MediaSource &sender, uint32_t stamp) {
// 拖动进度条后应该恢复播放 [AUTO-TRANSLATED:8a6d11f7]
// Playback should resume after dragging the progress bar
pause(sender, false);
TraceL << getOriginUrl(sender) << ",stamp:" << stamp;
return seekTo(stamp);
}
bool MP4Reader::pause(MediaSource &sender, bool pause) {
if (_paused == pause) {
return true;
}
// _seek_ticker重新计时不管是暂停还是seek都不影响总的播放进度 [AUTO-TRANSLATED:96051076]
// _seek_ticker restarts the timer, whether it is paused or seek does not affect the total playback progress
setCurrentStamp(getCurrentStamp());
_paused = pause;
TraceL << getOriginUrl(sender) << ",pause:" << pause;
return true;
}
bool MP4Reader::speed(MediaSource &sender, float speed) {
if (speed < 0.1 || speed > 20) {
WarnL << "播放速度取值范围非法:" << speed;
return false;
}
// _seek_ticker重置赋值_seek_to [AUTO-TRANSLATED:b30a3f06]
// _seek_ticker reset, assign _seek_to
setCurrentStamp(getCurrentStamp());
// 设置播放速度后应该恢复播放 [AUTO-TRANSLATED:851fcde9]
// Playback should resume after setting the playback speed
_paused = false;
if (_speed == speed) {
return true;
}
_speed = speed;
TraceL << getOriginUrl(sender) << ",speed:" << speed;
return true;
}
bool MP4Reader::seekTo(uint32_t stamp_seek) {
lock_guard<recursive_mutex> lck(_mtx);
if (stamp_seek > _demuxer->getDurationMS()) {
// 超过文件长度 [AUTO-TRANSLATED:b4361054]
// Exceeds the file length
return false;
}
auto stamp = _demuxer->seekTo(stamp_seek);
if (stamp == -1) {
// seek失败 [AUTO-TRANSLATED:88cc8444]
// Seek failed
return false;
}
if (!_have_video) {
// 没有视频,不需要搜索关键帧;设置当前时间戳 [AUTO-TRANSLATED:82f87f21]
// There is no video, no need to search for keyframes; set the current timestamp
setCurrentStamp((uint32_t) stamp);
return true;
}
// 搜索到下一帧关键帧 [AUTO-TRANSLATED:aa2ec689]
// Search for the next keyframe
bool keyFrame = false;
bool eof = false;
while (!eof) {
auto frame = _demuxer->readFrame(keyFrame, eof);
if (!frame) {
// 文件读完了都未找到下一帧关键帧 [AUTO-TRANSLATED:49a8d3a7]
// The file has been read but the next keyframe has not been found
continue;
}
if (keyFrame || frame->keyFrame() || frame->configFrame()) {
// 定位到key帧 [AUTO-TRANSLATED:0300901d]
// Locate to the keyframe
if (_muxer) {
_muxer->inputFrame(frame);
}
// 设置当前时间戳 [AUTO-TRANSLATED:88949974]
// Set the current timestamp
setCurrentStamp(frame->dts());
return true;
}
}
return false;
}
bool MP4Reader::close(MediaSource &sender) {
_timer = nullptr;
WarnL << "close media: " << sender.getUrl();
return true;
}
MediaOriginType MP4Reader::getOriginType(MediaSource &sender) const {
return MediaOriginType::mp4_vod;
}
string MP4Reader::getOriginUrl(MediaSource &sender) const {
return _file_path;
}
toolkit::EventPoller::Ptr MP4Reader::getOwnerPoller(MediaSource &sender) {
return _poller;
}
} /* namespace mediakit */
#endif //ENABLE_MP4

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_MEDIAFILE_MEDIAREADER_H_
#define SRC_MEDIAFILE_MEDIAREADER_H_
#ifdef ENABLE_MP4
#include "MP4Demuxer.h"
#include "Common/MultiMediaSourceMuxer.h"
namespace mediakit {
class MP4Reader : public std::enable_shared_from_this<MP4Reader>, public MediaSourceEvent {
public:
using Ptr = std::shared_ptr<MP4Reader>;
/**
* mp4文件使MediaSource流媒体
* @param vhost
* @param app
* @param stream_id id,,mp4,MediaSource
* @param file_path 使
* Play an mp4 file and convert it to a MediaSource stream
* @param vhost Virtual host
* @param app Application name
* @param stream_id Stream id, if empty, only demultiplex mp4, but not generate MediaSource
* @param file_path File path, if empty, it will be automatically generated according to the configuration file and the above parameters, otherwise use the specified file
* [AUTO-TRANSLATED:2faeb5db]
*/
MP4Reader(const MediaTuple &tuple, const std::string &file_path = "", toolkit::EventPoller::Ptr poller = nullptr);
MP4Reader(const MediaTuple &tuple, const std::string &file_path, const ProtocolOption &option, toolkit::EventPoller::Ptr poller = nullptr);
/**
* MP4文件
* @param sample_ms 0
* @param ref_self
* @param file_repeat
* Start demultiplexing the MP4 file
* @param sample_ms The amount of file data read each time, in milliseconds, set to 0 to use the configuration file configuration
* @param ref_self Whether to let the timer reference this object itself, if there is no other object referencing itself, when not looping to read the file, after reading the file, this object will be automatically destroyed
* @param file_repeat Whether to loop to read the file, if the configuration file is set to loop to read the file, this parameter is invalid
* [AUTO-TRANSLATED:2164a99d]
*/
void startReadMP4(uint64_t sample_ms = 0, bool ref_self = true, bool file_repeat = false);
/**
* MP4定时器
* Stop demultiplexing the MP4 timer
* [AUTO-TRANSLATED:45fb1ef7]
*/
void stopReadMP4();
/**
* mp4解复用器
* Get the mp4 demultiplexer
* [AUTO-TRANSLATED:4f0dfc29]
*/
const MP4Demuxer::Ptr& getDemuxer() const;
private:
//MediaSourceEvent override
bool seekTo(MediaSource &sender,uint32_t stamp) override;
bool pause(MediaSource &sender, bool pause) override;
bool speed(MediaSource &sender, float speed) override;
bool close(MediaSource &sender) override;
MediaOriginType getOriginType(MediaSource &sender) const override;
std::string getOriginUrl(MediaSource &sender) const override;
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
bool readSample();
bool readNextSample();
uint32_t getCurrentStamp();
void setCurrentStamp(uint32_t stamp);
bool seekTo(uint32_t stamp_seek);
void setup(const MediaTuple &tuple, const std::string &file_path, const ProtocolOption &option, toolkit::EventPoller::Ptr poller);
private:
bool _file_repeat = false;
bool _have_video = false;
bool _paused = false;
float _speed = 1.0;
uint32_t _last_dts = 0;
uint32_t _seek_to = 0;
std::string _file_path;
std::recursive_mutex _mtx;
toolkit::Ticker _seek_ticker;
toolkit::Timer::Ptr _timer;
MP4Demuxer::Ptr _demuxer;
MultiMediaSourceMuxer::Ptr _muxer;
toolkit::EventPoller::Ptr _poller;
};
} /* namespace mediakit */
#endif //ENABLE_MP4
#endif /* SRC_MEDIAFILE_MEDIAREADER_H_ */

View File

@ -0,0 +1,175 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifdef ENABLE_MP4
#include <ctime>
#include <sys/stat.h>
#include "Util/File.h"
#include "Common/config.h"
#include "MP4Recorder.h"
#include "Thread/WorkThreadPool.h"
#include "MP4Muxer.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
MP4Recorder::MP4Recorder(const MediaTuple &tuple, const string &path, size_t max_second) {
_folder_path = path;
// ///record 业务逻辑////// [AUTO-TRANSLATED:2e78931a]
// ///record Business Logic//////
static_cast<MediaTuple &>(_info) = tuple;
_info.folder = path;
GET_CONFIG(uint32_t, s_max_second, Protocol::kMP4MaxSecond);
_max_second = max_second ? max_second : s_max_second;
}
MP4Recorder::~MP4Recorder() {
try {
flush();
closeFile();
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
void MP4Recorder::createFile() {
closeFile();
auto date = getTimeStr("%Y-%m-%d");
auto file_name = getTimeStr("%H-%M-%S") + "-" + std::to_string(_file_index++) + ".mp4";
auto full_path = _folder_path + date + "/" + file_name;
auto full_path_tmp = _folder_path + date + "/." + file_name;
// ///record 业务逻辑////// [AUTO-TRANSLATED:2e78931a]
// ///record Business Logic//////
_info.start_time = ::time(NULL);
_info.file_name = file_name;
_info.file_path = full_path;
GET_CONFIG(string, appName, Record::kAppName);
_info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + file_name;
try {
_muxer = std::make_shared<MP4Muxer>();
TraceL << "Open tmp mp4 file: " << full_path_tmp;
_muxer->openMP4(full_path_tmp);
for (auto &track :_tracks) {
// 添加track [AUTO-TRANSLATED:80ae762a]
// Add track
_muxer->addTrack(track);
}
_full_path_tmp = full_path_tmp;
_full_path = full_path;
} catch (std::exception &ex) {
WarnL << ex.what();
}
}
void MP4Recorder::asyncClose() {
auto muxer = _muxer;
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可能非常耗时所以要放在后台线程执行 [AUTO-TRANSLATED:a7378a11]
// Closing mp4 can be very time-consuming, so it should be executed in the background thread
TraceL << "Closing tmp mp4 file: " << full_path_tmp;
muxer->closeMP4();
TraceL << "Closed tmp mp4 file: " << full_path_tmp;
if (!full_path_tmp.empty()) {
// 获取文件大小 [AUTO-TRANSLATED:7b90eb41]
// Get file size
info.file_size = File::fileSize(full_path_tmp);
if (info.file_size < 1024) {
// 录像文件太小,删除之 [AUTO-TRANSLATED:923d27c3]
// The recording file is too small, delete it
File::delete_file(full_path_tmp);
return;
}
// 临时文件名改成正式文件名防止mp4未完成时被访问 [AUTO-TRANSLATED:541a6f00]
// Change the temporary file name to the official file name to prevent access to the mp4 before it is completed
rename(full_path_tmp.data(), full_path.data());
}
TraceL << "Emit mp4 record event: " << full_path;
// 触发mp4录制切片生成事件 [AUTO-TRANSLATED:9959dcd4]
// Trigger mp4 recording slice generation event
NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info);
});
}
void MP4Recorder::closeFile() {
if (_muxer) {
asyncClose();
_muxer = nullptr;
}
}
void MP4Recorder::flush() {
if (_muxer) {
_muxer->flush();
}
}
bool MP4Recorder::inputFrame(const Frame::Ptr &frame) {
if (!(_have_video && frame->getTrackType() == TrackAudio)) {
// 如果有视频且输入的是音频,那么应该忽略切片逻辑 [AUTO-TRANSLATED:fbb15d93]
// If there is video and the input is audio, then the slice logic should be ignored
if (_last_dts == 0 || _last_dts > frame->dts()) {
// b帧情况下dts时间戳可能回退 [AUTO-TRANSLATED:1de38f77]
// In the case of b-frames, the dts timestamp may regress
_last_dts = MAX(frame->dts(), _last_dts);
}
auto duration = 5u; // 默认至少一帧5ms
if (frame->dts() > 0 && frame->dts() > _last_dts) {
duration = MAX(duration, frame->dts() - _last_dts);
}
if (!_muxer || ((duration > _max_second * 1000) && (!_have_video || (_have_video && frame->keyFrame())))) {
// 成立条件 [AUTO-TRANSLATED:8c9c6083]
// Conditions for establishment
// 1、_muxer为空 [AUTO-TRANSLATED:fa236097]
// 1. _muxer is empty
// 2、到了切片时间并且只有音频 [AUTO-TRANSLATED:212e9d23]
// 2. It's time to slice, and there is only audio
// 3、到了切片时间有视频并且遇到视频的关键帧 [AUTO-TRANSLATED:fa4a71ad]
// 3. It's time to slice, there is video and a video keyframe is encountered
_last_dts = 0;
createFile();
}
}
if (_muxer) {
// 生成mp4文件 [AUTO-TRANSLATED:76a8d77c]
// Generate mp4 file
return _muxer->inputFrame(frame);
}
return false;
}
bool MP4Recorder::addTrack(const Track::Ptr &track) {
// 保存所有的track为创建MP4MuxerFile做准备 [AUTO-TRANSLATED:815c2486]
// Save all tracks in preparation for creating MP4MuxerFile
_tracks.emplace_back(track);
if (track->getTrackType() == TrackVideo) {
_have_video = true;
}
return true;
}
void MP4Recorder::resetTracks() {
closeFile();
_tracks.clear();
_have_video = false;
}
} /* namespace mediakit */
#endif //ENABLE_MP4

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MP4MAKER_H_
#define MP4MAKER_H_
#include <mutex>
#include <memory>
#include "Common/MediaSink.h"
#include "Record/Recorder.h"
#include "MP4Muxer.h"
namespace mediakit {
#ifdef ENABLE_MP4
class MP4Muxer;
class MP4Recorder final : public MediaSinkInterface {
public:
using Ptr = std::shared_ptr<MP4Recorder>;
MP4Recorder(const MediaTuple &tuple, const std::string &path, size_t max_second);
~MP4Recorder() override;
/**
* Track
* Reset all Tracks
* [AUTO-TRANSLATED:8dd80826]
*/
void resetTracks() override;
/**
* frame
* Input frame
* [AUTO-TRANSLATED:3722ea0e]
*/
bool inputFrame(const Frame::Ptr &frame) override;
/**
* frame缓存
* Refresh output all frame cache
* [AUTO-TRANSLATED:adaea568]
*/
void flush() override;
/**
* ready状态的track
* Add ready state track
* [AUTO-TRANSLATED:2d8138b3]
*/
bool addTrack(const Track::Ptr & track) override;
private:
void createFile();
void closeFile();
void asyncClose();
private:
bool _have_video = false;
size_t _max_second;
uint64_t _last_dts = 0;
uint64_t _file_index = 0;
std::string _folder_path;
std::string _full_path;
std::string _full_path_tmp;
RecordInfo _info;
MP4Muxer::Ptr _muxer;
std::list<Track::Ptr> _tracks;
};
#endif ///ENABLE_MP4
} /* namespace mediakit */
#endif /* MP4MAKER_H_ */

161
MediaServer/Record/MPEG.cpp Normal file
View File

@ -0,0 +1,161 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <assert.h>
#include "MPEG.h"
#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY)
#include "mpeg-ts.h"
#include "mpeg-muxer.h"
using namespace toolkit;
namespace mediakit {
MpegMuxer::MpegMuxer(bool is_ps) {
_is_ps = is_ps;
createContext();
_buffer_pool.setSize(64);
}
MpegMuxer::~MpegMuxer() {
releaseContext();
}
bool MpegMuxer::addTrack(const Track::Ptr &track) {
auto mpeg_id = getMpegIdByCodec(track->getCodecId());
if (mpeg_id == PSI_STREAM_RESERVED) {
WarnL << "Unsupported codec: " << track->getCodecName();
return false;
}
if (track->getTrackType() == TrackVideo) {
_have_video = true;
}
_tracks[track->getIndex()].track_id = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0);
return true;
}
bool MpegMuxer::inputFrame(const Frame::Ptr &frame) {
auto it = _tracks.find(frame->getIndex());
if (it == _tracks.end()) {
return false;
}
auto &track = it->second;
_key_pos = !_have_video;
switch (frame->getCodecId()) {
case CodecH264:
case CodecH265: {
// 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理 [AUTO-TRANSLATED:edf57c32]
// The code logic here is to package frames with the same timestamp, such as SPS, PPS, and IDR, together as one frame.
return track.merger.inputFrame(frame, [this, &track](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) {
_key_pos = have_idr;
// 取视频时间戳为TS的时间戳 [AUTO-TRANSLATED:5ff7796d]
// Take the video timestamp as the TS timestamp.
_timestamp = dts;
_max_cache_size = 512 + 1.2 * buffer->size();
mpeg_muxer_input((::mpeg_muxer_t *)_context, track.track_id, have_idr ? 0x0001 : 0, pts * 90LL, dts * 90LL, buffer->data(), buffer->size());
flushCache();
});
}
case CodecAAC: {
CHECK(frame->prefixSize(), "Mpeg muxer required aac frame with adts heade");
}
default: {
if (!_have_video) {
// 没有视频时才以音频时间戳为TS的时间戳 [AUTO-TRANSLATED:17cef4f7]
// When there is no video, use the audio timestamp as the TS timestamp.
_timestamp = frame->dts();
}
if (frame->getTrackType() == TrackType::TrackVideo) {
_key_pos = frame->keyFrame();
_timestamp = frame->dts();
}
_max_cache_size = 512 + 1.2 * frame->size();
mpeg_muxer_input((::mpeg_muxer_t *)_context, track.track_id, frame->keyFrame() ? 0x0001 : 0, frame->pts() * 90LL, frame->dts() * 90LL, frame->data(), frame->size());
flushCache();
return true;
}
}
}
void MpegMuxer::resetTracks() {
_have_video = false;
// 通知片段中断 [AUTO-TRANSLATED:ed3d87ba]
// Notify fragment interruption.
onWrite(nullptr, _timestamp, false);
releaseContext();
createContext();
}
void MpegMuxer::createContext() {
static mpeg_muxer_func_t func = {
/*alloc*/
[](void *param, size_t bytes) {
MpegMuxer *thiz = (MpegMuxer *)param;
if (!thiz->_current_buffer
|| thiz->_current_buffer->size() + bytes > thiz->_current_buffer->getCapacity()) {
if (thiz->_current_buffer) {
thiz->flushCache();
}
thiz->_current_buffer = thiz->_buffer_pool.obtain2();
thiz->_current_buffer->setSize(0);
thiz->_current_buffer->setCapacity(MAX(thiz->_max_cache_size, bytes));
}
return (void *)(thiz->_current_buffer->data() + thiz->_current_buffer->size());
},
/*free*/
[](void *param, void *packet) {
// 什么也不做 [AUTO-TRANSLATED:e2f8de75]
// Do nothing.
},
/*wtite*/
[](void *param, int stream, void *packet, size_t bytes) {
MpegMuxer *thiz = (MpegMuxer *) param;
thiz->onWrite_l(packet, bytes);
return 0;
}
};
if (_context == nullptr) {
_context = (struct mpeg_muxer_t *)mpeg_muxer_create(_is_ps, &func, this);
}
}
void MpegMuxer::onWrite_l(const void *packet, size_t bytes) {
assert(_current_buffer && _current_buffer->data() + _current_buffer->size() == packet);
_current_buffer->setSize(_current_buffer->size() + bytes);
}
void MpegMuxer::flushCache() {
onWrite(std::move(_current_buffer), _timestamp, _key_pos);
_key_pos = false;
}
void MpegMuxer::releaseContext() {
if (_context) {
mpeg_muxer_destroy((::mpeg_muxer_t *)_context);
_context = nullptr;
}
_tracks.clear();
}
void MpegMuxer::flush() {
for (auto &pr : _tracks) {
pr.second.merger.flush();
}
}
}//mediakit
#endif

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