add ZLMediaKit code for learning.
This commit is contained in:
parent
7a87c76543
commit
9de3af15eb
@ -13,6 +13,8 @@ FetchContent_Declare(Kylin
|
|||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(Kylin)
|
FetchContent_MakeAvailable(Kylin)
|
||||||
|
|
||||||
|
add_subdirectory(MediaServer)
|
||||||
|
add_subdirectory(ToolKit)
|
||||||
add_subdirectory(Server)
|
add_subdirectory(Server)
|
||||||
add_subdirectory(ThirdParty)
|
add_subdirectory(ThirdParty)
|
||||||
add_subdirectory(UnitTest)
|
add_subdirectory(UnitTest)
|
143
MediaServer/CMakeLists.txt
Normal file
143
MediaServer/CMakeLists.txt
Normal 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
|
||||||
|
)
|
424
MediaServer/Common/MediaSink.cpp
Normal file
424
MediaServer/Common/MediaSink.cpp
Normal 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
|
300
MediaServer/Common/MediaSink.h
Normal file
300
MediaServer/Common/MediaSink.h
Normal 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加track,内部会调用Track的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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加track,内部会调用Track的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完毕,如果是单Track,会最多等待3秒才会触发onAllTrackReady
|
||||||
|
* 这样会增加生成流的延时,如果添加了音视频双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数,取值范围>=1;该方法与addTrackCompleted类型;
|
||||||
|
* 在设置单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输出frame,在onAllTrackReady触发后才会调用此方法
|
||||||
|
* @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
|
927
MediaServer/Common/MediaSource.cpp
Normal file
927
MediaServer/Common/MediaSource.cpp
Normal 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 */
|
580
MediaServer/Common/MediaSource.h
Normal file
580
MediaServer/Common/MediaSource.h
Normal 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
|
717
MediaServer/Common/MultiMediaSourceMuxer.cpp
Normal file
717
MediaServer/Common/MultiMediaSourceMuxer.cpp
Normal 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
|
264
MediaServer/Common/MultiMediaSourceMuxer.h
Normal file
264
MediaServer/Common/MultiMediaSourceMuxer.h
Normal 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输出frame,在onAllTrackReady触发后才会调用此方法
|
||||||
|
* @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
|
101
MediaServer/Common/PacketCache.h
Normal file
101
MediaServer/Common/PacketCache.h
Normal 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_
|
358
MediaServer/Common/Parser.cpp
Normal file
358
MediaServer/Common/Parser.cpp
Normal 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
159
MediaServer/Common/Parser.h
Normal 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 ¶ms() 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
|
401
MediaServer/Common/Stamp.cpp
Normal file
401
MediaServer/Common/Stamp.cpp
Normal 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
189
MediaServer/Common/Stamp.h
Normal 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 输入dts,如果为0则根据系统时间戳生成
|
||||||
|
* @param pts 输入pts,如果为0则等于dts
|
||||||
|
* @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
|
675
MediaServer/Common/config.cpp
Normal file
675
MediaServer/Common/config.cpp
Normal 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
630
MediaServer/Common/config.h
Normal 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 {
|
||||||
|
// 每个流媒体服务器的ID(GUID) [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 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏 [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-2016,c.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 */
|
38
MediaServer/Common/macros.cpp
Normal file
38
MediaServer/Common/macros.cpp
Normal 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
|
89
MediaServer/Common/macros.h
Normal file
89
MediaServer/Common/macros.h
Normal 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
|
246
MediaServer/Common/strCoding.cpp
Normal file
246
MediaServer/Common/strCoding.cpp
Normal 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 */
|
38
MediaServer/Common/strCoding.h
Normal file
38
MediaServer/Common/strCoding.h
Normal 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_ */
|
43
MediaServer/Extension/CommonRtmp.cpp
Normal file
43
MediaServer/Extension/CommonRtmp.cpp
Normal 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
|
74
MediaServer/Extension/CommonRtmp.h
Normal file
74
MediaServer/Extension/CommonRtmp.h
Normal 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
|
96
MediaServer/Extension/CommonRtp.cpp
Normal file
96
MediaServer/Extension/CommonRtp.cpp
Normal 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;
|
||||||
|
}
|
86
MediaServer/Extension/CommonRtp.h
Normal file
86
MediaServer/Extension/CommonRtp.h
Normal 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
|
218
MediaServer/Extension/Factory.cpp
Normal file
218
MediaServer/Extension/Factory.cpp
Normal 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
|
||||||
|
|
166
MediaServer/Extension/Factory.h
Normal file
166
MediaServer/Extension/Factory.h
Normal 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
|
349
MediaServer/Extension/Frame.cpp
Normal file
349
MediaServer/Extension/Frame.cpp
Normal 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
|
820
MediaServer/Extension/Frame.h
Normal file
820
MediaServer/Extension/Frame.h
Normal 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(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前缀长度,譬如264前缀为0x00 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
|
369
MediaServer/Extension/Track.h
Normal file
369
MediaServer/Extension/Track.h
Normal 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;};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回音频采样位数,一般为16或8
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回音频采样位数,一般为16或8
|
||||||
|
* 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
|
175
MediaServer/FMP4/FMP4MediaSource.h
Normal file
175
MediaServer/FMP4/FMP4MediaSource.h
Normal 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
|
95
MediaServer/FMP4/FMP4MediaSourceMuxer.h
Normal file
95
MediaServer/FMP4/FMP4MediaSourceMuxer.h
Normal 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
|
139
MediaServer/Http/HlsParser.cpp
Normal file
139
MediaServer/Http/HlsParser.cpp
Normal 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
|
143
MediaServer/Http/HlsParser.h
Normal file
143
MediaServer/Http/HlsParser.h
Normal 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
|
530
MediaServer/Http/HlsPlayer.cpp
Normal file
530
MediaServer/Http/HlsPlayer.cpp
Normal 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
|
158
MediaServer/Http/HlsPlayer.h
Normal file
158
MediaServer/Http/HlsPlayer.h
Normal 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
|
396
MediaServer/Http/HttpBody.cpp
Normal file
396
MediaServer/Http/HttpBody.cpp
Normal 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
220
MediaServer/Http/HttpBody.h
Normal 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
|
41
MediaServer/Http/HttpChunkedSplitter.cpp
Normal file
41
MediaServer/Http/HttpChunkedSplitter.cpp
Normal 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
|
50
MediaServer/Http/HttpChunkedSplitter.h
Normal file
50
MediaServer/Http/HttpChunkedSplitter.h
Normal 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
|
493
MediaServer/Http/HttpClient.cpp
Normal file
493
MediaServer/Http/HttpClient.cpp
Normal 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 */
|
321
MediaServer/Http/HttpClient.h
Normal file
321
MediaServer/Http/HttpClient.h
Normal 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
|
||||||
|
* 该值设置不为0后,HeaderTimeout和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 */
|
46
MediaServer/Http/HttpClientImp.cpp
Normal file
46
MediaServer/Http/HttpClientImp.cpp
Normal 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 */
|
29
MediaServer/Http/HttpClientImp.h
Normal file
29
MediaServer/Http/HttpClientImp.h
Normal 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_ */
|
219
MediaServer/Http/HttpConst.cpp
Normal file
219
MediaServer/Http/HttpConst.cpp
Normal 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
|
51
MediaServer/Http/HttpConst.h
Normal file
51
MediaServer/Http/HttpConst.h
Normal 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
|
134
MediaServer/Http/HttpCookie.cpp
Normal file
134
MediaServer/Http/HttpCookie.cpp
Normal 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 */
|
75
MediaServer/Http/HttpCookie.h
Normal file
75
MediaServer/Http/HttpCookie.h
Normal 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
|
320
MediaServer/Http/HttpCookieManager.cpp
Normal file
320
MediaServer/Http/HttpCookieManager.cpp
Normal 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
|
369
MediaServer/Http/HttpCookieManager.h
Normal file
369
MediaServer/Http/HttpCookieManager.h
Normal 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
|
77
MediaServer/Http/HttpDownloader.cpp
Normal file
77
MediaServer/Http/HttpDownloader.cpp
Normal 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 */
|
63
MediaServer/Http/HttpDownloader.h
Normal file
63
MediaServer/Http/HttpDownloader.h
Normal 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_ */
|
847
MediaServer/Http/HttpFileManager.cpp
Normal file
847
MediaServer/Http/HttpFileManager.cpp
Normal 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客户端是否有权限访问文件的逻辑步骤
|
||||||
|
* 1、根据http请求头查找cookie,找到进入步骤3
|
||||||
|
* 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5
|
||||||
|
* 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
|
||||||
|
* 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
|
||||||
|
* 5、触发kBroadcastHttpAccess事件
|
||||||
|
* 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
|
97
MediaServer/Http/HttpFileManager.h
Normal file
97
MediaServer/Http/HttpFileManager.h
Normal 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
|
181
MediaServer/Http/HttpRequestSplitter.cpp
Normal file
181
MediaServer/Http/HttpRequestSplitter.cpp
Normal 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 */
|
||||||
|
|
139
MediaServer/Http/HttpRequestSplitter.h
Normal file
139
MediaServer/Http/HttpRequestSplitter.h
Normal 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 : 代表后面所有数据都是content,此时后面的content将分段通过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
|
291
MediaServer/Http/HttpRequester.cpp
Normal file
291
MediaServer/Http/HttpRequester.cpp
Normal 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
|
46
MediaServer/Http/HttpRequester.h
Normal file
46
MediaServer/Http/HttpRequester.h
Normal 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 */
|
891
MediaServer/Http/HttpSession.cpp
Normal file
891
MediaServer/Http/HttpSession.cpp
Normal 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;
|
||||||
|
};
|
||||||
|
// 声明后续都是body;Http 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 */
|
186
MediaServer/Http/HttpSession.h
Normal file
186
MediaServer/Http/HttpSession.h
Normal 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总大小,如果为0则是不限长度content
|
||||||
|
* @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_ */
|
60
MediaServer/Http/HttpTSPlayer.cpp
Normal file
60
MediaServer/Http/HttpTSPlayer.cpp
Normal 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
|
61
MediaServer/Http/HttpTSPlayer.h
Normal file
61
MediaServer/Http/HttpTSPlayer.h
Normal 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
|
60
MediaServer/Http/TsPlayer.cpp
Normal file
60
MediaServer/Http/TsPlayer.cpp
Normal 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
|
50
MediaServer/Http/TsPlayer.h
Normal file
50
MediaServer/Http/TsPlayer.h
Normal 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
|
46
MediaServer/Http/TsPlayerImp.h
Normal file
46
MediaServer/Http/TsPlayerImp.h
Normal 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
|
80
MediaServer/Http/TsplayerImp.cpp
Normal file
80
MediaServer/Http/TsplayerImp.cpp
Normal 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
|
534
MediaServer/Http/WebSocketClient.h
Normal file
534
MediaServer/Http/WebSocketClient.h
Normal 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
|
304
MediaServer/Http/WebSocketSession.h
Normal file
304
MediaServer/Http/WebSocketSession.h
Normal 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
|
208
MediaServer/Http/WebSocketSplitter.cpp
Normal file
208
MediaServer/Http/WebSocketSplitter.cpp
Normal 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 */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
181
MediaServer/Http/WebSocketSplitter.h
Normal file
181
MediaServer/Http/WebSocketSplitter.h
Normal 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码一个数据包
|
||||||
|
* 将触发2次onWebSocketEncodeData回调
|
||||||
|
* @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
|
32
MediaServer/MediaServer.cpp
Normal file
32
MediaServer/MediaServer.cpp
Normal 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
17
MediaServer/MediaServer.h
Normal 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__
|
61
MediaServer/Player/MediaPlayer.cpp
Normal file
61
MediaServer/Player/MediaPlayer.cpp
Normal 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 */
|
37
MediaServer/Player/MediaPlayer.h
Normal file
37
MediaServer/Player/MediaPlayer.h
Normal 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_ */
|
83
MediaServer/Player/PlayerBase.cpp
Normal file
83
MediaServer/Player/PlayerBase.cpp
Normal 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 */
|
290
MediaServer/Player/PlayerBase.h
Normal file
290
MediaServer/Player/PlayerBase.h
Normal 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 视频url,支持rtsp/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>(); };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一个MediaSource,直接生产rtsp/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_ */
|
374
MediaServer/Player/PlayerProxy.cpp
Normal file
374
MediaServer/Player/PlayerProxy.cpp
Normal 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 */
|
182
MediaServer/Player/PlayerProxy.h
Normal file
182
MediaServer/Player/PlayerProxy.h
Normal 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_ */
|
60
MediaServer/Pusher/MediaPusher.cpp
Normal file
60
MediaServer/Pusher/MediaPusher.cpp
Normal 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 */
|
45
MediaServer/Pusher/MediaPusher.h
Normal file
45
MediaServer/Pusher/MediaPusher.h
Normal 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_ */
|
61
MediaServer/Pusher/PusherBase.cpp
Normal file
61
MediaServer/Pusher/PusherBase.cpp
Normal 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 */
|
158
MediaServer/Pusher/PusherBase.h
Normal file
158
MediaServer/Pusher/PusherBase.h
Normal 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 视频url,支持rtsp/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 推流url,支持rtsp/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_ */
|
140
MediaServer/Pusher/PusherProxy.cpp
Normal file
140
MediaServer/Pusher/PusherProxy.cpp
Normal 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 */
|
87
MediaServer/Pusher/PusherProxy.h
Normal file
87
MediaServer/Pusher/PusherProxy.h
Normal 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
|
214
MediaServer/Record/HlsMaker.cpp
Normal file
214
MediaServer/Record/HlsMaker.cpp
Normal 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;
|
||||||
|
// 最小允许设置为0,0个切片代表点播 [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
|
215
MediaServer/Record/HlsMaker.h
Normal file
215
MediaServer/Record/HlsMaker.h
Normal 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
|
212
MediaServer/Record/HlsMakerImp.cpp
Normal file
212
MediaServer/Record/HlsMakerImp.cpp
Normal 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 ¶ms, 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
|
83
MediaServer/Record/HlsMakerImp.h
Normal file
83
MediaServer/Record/HlsMakerImp.h
Normal 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 ¶ms, 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
|
117
MediaServer/Record/HlsMediaSource.cpp
Normal file
117
MediaServer/Record/HlsMediaSource.cpp
Normal 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
|
113
MediaServer/Record/HlsMediaSource.h
Normal file
113
MediaServer/Record/HlsMediaSource.h
Normal 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
|
142
MediaServer/Record/HlsRecorder.h
Normal file
142
MediaServer/Record/HlsRecorder.h
Normal 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 ¶ms, 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
187
MediaServer/Record/MP4.cpp
Normal 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
182
MediaServer/Record/MP4.h
Normal 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 支持0、MOV_FLAG_FASTSTART、MOV_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
|
193
MediaServer/Record/MP4Demuxer.cpp
Normal file
193
MediaServer/Record/MP4Demuxer.cpp
Normal 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
|
109
MediaServer/Record/MP4Demuxer.h
Normal file
109
MediaServer/Record/MP4Demuxer.h
Normal 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
|
260
MediaServer/Record/MP4Muxer.cpp
Normal file
260
MediaServer/Record/MP4Muxer.cpp
Normal 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)
|
239
MediaServer/Record/MP4Muxer.h
Normal file
239
MediaServer/Record/MP4Muxer.h
Normal 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
|
291
MediaServer/Record/MP4Reader.cpp
Normal file
291
MediaServer/Record/MP4Reader.cpp
Normal 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
|
110
MediaServer/Record/MP4Reader.h
Normal file
110
MediaServer/Record/MP4Reader.h
Normal 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_ */
|
175
MediaServer/Record/MP4Recorder.cpp
Normal file
175
MediaServer/Record/MP4Recorder.cpp
Normal 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
|
87
MediaServer/Record/MP4Recorder.h
Normal file
87
MediaServer/Record/MP4Recorder.h
Normal 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
161
MediaServer/Record/MPEG.cpp
Normal 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
Loading…
Reference in New Issue
Block a user