diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d264e9..973b7d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ FetchContent_Declare(Kylin ) FetchContent_MakeAvailable(Kylin) +add_subdirectory(MediaServer) +add_subdirectory(ToolKit) add_subdirectory(Server) add_subdirectory(ThirdParty) add_subdirectory(UnitTest) \ No newline at end of file diff --git a/MediaServer/CMakeLists.txt b/MediaServer/CMakeLists.txt new file mode 100644 index 0000000..2909f4a --- /dev/null +++ b/MediaServer/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/MediaServer/Common/MediaSink.cpp b/MediaServer/Common/MediaSink.cpp new file mode 100644 index 0000000..cca80e2 --- /dev/null +++ b/MediaServer/Common/MediaSink.cpp @@ -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 MediaSink::getTracks(bool ready) const { + vector 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>(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(); + _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(); + 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 Demuxer::getTracks(bool ready) const { + if (_sink) { + return _sink->getTracks(ready); + } + + vector ret; + for (auto &track : _origin_track) { + if (ready && !track->ready()) { + continue; + } + ret.emplace_back(track); + } + return ret; +} +} // namespace mediakit diff --git a/MediaServer/Common/MediaSink.h b/MediaServer/Common/MediaSink.h new file mode 100644 index 0000000..da517ff --- /dev/null +++ b/MediaServer/Common/MediaSink.h @@ -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 +#include +#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; +}; + +/** + * aac静音音频添加器 + * AAC mute audio adder + + * [AUTO-TRANSLATED:aa154f71] + */ +class MuteAudioMaker : public FrameDispatcher { +public: + using Ptr = std::shared_ptr; + 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; + /** + * 输入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 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 > _frame_unread; + std::unordered_map > _track_ready_callback; + std::unordered_map > _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 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 _origin_track; +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_MEDIASINK_H diff --git a/MediaServer/Common/MediaSource.cpp b/MediaServer/Common/MediaSource.cpp new file mode 100644 index 0000000..14c18d1 --- /dev/null +++ b/MediaServer/Common/MediaSource.cpp @@ -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 +#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 >; +using AppStreamMap = unordered_map; +using VhostAppStreamMap = unordered_map; +using SchemaVhostAppStreamMap = unordered_map; +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 s_null = std::make_shared(); + 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 MediaSource::getOwnership() { + if (_owned.test_and_set()) { + // 已经被所有 [AUTO-TRANSLATED:bab937dc] + // Already owned by all + return nullptr; + } + weak_ptr 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 *) 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 MediaSource::getTracks(bool ready) const { + auto listener = _listener.lock(); + if(!listener){ + return vector(); + } + return listener->getMediaTracks(const_cast(*this), ready); +} + +void MediaSource::setListener(const std::weak_ptr &listener){ + _listener = listener; +} + +std::weak_ptr 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(*this)); +} + +string MediaSource::getOriginUrl() const { + auto listener = _listener.lock(); + if (!listener) { + return getUrl(); + } + auto ret = listener->getOriginUrl(const_cast(*this)); + if (!ret.empty()) { + return ret; + } + return getUrl(); +} + +std::shared_ptr MediaSource::getOriginSock() const { + auto listener = _listener.lock(); + if (!listener) { + return nullptr; + } + return listener->getOriginSock(const_cast(*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 MediaSource::getMuxer() const { + auto listener = _listener.lock(); + return listener ? listener->getMuxer(const_cast(*this)) : nullptr; +} + +std::shared_ptr MediaSource::getRtpProcess() const { + auto listener = _listener.lock(); + return listener ? listener->getRtpProcess(const_cast(*this)) : nullptr; +} + +void MediaSource::onReaderChanged(int size) { + try { + weak_ptr 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 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 +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 +static void emplace_back(LIST &list, const Ptr &ptr) { + auto src = ptr.lock(); + if (src) { + list.emplace_back(std::move(src)); + } +} + +template +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 &cb, + const string &schema, + const string &vhost, + const string &app, + const string &stream) { + deque src_list; + { + lock_guard 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(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, bool retry, + const function &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 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 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 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, const function &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 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 +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 +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 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(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 weak_sender = sender.shared_from_this(); + + _async_close_timer = std::make_shared(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 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 MediaSourceEventInterceptor::getMuxer(MediaSource &sender) const { + auto listener = _listener.lock(); + if (!listener) { + return MediaSourceEvent::getMuxer(sender); + } + return listener->getMuxer(sender); +} + +std::shared_ptr 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 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 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 &listener) { + if (listener.lock().get() == this) { + throw std::invalid_argument("can not set self as a delegate"); + } + _listener = listener; +} + +std::shared_ptr 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 */ \ No newline at end of file diff --git a/MediaServer/Common/MediaSource.h b/MediaServer/Common/MediaSource.h new file mode 100644 index 0000000..abb5420 --- /dev/null +++ b/MediaServer/Common/MediaSource.h @@ -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 +#include +#include +#include +#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 + NotImplemented(T && ...args) : std::runtime_error(std::forward(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 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 getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector(); }; + // 获取MultiMediaSourceMuxer对象 [AUTO-TRANSLATED:2de96d44] + // Get MultiMediaSourceMuxer object + virtual std::shared_ptr getMuxer(MediaSource &sender) const { return nullptr; } + // 获取RtpProcess对象 [AUTO-TRANSLATED:c6b7da43] + // Get RtpProcess object + virtual std::shared_ptr 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 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 +static void getArgsValue(const MAP &allArgs, const KEY &key, TYPE &value) { + auto val = ((MAP &)allArgs)[key]; + if (!val.empty()) { + value = (TYPE)val; + } +} + +template +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 + ProtocolOption(const MAP &allArgs) : ProtocolOption() { + load(allArgs); + } + + template + 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 &listener); + std::shared_ptr getDelegate() const; + + MediaOriginType getOriginType(MediaSource &sender) const override; + std::string getOriginUrl(MediaSource &sender) const override; + std::shared_ptr 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 getMediaTracks(MediaSource &sender, bool trackReady = true) const override; + void startSendRtp(MediaSource &sender, const SendRtpArgs &args, const std::function 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 getMuxer(MediaSource &sender) const override; + std::shared_ptr getRtpProcess(MediaSource &sender) const override; + +private: + std::weak_ptr _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 { +public: + static MediaSource& NullMediaSource(); + using Ptr = std::shared_ptr; + + 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 getOwnership(); + + // 获取所有Track [AUTO-TRANSLATED:59f1c570] + // Get all Tracks + std::vector 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 &listener); + // 获取监听者 [AUTO-TRANSLATED:5c9cbb82] + // Get listener + std::weak_ptr 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 &info_list)> &cb, + const std::function &on_change) { + assert(cb); + cb(std::list()); + } + + 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 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 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 getMuxer() const; + // 获取RtpProcess对象 [AUTO-TRANSLATED:c6b7da43] + // Get the RtpProcess object + std::shared_ptr 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 &session, const std::function &cb); + // 遍历所有流 [AUTO-TRANSLATED:a39b2399] + // Traverse all streams + static void for_each_media(const std::function &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 _listener; + // 对象个数统计 [AUTO-TRANSLATED:f4a012d0] + // Object count statistics + toolkit::ObjectStatistic _statistic; +}; + +} /* namespace mediakit */ +#endif //ZLMEDIAKIT_MEDIASOURCE_H diff --git a/MediaServer/Common/MultiMediaSourceMuxer.cpp b/MediaServer/Common/MultiMediaSourceMuxer.cpp new file mode 100644 index 0000000..deed7c9 --- /dev/null +++ b/MediaServer/Common/MultiMediaSourceMuxer.cpp @@ -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 +#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 { +public: + using OnFrame = std::function; + // 最小缓存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 lck(_mtx); + std::weak_ptr weak_self = shared_from_this(); + _timer = std::make_shared(_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 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 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> _cache; +}; + +static std::shared_ptr makeRecorder(MediaSource &sender, const vector &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(track); + codec_info << "[" + << audio_track->getAudioSampleRate() << "/" + << audio_track->getAudioChannel() << "/" + << audio_track->getAudioSampleBit() << "] "; + break; + } + case TrackVideo : { + auto video_track = dynamic_pointer_cast(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 &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(_tuple, option, std::make_shared(dur_sec)); + } + if (option.enable_rtsp) { + _rtsp = std::make_shared(_tuple, option, std::make_shared(dur_sec)); + } + if (option.enable_hls) { + _hls = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls, _tuple, option)); + } + if (option.enable_hls_fmp4) { + _hls_fmp4 = dynamic_pointer_cast(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(Recorder::createRecorder(Recorder::type_ts, _tuple, option)); + } + if (option.enable_fmp4) { + _fmp4 = dynamic_pointer_cast(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 &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) { + _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(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(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(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(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 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(poller); + + weak_ptr 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 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::getMuxer(MediaSource &sender) const { + return const_cast(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 weak_self = shared_from_this(); + _paced_sender = std::make_shared(_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 weak_self = shared_from_this(); + auto src = std::make_shared(weak_self.lock()); + _ring = std::make_shared(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(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 diff --git a/MediaServer/Common/MultiMediaSourceMuxer.h b/MediaServer/Common/MultiMediaSourceMuxer.h new file mode 100644 index 0000000..0ec4de6 --- /dev/null +++ b/MediaServer/Common/MultiMediaSourceMuxer.h @@ -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{ +public: + using Ptr = std::shared_ptr; + using RingType = toolkit::RingBuffer; + + 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 &listener); + + /** + * 设置Track就绪事件监听器 + * @param listener 事件监听器 + * Set Track ready event listener + * @param listener Event listener + + * [AUTO-TRANSLATED:64262ac5] + */ + void setTrackListener(const std::weak_ptr &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 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 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 getMuxer(MediaSource &sender) const override; + + const ProtocolOption &getOption() const; + const MediaTuple &getMediaTuple() const; + std::string shortUrl() const; + + void forEachRtpSender(const std::function &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 _paced_sender; + MediaTuple _tuple; + ProtocolOption _option; + toolkit::Ticker _last_check; + std::unordered_map _stamps; + std::weak_ptr _track_listener; + std::unordered_multimap _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 _statistic; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H diff --git a/MediaServer/Common/PacketCache.h b/MediaServer/Common/PacketCache.h new file mode 100644 index 0000000..593f020 --- /dev/null +++ b/MediaServer/Common/PacketCache.h @@ -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 > > +class PacketCache { +public: + PacketCache() { _cache = std::make_shared(); } + + virtual ~PacketCache() = default; + + void inputPacket(uint64_t stamp, bool is_video, std::shared_ptr 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(); + _key_pos = false; + } + + virtual void clearCache() { + _cache->clear(); + } + + virtual void onFlush(std::shared_ptr, 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::value ? rtspLowLatency : (mergeWriteMS <= 0); + } + +private: + bool _key_pos = false; + policy _policy; + std::shared_ptr _cache; +}; +} + +#endif //ZLMEDIAKIT_PACKET_CACHE_H_ diff --git a/MediaServer/Common/Parser.cpp b/MediaServer/Common/Parser.cpp new file mode 100644 index 0000000..47f3c2d --- /dev/null +++ b/MediaServer/Common/Parser.cpp @@ -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 +#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(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 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 diff --git a/MediaServer/Common/Parser.h b/MediaServer/Common/Parser.h new file mode 100644 index 0000000..3bf2382 --- /dev/null +++ b/MediaServer/Common/Parser.h @@ -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 +#include +#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 { +public: + using Super = std::multimap; + + std::string &operator[](const std::string &k) { + auto it = find(k); + if (it == end()) { + it = Super::emplace(k, ""); + } + return it->second; + } + + template + void emplace(K &&k, V &&v) { + auto it = find(k); + if (it != end()) { + return; + } + Super::emplace(std::forward(k), std::forward(v)); + } + + template + void emplace_force(K &&k, V &&v) { + Super::emplace(std::forward(k), std::forward(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 diff --git a/MediaServer/Common/Stamp.cpp b/MediaServer/Common/Stamp.cpp new file mode 100644 index 0000000..0913b7f --- /dev/null +++ b/MediaServer/Common/Stamp.cpp @@ -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((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((_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 diff --git a/MediaServer/Common/Stamp.h b/MediaServer/Common/Stamp.h new file mode 100644 index 0000000..a21aacf --- /dev/null +++ b/MediaServer/Common/Stamp.h @@ -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 +#include +#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 _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 diff --git a/MediaServer/Common/config.cpp b/MediaServer/Common/config.cpp new file mode 100644 index 0000000..1968d5f --- /dev/null +++ b/MediaServer/Common/config.cpp @@ -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 +#include + +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 << "" + "404 Not Found" + "" + "

您访问的资源不存在!

" + "
" + << kServerName + << "
" + "" + "" + << 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 getBlockTypeSize() { + std::vector 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; + atomic mem_usage { 0 }; + atomic mem_block { 0 }; + atomic 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 +#include +#include +#include + +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 diff --git a/MediaServer/Common/config.h b/MediaServer/Common/config.h new file mode 100644 index 0000000..22a5d3b --- /dev/null +++ b/MediaServer/Common/config.h @@ -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 + +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; + +// 收到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; + +// 播放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 &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 */ diff --git a/MediaServer/Common/macros.cpp b/MediaServer/Common/macros.cpp new file mode 100644 index 0000000..e9574dc --- /dev/null +++ b/MediaServer/Common/macros.cpp @@ -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 diff --git a/MediaServer/Common/macros.h b/MediaServer/Common/macros.h new file mode 100644 index 0000000..e9f8782 --- /dev/null +++ b/MediaServer/Common/macros.h @@ -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 +#include +#include "Util/util.h" +#include "Util/logger.h" +#if defined(__MACH__) +#include +#include +#define __BYTE_ORDER BYTE_ORDER +#define __BIG_ENDIAN BIG_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#elif defined(__linux__) +#include +#include +#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 +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)...); + Assert_Throw(failed, exp, func, file, line, ss.str().data()); + } +} + +} // namespace mediakit +#endif // ZLMEDIAKIT_MACROS_H diff --git a/MediaServer/Common/strCoding.cpp b/MediaServer/Common/strCoding.cpp new file mode 100644 index 0000000..5eafe40 --- /dev/null +++ b/MediaServer/Common/strCoding.cpp @@ -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 +#include "strCoding.h" + +#if defined(_WIN32) +#include +#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 */ diff --git a/MediaServer/Common/strCoding.h b/MediaServer/Common/strCoding.h new file mode 100644 index 0000000..bfddf7f --- /dev/null +++ b/MediaServer/Common/strCoding.h @@ -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 +#include +#include +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_ */ diff --git a/MediaServer/Extension/CommonRtmp.cpp b/MediaServer/Extension/CommonRtmp.cpp new file mode 100644 index 0000000..46aebf4 --- /dev/null +++ b/MediaServer/Extension/CommonRtmp.cpp @@ -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 \ No newline at end of file diff --git a/MediaServer/Extension/CommonRtmp.h b/MediaServer/Extension/CommonRtmp.h new file mode 100644 index 0000000..faff330 --- /dev/null +++ b/MediaServer/Extension/CommonRtmp.h @@ -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; + + /** + * 构造函数 + * 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(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 diff --git a/MediaServer/Extension/CommonRtp.cpp b/MediaServer/Extension/CommonRtp.cpp new file mode 100644 index 0000000..fd3b8ab --- /dev/null +++ b/MediaServer/Extension/CommonRtp.cpp @@ -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; +} \ No newline at end of file diff --git a/MediaServer/Extension/CommonRtp.h b/MediaServer/Extension/CommonRtp.h new file mode 100644 index 0000000..46a370d --- /dev/null +++ b/MediaServer/Extension/CommonRtp.h @@ -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 ; + + /** + * 构造函数 + * @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 ; + + /** + * 输入帧数据并编码成rtp + * Input frame data and encode into rtp + + + * [AUTO-TRANSLATED:02bc9009] + */ + bool inputFrame(const Frame::Ptr &frame) override; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_COMMONRTP_H diff --git a/MediaServer/Extension/Factory.cpp b/MediaServer/Extension/Factory.cpp new file mode 100644 index 0000000..f430967 --- /dev/null +++ b/MediaServer/Extension/Factory.cpp @@ -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 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(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(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(frame, false, std::move(data)); +} + +}//namespace mediakit + diff --git a/MediaServer/Extension/Factory.h b/MediaServer/Extension/Factory.h new file mode 100644 index 0000000..d7d3665 --- /dev/null +++ b/MediaServer/Extension/Factory.h @@ -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 +#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 diff --git a/MediaServer/Extension/Frame.cpp b/MediaServer/Extension/Frame.cpp new file mode 100644 index 0000000..ceccced --- /dev/null +++ b/MediaServer/Extension/Frame.cpp @@ -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(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 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 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 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 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 >(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; + using onWriteFrame = std::function; + + /** + * 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 cb) { + return addDelegate(std::make_shared(std::move(cb))); +} + +}//namespace mediakit diff --git a/MediaServer/Extension/Frame.h b/MediaServer/Extension/Frame.h new file mode 100644 index 0000000..49ef476 --- /dev/null +++ b/MediaServer/Extension/Frame.h @@ -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 +#include +#include +#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; + + 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; + + /** + * 返回解码时间戳,单位毫秒 + * 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 _statistic; +}; + +class FrameImp : public Frame { +public: + using Ptr = std::shared_ptr; + + template + static std::shared_ptr create() { +#if 0 + static ResourcePool 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(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 _statistic; + +protected: + friend class toolkit::ResourcePool_l; + 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(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 +class FrameInternalBase : public Parent { +public: + using Ptr = std::shared_ptr; + 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 +class FrameInternal : public FrameInternalBase { +public: + using Ptr = std::shared_ptr; + FrameInternal(const Frame::Ptr &parent_frame, char *ptr, size_t size, size_t prefix_size) + : FrameInternalBase(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 + FrameAutoDelete(ARGS &&...args) : FrameFromPtr(std::forward(args)...) {} + + ~FrameAutoDelete() override { delete[] _ptr; }; + + bool cacheAble() const override { return true; } +}; + +// 把一个不可缓存的frame声明为可缓存的 [AUTO-TRANSLATED:2c8d0659] +// Declare a non-cacheable frame as cacheable +template +class FrameToCache : public Parent { +public: + template + FrameToCache(ARGS &&...args) : Parent(std::forward(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(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(); + 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(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 +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; + using Ptr = std::shared_ptr; + 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_cache; +}; + +/** + * 写帧接口的抽象接口类 + * Abstract interface class for write frame interface + + * [AUTO-TRANSLATED:dbe6a33c] + */ +class FrameWriterInterface { +public: + using Ptr = std::shared_ptr; + 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; + + /** + * 添加代理 + * Add proxy + + * [AUTO-TRANSLATED:0be3c076] + */ + FrameWriterInterface* addDelegate(FrameWriterInterface::Ptr delegate) { + std::lock_guard lck(_mtx); + return _delegates.emplace(delegate.get(), std::move(delegate)).first->second.get(); + } + + FrameWriterInterface* addDelegate(std::function cb); + + /** + * 删除代理 + * Delete proxy + + * [AUTO-TRANSLATED:c2c915aa] + */ + void delDelegate(FrameWriterInterface *ptr) { + std::lock_guard lck(_mtx); + _delegates.erase(ptr); + } + + /** + * 写入帧并派发 + * Write frame and dispatch + + * [AUTO-TRANSLATED:a3e7e6db] + */ + bool inputFrame(const Frame::Ptr &frame) override { + std::lock_guard 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 lck(_mtx); + return _delegates.size(); + } + + void clear() { + std::lock_guard lck(_mtx); + _delegates.clear(); + } + + /** + * 获取累计关键帧数 + * Get the cumulative number of keyframes + + * [AUTO-TRANSLATED:73cb2ab0] + */ + uint64_t getVideoKeyFrames() const { + std::lock_guard lck(_mtx); + return _video_key_frames; + } + + /** + * 获取帧数 + * Get the number of frames + + * [AUTO-TRANSLATED:118b395e] + */ + uint64_t getFrames() const { + std::lock_guard lck(_mtx); + return _frames; + } + + size_t getVideoGopSize() const { + std::lock_guard lck(_mtx); + return _gop_size; + } + + size_t getVideoGopInterval() const { + std::lock_guard lck(_mtx); + return _gop_interval_ms; + } + + int64_t getDuration() const { + std::lock_guard 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 _delegates; +}; + +} // namespace mediakit +#endif // ZLMEDIAKIT_FRAME_H \ No newline at end of file diff --git a/MediaServer/Extension/Track.h b/MediaServer/Extension/Track.h new file mode 100644 index 0000000..6c5712b --- /dev/null +++ b/MediaServer/Extension/Track.h @@ -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 +#include +#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; + + /** + * 默认构造 + * 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; + + /** + * 返回视频高度 + * 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 getConfigFrames() const { return std::vector{}; } +}; + +class VideoTrackImp : public VideoTrack { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * @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(*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; + + /** + * 返回音频采样率 + * 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; + + /** + * 构造函数 + * @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(*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 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 \ No newline at end of file diff --git a/MediaServer/FMP4/FMP4MediaSource.h b/MediaServer/FMP4/FMP4MediaSource.h new file mode 100644 index 0000000..7fe2321 --- /dev/null +++ b/MediaServer/FMP4/FMP4MediaSource.h @@ -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; + + template + FMP4Packet(ARGS && ...args) : toolkit::BufferString(std::forward(args)...) {}; + +public: + uint64_t time_stamp = 0; +}; + +// FMP4直播源 [AUTO-TRANSLATED:15c43604] +// FMP4 Live Source +class FMP4MediaSource final : public MediaSource, public toolkit::RingDelegate, private PacketCache{ +public: + using Ptr = std::shared_ptr; + using RingDataType = std::shared_ptr >; + using RingType = toolkit::RingBuffer; + + 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 &info_list)> &cb, + const std::function &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::inputPacket(stamp, true, std::move(packet), key); + } + + /** + * 情况GOP缓存 + * Clear GOP cache + + * [AUTO-TRANSLATED:d863f8c9] + */ + void clearCache() override { + PacketCache::clearCache(); + _ring->clearCache(); + } + +private: + void createRing(){ + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _ring = std::make_shared(_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 > 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 diff --git a/MediaServer/FMP4/FMP4MediaSourceMuxer.h b/MediaServer/FMP4/FMP4MediaSourceMuxer.h new file mode 100644 index 0000000..4cb8906 --- /dev/null +++ b/MediaServer/FMP4/FMP4MediaSourceMuxer.h @@ -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 { +public: + using Ptr = std::shared_ptr; + + FMP4MediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) { + _option = option; + _media_src = std::make_shared(tuple); + } + + ~FMP4MediaSourceMuxer() override { + try { + MP4MuxerMemory::flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + + void setListener(const std::weak_ptr &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(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 diff --git a/MediaServer/Http/HlsParser.cpp b/MediaServer/Http/HlsParser.cpp new file mode 100644 index 0000000..91c038f --- /dev/null +++ b/MediaServer/Http/HlsParser.cpp @@ -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 +#include +#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 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 diff --git a/MediaServer/Http/HlsParser.h b/MediaServer/Http/HlsParser.h new file mode 100644 index 0000000..c969791 --- /dev/null +++ b/MediaServer/Http/HlsParser.h @@ -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 +#include +#include + +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 &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 diff --git a/MediaServer/Http/HlsPlayer.cpp b/MediaServer/Http/HlsPlayer.cpp new file mode 100644 index 0000000..2443297 --- /dev/null +++ b/MediaServer/Http/HlsPlayer.cpp @@ -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()); + 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 weak_self = static_pointer_cast(shared_from_this()); + if (!_http_ts_player) { + _http_ts_player = std::make_shared(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(); + 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 &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 weak_self = static_pointer_cast(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(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 weak_self = static_pointer_cast(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 weak_self = shared_from_this(); + _timer = std::make_shared(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 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(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::onPlayResult(SockException(Err_success, "play hls success")); +} + +void HlsPlayerImp::onPlayResult(const SockException &ex) { + auto benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + if (ex || benchmark_mode) { + PlayerImp::onPlayResult(ex); + } else { + auto demuxer = std::make_shared(); + 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 weak_self = static_pointer_cast(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(_demuxer)->pushTask([weak_self, ex]() { + if (auto strong_self = weak_self.lock()) { + strong_self->_demuxer = nullptr; + strong_self->onShutdown(ex); + } + }); + return; + } catch (...) { + break; + } + } + PlayerImp::onShutdown(ex); +} + +vector HlsPlayerImp::getTracks(bool ready) const { + if (!_demuxer) { + return vector(); + } + return static_pointer_cast(_demuxer)->getTracks(ready); +} + +}//namespace mediakit diff --git a/MediaServer/Http/HlsPlayer.h b/MediaServer/Http/HlsPlayer.h new file mode 100644 index 0000000..1037389 --- /dev/null +++ b/MediaServer/Http/HlsPlayer.h @@ -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 { +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 getTracks(bool ready = true) const override { return _delegate.getTracks(ready); } + void pushTask(std::function 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 > > _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 &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_list; + std::list _ts_url_sort; + std::set _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, private TrackListener { +public: + using Ptr = std::shared_ptr; + 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 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 diff --git a/MediaServer/Http/HttpBody.cpp b/MediaServer/Http/HttpBody.cpp new file mode 100644 index 0000000..b73538c --- /dev/null +++ b/MediaServer/Http/HttpBody.cpp @@ -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 +#include + +#ifndef _WIN32 +#include +#endif +#if defined(__linux__) || defined(__linux) +#include +#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(_str, _offset, size); + _offset += size; + return ret; +} + +////////////////////////////////////////////////////////////////// +static mutex s_mtx; +static unordered_map /*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 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 getSharedMmap(const string &file_path, int64_t &file_size) { + { + lock_guard 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 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 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 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 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(const std::shared_ptr &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 _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(_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(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(_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(_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 diff --git a/MediaServer/Http/HttpBody.h b/MediaServer/Http/HttpBody.h new file mode 100644 index 0000000..fad045f --- /dev/null +++ b/MediaServer/Http/HttpBody.h @@ -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 +#include +#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{ +public: + using Ptr = std::shared_ptr; + 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 &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(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(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; + + /** + * 构造函数 + * @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 _fp; + std::shared_ptr _map_addr; + toolkit::ResourcePool _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; + + /** + * 构造函数 + * @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 diff --git a/MediaServer/Http/HttpChunkedSplitter.cpp b/MediaServer/Http/HttpChunkedSplitter.cpp new file mode 100644 index 0000000..224f9d0 --- /dev/null +++ b/MediaServer/Http/HttpChunkedSplitter.cpp @@ -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 +#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 diff --git a/MediaServer/Http/HttpChunkedSplitter.h b/MediaServer/Http/HttpChunkedSplitter.h new file mode 100644 index 0000000..1c29f79 --- /dev/null +++ b/MediaServer/Http/HttpChunkedSplitter.h @@ -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 +#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; + + 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 diff --git a/MediaServer/Http/HttpClient.cpp b/MediaServer/Http/HttpClient.cpp new file mode 100644 index 0000000..23b97b4 --- /dev/null +++ b/MediaServer/Http/HttpClient.cpp @@ -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 +#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([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(); + 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 */ diff --git a/MediaServer/Http/HttpClient.h b/MediaServer/Http/HttpClient.h new file mode 100644 index 0000000..c7ca243 --- /dev/null +++ b/MediaServer/Http/HttpClient.h @@ -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 +#include +#include +#include +#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 { +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; + + /** + * 发送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 _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 */ diff --git a/MediaServer/Http/HttpClientImp.cpp b/MediaServer/Http/HttpClientImp.cpp new file mode 100644 index 0000000..3c01e2c --- /dev/null +++ b/MediaServer/Http/HttpClientImp.cpp @@ -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::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 */ diff --git a/MediaServer/Http/HttpClientImp.h b/MediaServer/Http/HttpClientImp.h new file mode 100644 index 0000000..d186254 --- /dev/null +++ b/MediaServer/Http/HttpClientImp.h @@ -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 { +public: + using Ptr = std::shared_ptr; + +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_ */ diff --git a/MediaServer/Http/HttpConst.cpp b/MediaServer/Http/HttpConst.cpp new file mode 100644 index 0000000..7c1a896 --- /dev/null +++ b/MediaServer/Http/HttpConst.cpp @@ -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 +#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 diff --git a/MediaServer/Http/HttpConst.h b/MediaServer/Http/HttpConst.h new file mode 100644 index 0000000..7973243 --- /dev/null +++ b/MediaServer/Http/HttpConst.h @@ -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 + +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 diff --git a/MediaServer/Http/HttpCookie.cpp b/MediaServer/Http/HttpCookie.cpp new file mode 100644 index 0000000..43816bd --- /dev/null +++ b/MediaServer/Http/HttpCookie.cpp @@ -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 lck(_mtx_cookie); + if (!cookie || !(*cookie)) { + return; + } + _all_cookie[cookie->_host][cookie->_path][cookie->_key] = cookie; +} + +vector HttpCookieStorage::get(const string &host, const string &path) { + vector ret(0); + lock_guard 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 */ diff --git a/MediaServer/Http/HttpCookie.h b/MediaServer/Http/HttpCookie.h new file mode 100644 index 0000000..86d17a9 --- /dev/null +++ b/MediaServer/Http/HttpCookie.h @@ -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 +#include +#include +#include +#include +#include + +namespace mediakit { + +/** + * http客户端cookie对象 + * http client cookie object + + * [AUTO-TRANSLATED:5c1840bb] + */ +class HttpCookie { +public: + using Ptr = std::shared_ptr; + 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 get(const std::string &host,const std::string &path); + +private: + HttpCookieStorage() = default; + +private: + std::unordered_map > > _all_cookie; + std::mutex _mtx_cookie; +}; + + +} /* namespace mediakit */ + +#endif //ZLMEDIAKIT_HTTPCOOKIE_H diff --git a/MediaServer/Http/HttpCookieManager.cpp b/MediaServer/Http/HttpCookieManager.cpp new file mode 100644 index 0000000..1b3d4c3 --- /dev/null +++ b/MediaServer/Http/HttpCookieManager.cpp @@ -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 &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( + 10.0f, + [this]() { + onManager(); + return true; + }, + nullptr); +} + +HttpCookieManager::~HttpCookieManager() { + _timer.reset(); +} + +void HttpCookieManager::onManager() { + lock_guard 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 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 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 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 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 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 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 \ No newline at end of file diff --git a/MediaServer/Http/HttpCookieManager.h b/MediaServer/Http/HttpCookieManager.h new file mode 100644 index 0000000..a0e29a4 --- /dev/null +++ b/MediaServer/Http/HttpCookieManager.h @@ -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 +#include + +#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; + /** + * 构建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 &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 + T& getAttach() { + return _attach.get(); + } + +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 _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 _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 { +public: + friend class HttpServerCookie; + using Ptr = std::shared_ptr; + ~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> + _map_cookie; + std::unordered_map< + std::string /*cookie_name*/, + std::unordered_map>> + _map_uid_to_cookie; + std::recursive_mutex _mtx_cookie; + toolkit::Timer::Ptr _timer; + RandStrGenerator _generator; +}; + +} // namespace mediakit + +#endif // SRC_HTTP_COOKIEMANAGER_H diff --git a/MediaServer/Http/HttpDownloader.cpp b/MediaServer/Http/HttpDownloader.cpp new file mode 100644 index 0000000..b2ab5fb --- /dev/null +++ b/MediaServer/Http/HttpDownloader.cpp @@ -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 */ diff --git a/MediaServer/Http/HttpDownloader.h b/MediaServer/Http/HttpDownloader.h new file mode 100644 index 0000000..08f8f3f --- /dev/null +++ b/MediaServer/Http/HttpDownloader.h @@ -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; + using onDownloadResult = std::function; + + ~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_ */ diff --git a/MediaServer/Http/HttpFileManager.cpp b/MediaServer/Http/HttpFileManager.cpp new file mode 100644 index 0000000..6f53b8d --- /dev/null +++ b/MediaServer/Http/HttpFileManager.cpp @@ -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 +#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(storage).sin_addr), 4); + break; + } + case AF_INET6: { + memcpy(_bytes, &(reinterpret_cast(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>; + 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 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 << "\r\n" + "\r\n" + "File Index\r\n" + "\r\n" + "\r\n" + "

Index of "; + + ss << httpPath; + ss << "

\r\n"; + if (httpPath != "/") { + ss << "
  • "; + ss << "root"; + ss << "
  • \r\n"; + + ss << "
  • "; + ss << "../"; + ss << "
  • \r\n"; + } + + multimap > 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 << "
  • " << i++ << "\t"; + ss << ""; + // 路径名称 [AUTO-TRANSLATED:4dae8790] + // Path name + ss << pr.second.first; + if (isDir) { + ss << "/
  • \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 << "\r\n"; + } + ss << "
      \r\n"; + ss << "
    \r\n"; + 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; + + 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 &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(); + 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(); + 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(); + // 记录用户能访问的路径 [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(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(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(media_info.schema) = HLS_SCHEMA; + replace(const_cast(media_info.stream), kHlsSuffix, ""); + } else { + const_cast(media_info.schema) = HLS_FMP4_SCHEMA; + replace(const_cast(media_info.stream), kHlsFMP4Suffix, ""); + } + } + + weak_ptr weakSession = static_pointer_cast(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()._path); + } + cb(401, "text/html", headerOut, std::make_shared(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()._path); + } + HttpSession::HttpResponseInvoker invoker = [&](int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { + if (cookie && body) { + auto& attach = cookie->getAttach(); + if (attach._hls_data) { + attach._hls_data->addByteUsage(body->remainSize()); + } + } + cb(code, HttpFileManager::getContentType(file_path.data()), headerOut, body); + }; + GET_CONFIG_FUNC(vector, 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(); + 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(src); + if (!hls) { + // 流不在线 [AUTO-TRANSLATED:5a6a5695] + // The stream is not online + response_file(cookie, cb, file_path, parser); + return; + } + + auto &attach = cookie->getAttach(); + 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).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()._path); + } + cb(err_msg.empty() ? 200 : 401, "text/html", headerOut, std::make_shared(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(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(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(file)); + return; + } + + // file是文件路径 [AUTO-TRANSLATED:28dcac38] + // file is the file path + GET_CONFIG(string, charSet, Http::kCharSet); + StrCaseMap &httpHeader = const_cast(responseHeader); + auto fileBody = std::make_shared(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(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 diff --git a/MediaServer/Http/HttpFileManager.h b/MediaServer/Http/HttpFileManager.h new file mode 100644 index 0000000..4c636dd --- /dev/null +++ b/MediaServer/Http/HttpFileManager.h @@ -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 HttpResponseInvokerLambda0; + typedef std::function HttpResponseInvokerLambda1; + + template + HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename toolkit::function_traits::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 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 diff --git a/MediaServer/Http/HttpRequestSplitter.cpp b/MediaServer/Http/HttpRequestSplitter.cpp new file mode 100644 index 0000000..65a6c65 --- /dev/null +++ b/MediaServer/Http/HttpRequestSplitter.cpp @@ -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 */ + diff --git a/MediaServer/Http/HttpRequestSplitter.h b/MediaServer/Http/HttpRequestSplitter.h new file mode 100644 index 0000000..0f9735b --- /dev/null +++ b/MediaServer/Http/HttpRequestSplitter.h @@ -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 +#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 diff --git a/MediaServer/Http/HttpRequester.cpp b/MediaServer/Http/HttpRequester.cpp new file mode 100644 index 0000000..7a564ac --- /dev/null +++ b/MediaServer/Http/HttpRequester.cpp @@ -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 +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 weak_self = std::static_pointer_cast(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(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(); + // 获取一次静态信息,定时上报主要方便统计在线实例个数 [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 diff --git a/MediaServer/Http/HttpRequester.h b/MediaServer/Http/HttpRequester.h new file mode 100644 index 0000000..f5a5b45 --- /dev/null +++ b/MediaServer/Http/HttpRequester.h @@ -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; + using HttpRequesterResult = std::function; + + 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 */ diff --git a/MediaServer/Http/HttpSession.cpp b/MediaServer/Http/HttpSession.cpp new file mode 100644 index 0000000..0b89d18 --- /dev/null +++ b/MediaServer/Http/HttpSession.cpp @@ -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 +#include +#include +#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 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 &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 weak_self = static_pointer_cast(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(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 &cb) { + return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) { + auto fmp4_src = dynamic_pointer_cast(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(fmp4_src->getInitSegment()), true); + weak_ptr weak_self = static_pointer_cast(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(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 &cb) { + return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) { + auto ts_src = dynamic_pointer_cast(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 weak_self = static_pointer_cast(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(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 &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(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 weak_self = static_pointer_cast(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(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 _session; + HttpBody::Ptr _body; + bool _close_when_complete; + bool _read_complete = false; +}; + +class AsyncSender { +public: + using Ptr = std::shared_ptr; + 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 &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 &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(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(static_pointer_cast(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(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 weak_self = static_pointer_cast(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(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(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 HttpSession::getSharedPtr() { + return dynamic_pointer_cast(shared_from_this()); +} + +} /* namespace mediakit */ diff --git a/MediaServer/Http/HttpSession.h b/MediaServer/Http/HttpSession.h new file mode 100644 index 0000000..2754431 --- /dev/null +++ b/MediaServer/Http/HttpSession.h @@ -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 +#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; + 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; + + 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 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 &cb); + + bool checkLiveStreamFlv(const std::function &cb = nullptr); + bool checkLiveStreamTS(const std::function &cb = nullptr); + bool checkLiveStreamFMP4(const std::function &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 _on_recv_body; +}; + +using HttpsSession = toolkit::SessionWithSSL; + +} /* namespace mediakit */ + +#endif /* SRC_HTTP_HTTPSESSION_H_ */ diff --git a/MediaServer/Http/HttpTSPlayer.cpp b/MediaServer/Http/HttpTSPlayer.cpp new file mode 100644 index 0000000..f22aafe --- /dev/null +++ b/MediaServer/Http/HttpTSPlayer.cpp @@ -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(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 diff --git a/MediaServer/Http/HttpTSPlayer.h b/MediaServer/Http/HttpTSPlayer.h new file mode 100644 index 0000000..523d558 --- /dev/null +++ b/MediaServer/Http/HttpTSPlayer.h @@ -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; + using onComplete = std::function; + + 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 diff --git a/MediaServer/Http/TsPlayer.cpp b/MediaServer/Http/TsPlayer.cpp new file mode 100644 index 0000000..339064e --- /dev/null +++ b/MediaServer/Http/TsPlayer.cpp @@ -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(); + setProxyUrl((*this)[Client::kProxyUrl]); + setHeaderTimeout((*this)[Client::kTimeoutMS].as()); + setBodyTimeout((*this)[Client::kMediaTimeoutMS].as()); + 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 \ No newline at end of file diff --git a/MediaServer/Http/TsPlayer.h b/MediaServer/Http/TsPlayer.h new file mode 100644 index 0000000..4674f68 --- /dev/null +++ b/MediaServer/Http/TsPlayer.h @@ -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 diff --git a/MediaServer/Http/TsPlayerImp.h b/MediaServer/Http/TsPlayerImp.h new file mode 100644 index 0000000..821fffb --- /dev/null +++ b/MediaServer/Http/TsPlayerImp.h @@ -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 +#include "TsPlayer.h" + +namespace mediakit { + +class TsPlayerImp : public PlayerImp, private TrackListener { +public: + using Ptr = std::shared_ptr; + + 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 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 diff --git a/MediaServer/Http/TsplayerImp.cpp b/MediaServer/Http/TsplayerImp.cpp new file mode 100644 index 0000000..42df6a5 --- /dev/null +++ b/MediaServer/Http/TsplayerImp.cpp @@ -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(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::onPlayResult(SockException(Err_success, "play http-ts success")); +} + +void TsPlayerImp::onPlayResult(const SockException &ex) { + auto benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + if (ex || benchmark_mode) { + PlayerImp::onPlayResult(ex); + } else { + auto demuxer = std::make_shared(); + 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 weak_self = static_pointer_cast(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(_demuxer)->pushTask([weak_self, ex]() { + if (auto strong_self = weak_self.lock()) { + strong_self->_demuxer = nullptr; + strong_self->onShutdown(ex); + } + }); + return; + } catch (...) { + break; + } + } + PlayerImp::onShutdown(ex); +} + +vector TsPlayerImp::getTracks(bool ready) const { + if (!_demuxer) { + return vector(); + } + return static_pointer_cast(_demuxer)->getTracks(ready); +} + +}//namespace mediakit \ No newline at end of file diff --git a/MediaServer/Http/WebSocketClient.h b/MediaServer/Http/WebSocketClient.h new file mode 100644 index 0000000..14fe79b --- /dev/null +++ b/MediaServer/Http/WebSocketClient.h @@ -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 +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 +class ClientTypeImp : public ClientType { +public: + friend class HttpWsClient; + + using onBeforeSendCB = std::function; + + template + ClientTypeImp(ArgsType &&...args) : ClientType(std::forward(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 +class HttpWsClient : public HttpClientImp, public WebSocketSplitter { +public: + using Ptr = std::shared_ptr; + + HttpWsClient(const std::shared_ptr> &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(headers)["Sec-WebSocket-Accept"]) { + // success + onWebSocketException(toolkit::SockException()); + // 防止ws服务器返回Content-Length [AUTO-TRANSLATED:f4454ae6] + // Prevent ws server from returning Content-Length + const_cast(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(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(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(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(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 weakSelf = std::static_pointer_cast(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 _onRecv; + std::weak_ptr> _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 +class WebSocketClient : public ClientTypeImp { +public: + using Ptr = std::shared_ptr; + + template + WebSocketClient(ArgsType &&...args) : ClientTypeImp(std::forward(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>(std::static_pointer_cast(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::Ptr _wsClient; +}; + +} // namespace mediakit +#endif // ZLMEDIAKIT_WebSocketClient_H diff --git a/MediaServer/Http/WebSocketSession.h b/MediaServer/Http/WebSocketSession.h new file mode 100644 index 0000000..5c121c1 --- /dev/null +++ b/MediaServer/Http/WebSocketSession.h @@ -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; + + 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 +class SessionTypeImp : public SessionType, public SendInterceptor{ +public: + using Ptr = std::shared_ptr; + + 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 +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 >(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 +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(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 weakSelf = std::static_pointer_cast(HttpSessionType::shared_from_this()); + std::dynamic_pointer_cast(_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(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(_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(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(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 _weak_server; + toolkit::Session::Ptr _session; + Creator _creator; + toolkit::Ticker _recv_ticker; +}; + + +template +class WebSocketSession : public WebSocketSessionBase,HttpSessionType,DataType>{ +public: + WebSocketSession(const toolkit::Socket::Ptr &pSock) : WebSocketSessionBase,HttpSessionType,DataType>(pSock){} +}; + +#endif //ZLMEDIAKIT_WEBSOCKETSESSION_H diff --git a/MediaServer/Http/WebSocketSplitter.cpp b/MediaServer/Http/WebSocketSplitter.cpp new file mode 100644 index 0000000..676df46 --- /dev/null +++ b/MediaServer/Http/WebSocketSplitter.cpp @@ -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 +#if !defined(_WIN32) +#include +#include +#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(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 */ + + + + diff --git a/MediaServer/Http/WebSocketSplitter.h b/MediaServer/Http/WebSocketSplitter.h new file mode 100644 index 0000000..a49b693 --- /dev/null +++ b/MediaServer/Http/WebSocketSplitter.h @@ -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 +#include +#include +#include +#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; + 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 _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; + + template + WebSocketBuffer(WebSocketHeader::Type headType, bool fin, ARGS &&...args) + : toolkit::BufferString(std::forward(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 diff --git a/MediaServer/MediaServer.cpp b/MediaServer/MediaServer.cpp new file mode 100644 index 0000000..fd6525f --- /dev/null +++ b/MediaServer/MediaServer.cpp @@ -0,0 +1,32 @@ +#include "MediaServer.h" +#include "BoostLog.h" +#include "Network/TcpServer.h" +#include "Rtsp/RtspSession.h" +#include + +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(); + if (ssl) { + m_d->rtsp_server[ssl]->start>(port); + } else { + m_d->rtsp_server[ssl]->start(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; + } +} \ No newline at end of file diff --git a/MediaServer/MediaServer.h b/MediaServer/MediaServer.h new file mode 100644 index 0000000..c4bdbe1 --- /dev/null +++ b/MediaServer/MediaServer.h @@ -0,0 +1,17 @@ +#ifndef __MEDIASERVER_H__ +#define __MEDIASERVER_H__ + +#include + +class MediaServerPrivate; + +class MediaServer { +public: + MediaServer(uint16_t port, bool ssl); + ~MediaServer(); + +private: + MediaServerPrivate *m_d = nullptr; +}; + +#endif // __MEDIASERVER_H__ \ No newline at end of file diff --git a/MediaServer/Player/MediaPlayer.cpp b/MediaServer/Player/MediaPlayer.cpp new file mode 100644 index 0000000..8ed4cc2 --- /dev/null +++ b/MediaServer/Player/MediaPlayer.cpp @@ -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 +#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 &delegate, const Socket::onCreateSocket &cb){ + auto helper = dynamic_pointer_cast(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 */ diff --git a/MediaServer/Player/MediaPlayer.h b/MediaServer/Player/MediaPlayer.h new file mode 100644 index 0000000..ca18701 --- /dev/null +++ b/MediaServer/Player/MediaPlayer.h @@ -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 +#include +#include "PlayerBase.h" + +namespace mediakit { + +class MediaPlayer : public PlayerImp { +public: + using Ptr = std::shared_ptr; + + 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_ */ diff --git a/MediaServer/Player/PlayerBase.cpp b/MediaServer/Player/PlayerBase.cpp new file mode 100644 index 0000000..b94aaee --- /dev/null +++ b/MediaServer/Player/PlayerBase.cpp @@ -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 +#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 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(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(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 */ diff --git a/MediaServer/Player/PlayerBase.h b/MediaServer/Player/PlayerBase.h new file mode 100644 index 0000000..dc03394 --- /dev/null +++ b/MediaServer/Player/PlayerBase.h @@ -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 +#include +#include +#include +#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; + using Event = std::function; + + 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 getTracks(bool ready = true) const override { return std::vector(); }; + + /** + * 设置一个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 &cb) = 0; + +protected: + virtual void onResume() = 0; + virtual void onShutdown(const toolkit::SockException &ex) = 0; + virtual void onPlayResult(const toolkit::SockException &ex) = 0; +}; + +template +class PlayerImp : public Parent { +public: + using Ptr = std::shared_ptr; + + template + PlayerImp(ArgsType &&...args) : Parent(std::forward(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 getTracks(bool ready = true) const override { + return _delegate ? _delegate->getTracks(ready) : Parent::getTracks(ready); + } + + std::shared_ptr getSockInfo() const { + return std::dynamic_pointer_cast(_delegate); + } + + void setMediaSource(const MediaSource::Ptr &src) override { + if (_delegate) { + _delegate->setMediaSource(src); + } + _media_src = src; + } + + void setOnShutdown(const std::function &cb) override { + if (_delegate) { + _delegate->setOnShutdown(cb); + } + _on_shutdown = cb; + } + + void setOnPlayResult(const std::function &cb) override { + if (_delegate) { + _delegate->setOnPlayResult(cb); + } + _on_play_result = cb; + } + + void setOnResume(const std::function &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 _on_resume; + PlayerBase::Event _on_shutdown; + PlayerBase::Event _on_play_result; + MediaSource::Ptr _media_src; + std::shared_ptr _delegate; +}; + +} /* namespace mediakit */ + +#endif /* SRC_PLAYER_PLAYERBASE_H_ */ diff --git a/MediaServer/Player/PlayerProxy.cpp b/MediaServer/Player/PlayerProxy.cpp new file mode 100644 index 0000000..9ccd701 --- /dev/null +++ b/MediaServer/Player/PlayerProxy.cpp @@ -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 cb) { + _on_play = std::move(cb); +} + +void PlayerProxy::setOnClose(function cb) { + _on_close = cb ? std::move(cb) : [](const SockException &) {}; +} + +void PlayerProxy::setOnDisconnect(std::function cb) { + _on_disconnect = cb ? std::move(cb) : [] () {}; +} + +void PlayerProxy::setOnConnect(std::function 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(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(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 weakSelf = shared_from_this(); + std::shared_ptr 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(_delegate)) { + // rtsp拉流 [AUTO-TRANSLATED:189cf691] + // Rtsp stream + GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy); + if (directProxy && _option.enable_rtsp) { + mediaSource = std::make_shared(_tuple); + } + } else if (dynamic_pointer_cast(_delegate)) { + // rtmp拉流 [AUTO-TRANSLATED:f70a142c] + // Rtmp stream + GET_CONFIG(bool, directProxy, Rtmp::kDirectProxy); + if (directProxy && _option.enable_rtmp) { + mediaSource = std::make_shared(_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 weakSelf = shared_from_this(); + _timer = std::make_shared( + 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 weakSelf = dynamic_pointer_cast(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 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(_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(_tuple, getDuration(), _option); + _option.enable_rtsp = old; + } + } else if (dynamic_pointer_cast(_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(_tuple, getDuration(), _option); + _option.enable_rtmp = old; + } + } else { + // 其他拉流代理 [AUTO-TRANSLATED:e5f2e45d] + // Other stream proxies + if (reset_when_replay || !_muxer) { + _muxer = std::make_shared(_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 */ diff --git a/MediaServer/Player/PlayerProxy.h b/MediaServer/Player/PlayerProxy.h new file mode 100644 index 0000000..a90d2b4 --- /dev/null +++ b/MediaServer/Player/PlayerProxy.h @@ -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 + +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 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 { +public: + using Ptr = std::shared_ptr; + + // 如果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 cb); + + /** + * 设置主动关闭回调 + * @param cb 回调对象 + * Set a callback for active closure + * @param cb Callback object + + * [AUTO-TRANSLATED:83b7700a] + */ + void setOnClose(std::function 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 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 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 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 _on_disconnect; + std::function _on_connect; + std::function _on_close; + std::function _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 _live_status; + std::atomic _live_secs; + + std::atomic _repull_count; +}; + +} /* namespace mediakit */ +#endif /* SRC_DEVICE_PLAYERPROXY_H_ */ diff --git a/MediaServer/Pusher/MediaPusher.cpp b/MediaServer/Pusher/MediaPusher.cpp new file mode 100644 index 0000000..494f0ee --- /dev/null +++ b/MediaServer/Pusher/MediaPusher.cpp @@ -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 +#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 &delegate, const Socket::onCreateSocket &cb){ + auto helper = dynamic_pointer_cast(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 */ diff --git a/MediaServer/Pusher/MediaPusher.h b/MediaServer/Pusher/MediaPusher.h new file mode 100644 index 0000000..899f7cf --- /dev/null +++ b/MediaServer/Pusher/MediaPusher.h @@ -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 +#include +#include "PusherBase.h" + +namespace mediakit { + +class MediaPusher : public PusherImp { +public: + using Ptr = std::shared_ptr; + + 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 _src; + toolkit::EventPoller::Ptr _poller; + toolkit::Socket::onCreateSocket _on_create_socket; +}; + +} /* namespace mediakit */ + +#endif /* SRC_PUSHER_MEDIAPUSHER_H_ */ diff --git a/MediaServer/Pusher/PusherBase.cpp b/MediaServer/Pusher/PusherBase.cpp new file mode 100644 index 0000000..32ae58e --- /dev/null +++ b/MediaServer/Pusher/PusherBase.cpp @@ -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 +#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 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(poller, std::dynamic_pointer_cast(src)), release_func); + } + + if (strcasecmp("rtsp",prefix.data()) == 0) { + return PusherBase::Ptr(new RtspPusherImp(poller, std::dynamic_pointer_cast(src)), release_func); + } + + if (strcasecmp("rtmps",prefix.data()) == 0) { + return PusherBase::Ptr(new TcpClientWithSSL(poller, std::dynamic_pointer_cast(src)), release_func); + } + + if (strcasecmp("rtmp",prefix.data()) == 0) { + return PusherBase::Ptr(new RtmpPusherImp(poller, std::dynamic_pointer_cast(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 */ diff --git a/MediaServer/Pusher/PusherBase.h b/MediaServer/Pusher/PusherBase.h new file mode 100644 index 0000000..859722e --- /dev/null +++ b/MediaServer/Pusher/PusherBase.h @@ -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 +#include +#include +#include +#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; + using Event = std::function; + + 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 +class PusherImp : public Parent { +public: + using Ptr = std::shared_ptr; + + template + PusherImp(ArgsType &&...args) : Parent(std::forward(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 getSockInfo() const { + return std::dynamic_pointer_cast(_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; +}; + +} /* namespace mediakit */ +#endif /* SRC_PUSHER_PUSHERBASE_H_ */ diff --git a/MediaServer/Pusher/PusherProxy.cpp b/MediaServer/Pusher/PusherProxy.cpp new file mode 100644 index 0000000..397aada --- /dev/null +++ b/MediaServer/Pusher/PusherProxy.cpp @@ -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 &cb) { + _on_publish = cb; +} + +void PusherProxy::setOnClose(const function &cb) { + _on_close = cb; +} + +void PusherProxy::publish(const string &dst_url) { + std::weak_ptr weak_self = shared_from_this(); + std::shared_ptr 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 weak_self = shared_from_this(); + _timer = std::make_shared( + 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 */ diff --git a/MediaServer/Pusher/PusherProxy.h b/MediaServer/Pusher/PusherProxy.h new file mode 100644 index 0000000..965096b --- /dev/null +++ b/MediaServer/Pusher/PusherProxy.h @@ -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 { +public: + using Ptr = std::shared_ptr; + + // 如果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 &cb); + + /** + * 设置主动关闭回调 + * @param cb 回调对象 + * Set the active close callback + * @param cb Callback object + + * [AUTO-TRANSLATED:83b7700a] + */ + void setOnClose(const std::function &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 _live_status; + std::atomic _live_secs; + std::atomic _republish_count; + std::weak_ptr _weak_src; + std::function _on_close; + std::function _on_publish; +}; + +} /* namespace mediakit */ + +#endif // SRC_DEVICE_PUSHERPROXY_H diff --git a/MediaServer/Record/HlsMaker.cpp b/MediaServer/Record/HlsMaker.cpp new file mode 100644 index 0000000..b26d3bc --- /dev/null +++ b/MediaServer/Record/HlsMaker.cpp @@ -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 +#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> 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 diff --git a/MediaServer/Record/HlsMaker.h b/MediaServer/Record/HlsMaker.h new file mode 100644 index 0000000..05621b0 --- /dev/null +++ b/MediaServer/Record/HlsMaker.h @@ -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 +#include +#include +#include + +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 > _seg_dur_list; +}; + +}//namespace mediakit +#endif //HLSMAKER_H diff --git a/MediaServer/Record/HlsMakerImp.cpp b/MediaServer/Record/HlsMakerImp.cpp new file mode 100644 index 0000000..002902f --- /dev/null +++ b/MediaServer/Record/HlsMakerImp.cpp @@ -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 +#include +#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 &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 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 HlsMakerImp::makeFile(const string &file, bool setbuf) { + auto file_buf = _file_buf; + auto ret = shared_ptr(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(_info) = tuple; + _media_src = std::make_shared(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info); +} + +HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { + return _media_src; +} + +} // namespace mediakit \ No newline at end of file diff --git a/MediaServer/Record/HlsMakerImp.h b/MediaServer/Record/HlsMakerImp.h new file mode 100644 index 0000000..e82be46 --- /dev/null +++ b/MediaServer/Record/HlsMakerImp.h @@ -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 +#include +#include +#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 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; + std::shared_ptr _file_buf; + HlsMediaSource::Ptr _media_src; + toolkit::EventPoller::Ptr _poller; + std::map _segment_file_paths; +}; + +}//namespace mediakit +#endif //HLSMAKERIMP_H diff --git a/MediaServer/Record/HlsMediaSource.cpp b/MediaServer/Record/HlsMediaSource.cpp new file mode 100644 index 0000000..e1d9b12 --- /dev/null +++ b/MediaServer/Record/HlsMediaSource.cpp @@ -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 &sock_info) { + _info = info; + _sock_info = sock_info; + _added = std::make_shared(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 weakSelf = std::static_pointer_cast(shared_from_this()); + auto lam = [weakSelf](int size) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + strongSelf->onReaderChanged(size); + }; + _ring = std::make_shared(0, std::move(lam)); + regist(); + } + + // 赋值m3u8索引文件内容 [AUTO-TRANSLATED:c11882b5] + // Assign m3u8 index file content + std::lock_guard lck(_mtx_index); + _index_file = std::move(index_file); + + if (!_index_file.empty()) { + _list_cb.for_each([&](const std::function& cb) { cb(_index_file); }); + _list_cb.clear(); + } +} + +void HlsMediaSource::getIndexFile(std::function cb) +{ + std::lock_guard 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 diff --git a/MediaServer/Record/HlsMediaSource.h b/MediaServer/Record/HlsMediaSource.h new file mode 100644 index 0000000..53f7452 --- /dev/null +++ b/MediaServer/Record/HlsMediaSource.h @@ -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 + +namespace mediakit { + +class HlsMediaSource : public MediaSource { +public: + friend class HlsCookieData; + + using RingType = toolkit::RingBuffer; + using Ptr = std::shared_ptr; + + 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 cb); + + /** + * 同步获取m3u8文件 + * Synchronously get the m3u8 file + + + * [AUTO-TRANSLATED:52b228df] + */ + std::string getIndexFile() const { + std::lock_guard lck(_mtx_index); + return _index_file; + } + + void onSegmentSize(size_t bytes) { _speed[TrackVideo] += bytes; } + + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { + _ring->getInfoList(cb, on_change); + } + +private: + RingType::Ptr _ring; + std::string _index_file; + mutable std::mutex _mtx_index; + toolkit::List> _list_cb; +}; + +class HlsCookieData { +public: + using Ptr = std::shared_ptr; + + HlsCookieData(const MediaInfo &info, const std::shared_ptr &sock_info); + ~HlsCookieData(); + + void addByteUsage(size_t bytes); + void setMediaSource(const HlsMediaSource::Ptr &src); + HlsMediaSource::Ptr getMediaSource() const; + +private: + void addReaderCount(); + +private: + std::atomic _bytes { 0 }; + MediaInfo _info; + std::shared_ptr _added; + toolkit::Ticker _ticker; + std::weak_ptr _src; + std::shared_ptr _sock_info; + HlsMediaSource::RingType::RingReader::Ptr _ring_reader; +}; + +} // namespace mediakit +#endif // ZLMEDIAKIT_HLSMEDIASOURCE_H diff --git a/MediaServer/Record/HlsRecorder.h b/MediaServer/Record/HlsRecorder.h new file mode 100644 index 0000000..1810aec --- /dev/null +++ b/MediaServer/Record/HlsRecorder.h @@ -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 +class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this > { +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(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 &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 _hls; +}; + +class HlsRecorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsRecorder(ARGS && ...args) : HlsRecorderBase(false, std::forward(args)...) {} + ~HlsRecorder() override { + try { + this->flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + +private: + void onWrite(std::shared_ptr 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 { +public: + using Ptr = std::shared_ptr; + template + HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase(true, std::forward(args)...) {} + ~HlsFMP4Recorder() override { + try { + this->flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + + void addTrackCompleted() override { + HlsRecorderBase::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 diff --git a/MediaServer/Record/MP4.cpp b/MediaServer/Record/MP4.cpp new file mode 100644 index 0000000..f922289 --- /dev/null +++ b/MediaServer/Record/MP4.cpp @@ -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 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) diff --git a/MediaServer/Record/MP4.h b/MediaServer/Record/MP4.h new file mode 100644 index 0000000..cc815e8 --- /dev/null +++ b/MediaServer/Record/MP4.h @@ -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 +#include +#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 { +public: + using Ptr = std::shared_ptr; + using Writer = std::shared_ptr; + using Reader = std::shared_ptr; + + 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; + + /** + * 打开磁盘文件 + * @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; +}; + +class MP4FileMemory : public MP4FileIO{ +public: + using Ptr = std::shared_ptr; + + /** + * 获取文件大小 + * 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 diff --git a/MediaServer/Record/MP4Demuxer.cpp b/MediaServer/Record/MP4Demuxer.cpp new file mode 100644 index 0000000..4d6800d --- /dev/null +++ b/MediaServer/Record/MP4Demuxer.cpp @@ -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(); + _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 MP4Demuxer::getTracks(bool ready) const { + vector 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 diff --git a/MediaServer/Record/MP4Demuxer.h b/MediaServer/Record/MP4Demuxer.h new file mode 100644 index 0000000..87cce6a --- /dev/null +++ b/MediaServer/Record/MP4Demuxer.h @@ -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() 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 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 _tracks; + toolkit::ResourcePool _buffer_pool; +}; + + +}//namespace mediakit +#endif//ENABLE_MP4 +#endif //ZLMEDIAKIT_MP4DEMUXER_H diff --git a/MediaServer/Record/MP4Muxer.cpp b/MediaServer/Record/MP4Muxer.cpp new file mode 100644 index 0000000..ffa3295 --- /dev/null +++ b/MediaServer/Record/MP4Muxer.cpp @@ -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(); + _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(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(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(); +} + +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(); + _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) diff --git a/MediaServer/Record/MP4Muxer.h b/MediaServer/Record/MP4Muxer.h new file mode 100644 index 0000000..1a143d6 --- /dev/null +++ b/MediaServer/Record/MP4Muxer.h @@ -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 _tracks; +}; + +class MP4Muxer : public MP4MuxerInterface{ +public: + using Ptr = std::shared_ptr; + ~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 diff --git a/MediaServer/Record/MP4Reader.cpp b/MediaServer/Record/MP4Reader.cpp new file mode 100644 index 0000000..55941be --- /dev/null +++ b/MediaServer/Record/MP4Reader.cpp @@ -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(); + _demuxer->openMP4(_file_path); + + if (tuple.stream.empty()) { + return; + } + + _muxer = std::make_shared(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_sec, [strong_self]() { + lock_guard lck(strong_self->_mtx); + return strong_self->readSample(); + }, _poller); + } else { + weak_ptr weak_self = strong_self; + _timer = std::make_shared(timer_sec, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + lock_guard 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 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 \ No newline at end of file diff --git a/MediaServer/Record/MP4Reader.h b/MediaServer/Record/MP4Reader.h new file mode 100644 index 0000000..2bd9597 --- /dev/null +++ b/MediaServer/Record/MP4Reader.h @@ -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, public MediaSourceEvent { +public: + using Ptr = std::shared_ptr; + + /** + * 点播一个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_ */ diff --git a/MediaServer/Record/MP4Recorder.cpp b/MediaServer/Record/MP4Recorder.cpp new file mode 100644 index 0000000..cd9e0e5 --- /dev/null +++ b/MediaServer/Record/MP4Recorder.cpp @@ -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 +#include +#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(_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(); + 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 diff --git a/MediaServer/Record/MP4Recorder.h b/MediaServer/Record/MP4Recorder.h new file mode 100644 index 0000000..6c042b8 --- /dev/null +++ b/MediaServer/Record/MP4Recorder.h @@ -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 +#include +#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(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 _tracks; +}; + +#endif ///ENABLE_MP4 + +} /* namespace mediakit */ + +#endif /* MP4MAKER_H_ */ diff --git a/MediaServer/Record/MPEG.cpp b/MediaServer/Record/MPEG.cpp new file mode 100644 index 0000000..483cf12 --- /dev/null +++ b/MediaServer/Record/MPEG.cpp @@ -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 +#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 \ No newline at end of file diff --git a/MediaServer/Record/MPEG.h b/MediaServer/Record/MPEG.h new file mode 100644 index 0000000..e74f763 --- /dev/null +++ b/MediaServer/Record/MPEG.h @@ -0,0 +1,131 @@ +/* + * 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_MPEG_H +#define ZLMEDIAKIT_MPEG_H + +#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY) + +#include +#include +#include +#include "Extension/Frame.h" +#include "Extension/Track.h" +#include "Common/MediaSink.h" +#include "Util/ResourcePool.h" +namespace mediakit { + +// 该类用于产生MPEG-TS/MPEG-PS [AUTO-TRANSLATED:267efc85] +// This class is used to generate MPEG-TS/MPEG-PS +class MpegMuxer : public MediaSinkInterface { +public: + MpegMuxer(bool is_ps = false); + ~MpegMuxer() override; + + /** + * 添加音视频轨道 + * Add audio and video tracks + + * [AUTO-TRANSLATED:7b0c1d64] + */ + bool addTrack(const Track::Ptr &track) override; + + /** + * 重置音视频轨道 + * Reset audio and video tracks + + * [AUTO-TRANSLATED:6eb1b742] + */ + void resetTracks() override; + + /** + * 输入帧数据 + * Input frame data + + * [AUTO-TRANSLATED:d13bc7f2] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame buffers in the output + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + +protected: + /** + * 输出ts/ps数据回调 + * @param buffer ts/ps数据包 + * @param timestamp 时间戳,单位毫秒 + * @param key_pos 是否为关键帧的第一个ts/ps包,用于确保ts切片第一帧为关键帧 + * Callback for outputting ts/ps data + * @param buffer ts/ps data packet + * @param timestamp Timestamp, in milliseconds + * @param key_pos Whether it is the first ts/ps packet of a key frame, used to ensure that the first frame of the ts slice is a key frame + + + * [AUTO-TRANSLATED:dda8ed40] + */ + virtual void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) = 0; + +private: + void createContext(); + void releaseContext(); + void onWrite_l(const void *packet, size_t bytes); + void flushCache(); + +private: + bool _is_ps = false; + bool _have_video = false; + bool _key_pos = false; + uint32_t _max_cache_size = 0; + uint64_t _timestamp = 0; + struct mpeg_muxer_t *_context = nullptr; + + class FrameMergerImp : public FrameMerger { + public: + FrameMergerImp() : FrameMerger(FrameMerger::h264_prefix) {} + }; + + struct MP4Track { + int track_id = -1; + FrameMergerImp merger; + }; + std::unordered_map _tracks; + toolkit::BufferRaw::Ptr _current_buffer; + toolkit::ResourcePool _buffer_pool; +}; + +}//mediakit + +#else + +#include "Common/MediaSink.h" + +namespace mediakit { + +class MpegMuxer : public MediaSinkInterface { +public: + MpegMuxer(bool is_ps = false) {} + bool addTrack(const Track::Ptr &track) override { return false; } + void resetTracks() override {} + bool inputFrame(const Frame::Ptr &frame) override { return false; } + +protected: + virtual void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) = 0; +}; + +}//namespace mediakit + +#endif + +#endif //ZLMEDIAKIT_MPEG_H diff --git a/MediaServer/Record/Recorder.cpp b/MediaServer/Record/Recorder.cpp new file mode 100644 index 0000000..51b9e03 --- /dev/null +++ b/MediaServer/Record/Recorder.cpp @@ -0,0 +1,130 @@ +/* + * 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 "Recorder.h" +#include "Common/config.h" +#include "Util/File.h" +#include "Common/MediaSource.h" +#include "MP4Recorder.h" +#include "HlsRecorder.h" +#include "FMP4/FMP4MediaSourceMuxer.h" +#include "TS/TSMediaSourceMuxer.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, const string &customized_path) { + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + switch (type) { + case Recorder::type_hls: { + GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath); + string m3u8FilePath; + if (enableVhost) { + m3u8FilePath = tuple.shortUrl() + "/hls.m3u8"; + } else { + m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.m3u8"; + } + //Here we use the customized file path. + if (!customized_path.empty()) { + return File::absolutePath(m3u8FilePath, customized_path); + } + return File::absolutePath(m3u8FilePath, hlsPath); + } + case Recorder::type_mp4: { + GET_CONFIG(string, recordPath, Protocol::kMP4SavePath); + GET_CONFIG(string, recordAppName, Record::kAppName); + string mp4FilePath; + if (enableVhost) { + mp4FilePath = tuple.vhost + "/" + recordAppName + "/" + tuple.app + "/" + tuple.stream + "/"; + } else { + mp4FilePath = recordAppName + "/" + tuple.app + "/" + tuple.stream + "/"; + } + //Here we use the customized file path. + if (!customized_path.empty()) { + return File::absolutePath(mp4FilePath, customized_path); + } + return File::absolutePath(mp4FilePath, recordPath); + } + case Recorder::type_hls_fmp4: { + GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath); + string m3u8FilePath; + if (enableVhost) { + m3u8FilePath = tuple.shortUrl() + "/hls.fmp4.m3u8"; + } else { + m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.fmp4.m3u8"; + } + // Here we use the customized file path. + if (!customized_path.empty()) { + return File::absolutePath(m3u8FilePath, customized_path); + } + return File::absolutePath(m3u8FilePath, hlsPath); + } + default: return ""; + } +} + +std::shared_ptr Recorder::createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option){ + switch (type) { + case Recorder::type_hls: { +#if defined(ENABLE_HLS) + auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path); + GET_CONFIG(bool, enable_vhost, General::kEnableVhost); + auto ret = std::make_shared(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option); + ret->setMediaSource(tuple); + return ret; +#else + throw std::invalid_argument("hls相关功能未打开,请开启ENABLE_HLS宏后编译再测试"); +#endif + } + + case Recorder::type_mp4: { +#if defined(ENABLE_MP4) + auto path = Recorder::getRecordPath(type, tuple, option.mp4_save_path); + return std::make_shared(tuple, path, option.mp4_max_second); +#else + throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试"); +#endif + } + + case Recorder::type_hls_fmp4: { +#if defined(ENABLE_MP4) + auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path); + GET_CONFIG(bool, enable_vhost, General::kEnableVhost); + auto ret = std::make_shared(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option); + ret->setMediaSource(tuple); + return ret; +#else + throw std::invalid_argument("hls.fmp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试"); +#endif + } + + case Recorder::type_fmp4: { +#if defined(ENABLE_MP4) + return std::make_shared(tuple, option); +#else + throw std::invalid_argument("fmp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试"); +#endif + } + + case Recorder::type_ts: { +#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY) + return std::make_shared(tuple, option); +#else + throw std::invalid_argument("mpegts相关功能未打开,请开启ENABLE_HLS或ENABLE_RTPPROXY宏后编译再测试"); +#endif + } + + default: throw std::invalid_argument("未知的录制类型"); + } +} + +} /* namespace mediakit */ diff --git a/MediaServer/Record/Recorder.h b/MediaServer/Record/Recorder.h new file mode 100644 index 0000000..1e73453 --- /dev/null +++ b/MediaServer/Record/Recorder.h @@ -0,0 +1,111 @@ +/* + * 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_RECORDER_H_ +#define SRC_MEDIAFILE_RECORDER_H_ + +#include +#include + +namespace mediakit { +class MediaSinkInterface; +class ProtocolOption; + +struct MediaTuple { + std::string vhost; + std::string app; + std::string stream; + std::string params; + std::string shortUrl() const { + return vhost + '/' + app + '/' + stream; + } +}; + +class RecordInfo: public MediaTuple { +public: + time_t start_time; // GMT 标准时间,单位秒 + float time_len; // 录像长度,单位秒 + uint64_t file_size; // 文件大小,单位 BYTE + std::string file_path; // 文件路径 + std::string file_name; // 文件名称 + std::string folder; // 文件夹路径 + std::string url; // 播放路径 +}; + +class Recorder{ +public: + typedef enum { + // 录制hls [AUTO-TRANSLATED:24a50dff] + // Record hls + type_hls = 0, + // 录制MP4 [AUTO-TRANSLATED:03d73bb7] + // Record MP4 + type_mp4 = 1, + // 录制hls.fmp4 [AUTO-TRANSLATED:031cf6f1] + // Record hls.fmp4 + type_hls_fmp4 = 2, + // fmp4直播 [AUTO-TRANSLATED:ac37a248] + // fmp4 live + type_fmp4 = 3, + // ts直播 [AUTO-TRANSLATED:b062b43a] + // ts live + type_ts = 4, + } type; + + /** + * 获取录制文件绝对路径 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + * @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置 + * @return 录制文件绝对路径 + * Get the absolute path of the recording file + * @param type hls or MP4 recording + * @param vhost virtual host + * @param app application name + * @param stream_id stream id + * @param customized_path custom root directory for saving recording files, empty means using configuration file settings + * @return absolute path of the recording file + + * [AUTO-TRANSLATED:2fd57fcd] + */ + static std::string getRecordPath(type type, const MediaTuple& tuple, const std::string &customized_path = ""); + + /** + * 创建录制器对象 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + * @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置 + * @param max_second mp4录制最大切片时间,单位秒,置0则采用配置文件配置 + * @return 对象指针,可能为nullptr + * Create a recorder object + * @param type hls or MP4 recording + * @param vhost virtual host + * @param app application name + * @param stream_id stream id + * @param customized_path custom root directory for saving recording files, empty means using configuration file settings + * @param max_second maximum slice time for mp4 recording, in seconds, 0 means using configuration file settings + * @return object pointer, may be nullptr + + + * [AUTO-TRANSLATED:e0b6e43b] + */ + static std::shared_ptr createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option); + +private: + Recorder() = delete; + ~Recorder() = delete; +}; + +} /* namespace mediakit */ +#endif /* SRC_MEDIAFILE_RECORDER_H_ */ diff --git a/MediaServer/Rtcp/Rtcp.cpp b/MediaServer/Rtcp/Rtcp.cpp new file mode 100644 index 0000000..afaa9be --- /dev/null +++ b/MediaServer/Rtcp/Rtcp.cpp @@ -0,0 +1,813 @@ +/* + * 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 "Rtcp.h" +#include "RtcpFCI.h" +#include "Util/logger.h" +#include +#include + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +const char *rtcpTypeToStr(RtcpType type) { + switch (type) { +#define SWITCH_CASE(key, value) \ + case RtcpType::key: return #value "(" #key ")"; + RTCP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return "unknown rtcp pt"; + } +} + +const char *sdesTypeToStr(SdesType type) { + switch (type) { +#define SWITCH_CASE(key, value) \ + case SdesType::key: return #value "(" #key ")"; + SDES_TYPE_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return "unknown source description type"; + } +} + +const char *psfbTypeToStr(PSFBType type) { + switch (type) { +#define SWITCH_CASE(key, value) \ + case PSFBType::key: return #value "(" #key ")"; + PSFB_TYPE_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return "unknown payload-specific fb message fmt type"; + } +} + +const char *rtpfbTypeToStr(RTPFBType type) { + switch (type) { +#define SWITCH_CASE(key, value) \ + case RTPFBType::key: return #value "(" #key ")"; + RTPFB_TYPE_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return "unknown transport layer feedback messages fmt type"; + } +} + +static size_t alignSize(size_t bytes) { + return (size_t)((bytes + 3) >> 2) << 2; +} + +static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, size_t total_bytes) { + rtcp->version = 2; + rtcp->padding = 0; + if (report_count > 0x1F) { + throw std::invalid_argument(StrPrinter << "rtcp report_count最大赋值为31,当前为:" << report_count); + } + // items总个数 [AUTO-TRANSLATED:2d40d010] + // Total number of items + rtcp->report_count = report_count; + rtcp->pt = (uint8_t)type; + rtcp->setSize(total_bytes); +} + +static void setupPadding(RtcpHeader *rtcp, size_t padding_size) { + if (padding_size) { + rtcp->padding = 1; + ((uint8_t *)rtcp)[rtcp->getSize() - 1] = padding_size & 0xFF; + } else { + rtcp->padding = 0; + } +} + +///////////////////////////////////////////////////////////////////////////// + +string RtcpHeader::dumpHeader() const { + _StrPrinter printer; + printer << "version:" << version << "\r\n"; + if (padding) { + printer << "padding:" << padding << " " << getPaddingSize() << "\r\n"; + } else { + printer << "padding:" << padding << "\r\n"; + } + + switch ((RtcpType)pt) { + case RtcpType::RTCP_RTPFB: { + printer << "report_count:" << rtpfbTypeToStr((RTPFBType)report_count) << "\r\n"; + break; + } + case RtcpType::RTCP_PSFB: { + printer << "report_count:" << psfbTypeToStr((PSFBType)report_count) << "\r\n"; + break; + } + default: { + printer << "report_count:" << report_count << "\r\n"; + break; + } + } + + printer << "pt:" << rtcpTypeToStr((RtcpType)pt) << "\r\n"; + printer << "size:" << getSize() << "\r\n"; + printer << "--------\r\n"; + return std::move(printer); +} + +string RtcpHeader::dumpString() const { + switch ((RtcpType)pt) { + case RtcpType::RTCP_SR: { + RtcpSR *rtcp = (RtcpSR *)this; + return rtcp->dumpString(); + } + + case RtcpType::RTCP_RR: { + RtcpRR *rtcp = (RtcpRR *)this; + return rtcp->dumpString(); + } + + case RtcpType::RTCP_SDES: { + RtcpSdes *rtcp = (RtcpSdes *)this; + return rtcp->dumpString(); + } + + case RtcpType::RTCP_RTPFB: + case RtcpType::RTCP_PSFB: { + RtcpFB *rtcp = (RtcpFB *)this; + return rtcp->dumpString(); + } + + case RtcpType::RTCP_BYE: { + RtcpBye *rtcp = (RtcpBye *)this; + return rtcp->dumpString(); + } + + default: return StrPrinter << dumpHeader() << hexdump((char *)this + sizeof(*this), getSize() - sizeof(*this)); + } +} + +size_t RtcpHeader::getSize() const { + // 加上rtcp头长度 [AUTO-TRANSLATED:21a40b4b] + // Add rtcp header length + return (1 + ntohs(length)) << 2; +} + +size_t RtcpHeader::getPaddingSize() const { + if (!padding) { + return 0; + } + return ((uint8_t *)this)[getSize() - 1]; +} + +void RtcpHeader::setSize(size_t size) { + // 不包含rtcp头的长度 [AUTO-TRANSLATED:b26ad8ef] + // Length excluding rtcp header + length = htons((uint16_t)((size >> 2) - 1)); +} + +void RtcpHeader::net2Host(size_t len) { + switch ((RtcpType)pt) { + case RtcpType::RTCP_SR: { + RtcpSR *sr = (RtcpSR *)this; + sr->net2Host(len); + break; + } + + case RtcpType::RTCP_RR: { + RtcpRR *rr = (RtcpRR *)this; + rr->net2Host(len); + break; + } + + case RtcpType::RTCP_SDES: { + RtcpSdes *sdes = (RtcpSdes *)this; + sdes->net2Host(len); + break; + } + + case RtcpType::RTCP_RTPFB: + case RtcpType::RTCP_PSFB: { + RtcpFB *fb = (RtcpFB *)this; + fb->net2Host(len); + break; + } + + case RtcpType::RTCP_BYE: { + RtcpBye *bye = (RtcpBye *)this; + bye->net2Host(len); + break; + } + case RtcpType::RTCP_XR: { + RtcpXRRRTR *xr = (RtcpXRRRTR *)this; + if (xr->bt == 4) { + xr->net2Host(len); + // TraceL<dumpString(); + } else if (xr->bt == 5) { + RtcpXRDLRR *dlrr = (RtcpXRDLRR *)this; + dlrr->net2Host(len); + TraceL << dlrr->dumpString(); + } else { + throw std::runtime_error(StrPrinter << "rtcp xr bt " << xr->bt << " not support"); + } + break; + } + default: throw std::runtime_error(StrPrinter << "未处理的rtcp包:" << rtcpTypeToStr((RtcpType)this->pt)); + } +} + +vector RtcpHeader::loadFromBytes(char *data, size_t len) { + vector ret; + ssize_t remain = len; + char *ptr = data; + while (remain > (ssize_t)sizeof(RtcpHeader)) { + RtcpHeader *rtcp = (RtcpHeader *)ptr; + auto rtcp_len = rtcp->getSize(); + if (remain < (ssize_t)rtcp_len) { + WarnL << "非法的rtcp包,声明的长度超过实际数据长度"; + break; + } + try { + rtcp->net2Host(rtcp_len); + ret.emplace_back(rtcp); + } catch (std::exception &ex) { + // 不能处理的rtcp包,或者无法解析的rtcp包,忽略掉 [AUTO-TRANSLATED:752ec400] + // Ignore unprocessable rtcp packets or rtcp packets that cannot be parsed + WarnL << ex.what() << ",长度为:" << rtcp_len; + } + ptr += rtcp_len; + remain -= rtcp_len; + } + return ret; +} + +class BufferRtcp : public Buffer { +public: + BufferRtcp(std::shared_ptr rtcp) { _rtcp = std::move(rtcp); } + char *data() const override { return (char *)_rtcp.get(); } + size_t size() const override { return _rtcp->getSize(); } + +private: + std::shared_ptr _rtcp; +}; + +Buffer::Ptr RtcpHeader::toBuffer(std::shared_ptr rtcp) { + return std::make_shared(std::move(rtcp)); +} + +///////////////////////////////////////////////////////////////////////////// + +std::shared_ptr RtcpSR::create(size_t item_count) { + auto real_size = sizeof(RtcpSR) - sizeof(ReportItem) + item_count * sizeof(ReportItem); + auto bytes = alignSize(real_size); + auto ptr = (RtcpSR *)new char[bytes]; + setupHeader(ptr, RtcpType::RTCP_SR, item_count, bytes); + setupPadding(ptr, bytes - real_size); + return std::shared_ptr(ptr, [](RtcpSR *ptr) { delete[](char *) ptr; }); +} + +string RtcpSR::getNtpStamp() const { + struct timeval tv; + tv.tv_sec = ntpmsw - 0x83AA7E80; + tv.tv_usec = (decltype(tv.tv_usec))(ntplsw / ((double)(((uint64_t)1) << 32) * 1.0e-6)); + return LogChannel::printTime(tv); +} + +uint64_t RtcpSR::getNtpUnixStampMS() const { + if (ntpmsw < 0x83AA7E80) { + // ntp时间戳起始时间为1900年,但是utc时间戳起始时间为1970年,两者相差0x83AA7E80秒 [AUTO-TRANSLATED:6b3ac2fa] + // The ntp timestamp starts from 1900, but the utc timestamp starts from 1970, with a difference of 0x83AA7E80 seconds + // ntp时间戳不得早于1970年,否则无法转换为utc时间戳 [AUTO-TRANSLATED:d70fc88c] + // The ntp timestamp must not be earlier than 1970, otherwise it cannot be converted to utc timestamp + return 0; + } + struct timeval tv; + tv.tv_sec = ntpmsw - 0x83AA7E80; + tv.tv_usec = (decltype(tv.tv_usec))(ntplsw / ((double)(((uint64_t)1) << 32) * 1.0e-6)); + return (uint64_t)1000 * tv.tv_sec + tv.tv_usec / 1000; +} + +void RtcpSR::setNtpStamp(struct timeval tv) { + ntpmsw = htonl(tv.tv_sec + 0x83AA7E80); /* 0x83AA7E80 is the number of seconds from 1900 to 1970 */ + ntplsw = htonl((uint32_t)((double)tv.tv_usec * (double)(((uint64_t)1) << 32) * 1.0e-6)); +} + +void RtcpSR::setNtpStamp(uint64_t unix_stamp_ms) { + struct timeval tv; + tv.tv_sec = unix_stamp_ms / 1000; + tv.tv_usec = (unix_stamp_ms % 1000) * 1000; + setNtpStamp(tv); +} + +string RtcpSR::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + printer << "ssrc:" << ssrc << "\r\n"; + printer << "ntpmsw:" << ntpmsw << "\r\n"; + printer << "ntplsw:" << ntplsw << "\r\n"; + printer << "ntp time:" << getNtpStamp() << "\r\n"; + printer << "rtpts:" << rtpts << "\r\n"; + printer << "packet_count:" << packet_count << "\r\n"; + printer << "octet_count:" << octet_count << "\r\n"; + auto items = ((RtcpSR *)this)->getItemList(); + auto i = 0; + for (auto &item : items) { + printer << "---- item:" << i++ << " ----\r\n"; + printer << item->dumpString(); + } + return std::move(printer); +} + +#define CHECK_MIN_SIZE(size, kMinSize) \ + if (size < kMinSize) { \ + throw std::out_of_range( \ + StrPrinter << rtcpTypeToStr((RtcpType)pt) << " 长度不足:" << size << " < " << kMinSize); \ + } + +#define CHECK_REPORT_COUNT(item_count) \ + /*修正个数,防止getItemList时内存越界 + /*Correct the number to prevent memory overflow when getItemList + * [AUTO-TRANSLATED:852bd70e] + */ \ + if (report_count != item_count) { \ + WarnL << rtcpTypeToStr((RtcpType)pt) << " report_count 字段不正确,已修正为:" << (int)report_count << " -> " \ + << item_count; \ + report_count = item_count; \ + } + +void RtcpSR::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpSR) - sizeof(items); + CHECK_MIN_SIZE(size, kMinSize); + + ssrc = ntohl(ssrc); + ntpmsw = ntohl(ntpmsw); + ntplsw = ntohl(ntplsw); + rtpts = ntohl(rtpts); + packet_count = ntohl(packet_count); + octet_count = ntohl(octet_count); + + ReportItem *ptr = &items; + int item_count = 0; + for (int i = 0; i < (int)report_count && (char *)(ptr) + sizeof(ReportItem) <= (char *)(this) + size; ++i) { + ptr->net2Host(); + ++ptr; + ++item_count; + } + CHECK_REPORT_COUNT(item_count); +} + +vector RtcpSR::getItemList() { + vector ret; + ReportItem *ptr = &items; + for (int i = 0; i < (int)report_count; ++i) { + ret.emplace_back(ptr); + ++ptr; + } + return ret; +} + +///////////////////////////////////////////////////////////////////////////// + +string ReportItem::dumpString() const { + _StrPrinter printer; + printer << "ssrc:" << ssrc << "\r\n"; + printer << "fraction:" << fraction << "\r\n"; + printer << "cumulative:" << cumulative << "\r\n"; + printer << "seq_cycles:" << seq_cycles << "\r\n"; + printer << "seq_max:" << seq_max << "\r\n"; + printer << "jitter:" << jitter << "\r\n"; + printer << "last_sr_stamp:" << last_sr_stamp << "\r\n"; + printer << "delay_since_last_sr:" << delay_since_last_sr << "\r\n"; + return std::move(printer); +} + +void ReportItem::net2Host() { + ssrc = ntohl(ssrc); + cumulative = ntohl(cumulative) >> 8; + seq_cycles = ntohs(seq_cycles); + seq_max = ntohs(seq_max); + jitter = ntohl(jitter); + last_sr_stamp = ntohl(last_sr_stamp); + delay_since_last_sr = ntohl(delay_since_last_sr); +} + +///////////////////////////////////////////////////////////////////////////// + +std::shared_ptr RtcpRR::create(size_t item_count) { + auto real_size = sizeof(RtcpRR) - sizeof(ReportItem) + item_count * sizeof(ReportItem); + auto bytes = alignSize(real_size); + auto ptr = (RtcpRR *)new char[bytes]; + setupHeader(ptr, RtcpType::RTCP_RR, item_count, bytes); + setupPadding(ptr, bytes - real_size); + return std::shared_ptr(ptr, [](RtcpRR *ptr) { delete[](char *) ptr; }); +} + +string RtcpRR::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + printer << "ssrc:" << ssrc << "\r\n"; + auto items = ((RtcpRR *)this)->getItemList(); + auto i = 0; + for (auto &item : items) { + printer << "---- item:" << i++ << " ----\r\n"; + printer << item->dumpString(); + } + return std::move(printer); +} + +void RtcpRR::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpRR) - sizeof(items); + CHECK_MIN_SIZE(size, kMinSize); + ssrc = ntohl(ssrc); + + ReportItem *ptr = &items; + int item_count = 0; + for (int i = 0; i < (int)report_count && (char *)(ptr) + sizeof(ReportItem) <= (char *)(this) + size; ++i) { + ptr->net2Host(); + ++ptr; + ++item_count; + } + CHECK_REPORT_COUNT(item_count); +} + +vector RtcpRR::getItemList() { + vector ret; + ReportItem *ptr = &items; + for (int i = 0; i < (int)report_count; ++i) { + ret.emplace_back(ptr); + ++ptr; + } + return ret; +} + +///////////////////////////////////////////////////////////////////////////// + +void SdesChunk::net2Host() { + ssrc = ntohl(ssrc); +} + +size_t SdesChunk::totalBytes() const { + return alignSize(minSize() + txt_len); +} + +size_t SdesChunk::minSize() { + return sizeof(SdesChunk) - sizeof(text); +} + +string SdesChunk::dumpString() const { + _StrPrinter printer; + printer << "ssrc:" << ssrc << "\r\n"; + printer << "type:" << sdesTypeToStr((SdesType)type) << "\r\n"; + printer << "txt_len:" << (int)txt_len << "\r\n"; + printer << "text:" << (txt_len ? string(text, txt_len) : "") << "\r\n"; + return std::move(printer); +} + +///////////////////////////////////////////////////////////////////////////// + +std::shared_ptr RtcpSdes::create(const std::vector &item_text) { + size_t item_total_size = 0; + for (auto &text : item_text) { + // 统计所有SdesChunk对象占用的空间 [AUTO-TRANSLATED:87871205] + // Count the space occupied by all SdesChunk objects + item_total_size += alignSize(SdesChunk::minSize() + (0xFF & text.size())); + } + auto real_size = sizeof(RtcpSdes) - sizeof(SdesChunk) + item_total_size; + auto bytes = alignSize(real_size); + auto ptr = (RtcpSdes *)new char[bytes]; + memset(ptr, 0x00, bytes); + auto item_ptr = &ptr->chunks; + for (auto &text : item_text) { + item_ptr->txt_len = (0xFF & text.size()); + // 确保赋值\0为RTCP_SDES_END [AUTO-TRANSLATED:316be0a3] + // Ensure that the assignment \0 is RTCP_SDES_END + memcpy(item_ptr->text, text.data(), item_ptr->txt_len + 1); + item_ptr = (SdesChunk *)((char *)item_ptr + item_ptr->totalBytes()); + } + + setupHeader(ptr, RtcpType::RTCP_SDES, item_text.size(), bytes); + setupPadding(ptr, bytes - real_size); + return std::shared_ptr(ptr, [](RtcpSdes *ptr) { delete[](char *) ptr; }); +} + +string RtcpSdes::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + auto items = ((RtcpSdes *)this)->getChunkList(); + auto i = 0; + for (auto &item : items) { + printer << "---- item:" << i++ << " ----\r\n"; + printer << item->dumpString(); + } + return std::move(printer); +} + +void RtcpSdes::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpSdes) - sizeof(chunks); + CHECK_MIN_SIZE(size, kMinSize); + SdesChunk *ptr = &chunks; + int item_count = 0; + for (int i = 0; i < (int)report_count && (char *)(ptr) + SdesChunk::minSize() <= (char *)(this) + size; ++i) { + ptr->net2Host(); + ptr = (SdesChunk *)((char *)ptr + ptr->totalBytes()); + ++item_count; + } + CHECK_REPORT_COUNT(item_count); +} + +vector RtcpSdes::getChunkList() { + vector ret; + SdesChunk *ptr = &chunks; + for (int i = 0; i < (int)report_count; ++i) { + ret.emplace_back(ptr); + ptr = (SdesChunk *)((char *)ptr + ptr->totalBytes()); + } + return ret; +} + +//////////////////////////////////////////////////////////////////// + +std::shared_ptr RtcpFB::create_l(RtcpType type, int fmt, const void *fci, size_t fci_len) { + if (!fci) { + fci_len = 0; + } + auto real_size = sizeof(RtcpFB) + fci_len; + auto bytes = alignSize(real_size); + auto ptr = (RtcpFB *)new char[bytes]; + if (fci && fci_len) { + memcpy((char *)ptr + sizeof(RtcpFB), fci, fci_len); + } + setupHeader(ptr, type, fmt, bytes); + setupPadding(ptr, bytes - real_size); + return std::shared_ptr((RtcpFB *)ptr, [](RtcpFB *ptr) { delete[](char *) ptr; }); +} + +std::shared_ptr RtcpFB::create(PSFBType fmt, const void *fci, size_t fci_len) { + return RtcpFB::create_l(RtcpType::RTCP_PSFB, (int)fmt, fci, fci_len); +} + +std::shared_ptr RtcpFB::create(RTPFBType fmt, const void *fci, size_t fci_len) { + return RtcpFB::create_l(RtcpType::RTCP_RTPFB, (int)fmt, fci, fci_len); +} + +const void *RtcpFB::getFciPtr() const { + return (uint8_t *)&ssrc_media + sizeof(ssrc_media); +} + +size_t RtcpFB::getFciSize() const { + auto fci_len = (ssize_t)getSize() - getPaddingSize() - sizeof(RtcpFB); + CHECK(getSize() >= getPaddingSize() + sizeof(RtcpFB)); + return fci_len; +} + +string RtcpFB::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + printer << "ssrc:" << ssrc << "\r\n"; + printer << "ssrc_media:" << ssrc_media << "\r\n"; + switch ((RtcpType)pt) { + case RtcpType::RTCP_PSFB: { + switch ((PSFBType)report_count) { + case PSFBType::RTCP_PSFB_SLI: { + auto &fci = getFci(); + printer << "fci:" << psfbTypeToStr((PSFBType)report_count) << " " << fci.dumpString(); + break; + } + case PSFBType::RTCP_PSFB_PLI: { + getFciSize(); + printer << "fci:" << psfbTypeToStr((PSFBType)report_count); + break; + } + + case PSFBType::RTCP_PSFB_FIR: { + auto &fci = getFci(); + printer << "fci:" << psfbTypeToStr((PSFBType)report_count) << " " << fci.dumpString(); + break; + } + + case PSFBType::RTCP_PSFB_REMB: { + auto &fci = getFci(); + printer << "fci:" << psfbTypeToStr((PSFBType)report_count) << " " << fci.dumpString(); + break; + } + default: { + printer << "fci:" << psfbTypeToStr((PSFBType)report_count) << " " + << hexdump(getFciPtr(), getFciSize()); + break; + } + } + break; + } + case RtcpType::RTCP_RTPFB: { + switch ((RTPFBType)report_count) { + case RTPFBType::RTCP_RTPFB_NACK: { + auto &fci = getFci(); + printer << "fci:" << rtpfbTypeToStr((RTPFBType)report_count) << " " << fci.dumpString(); + break; + } + case RTPFBType::RTCP_RTPFB_TWCC: { + auto &fci = getFci(); + printer << "fci:" << rtpfbTypeToStr((RTPFBType)report_count) << " " << fci.dumpString(getFciSize()); + break; + } + default: { + printer << "fci:" << rtpfbTypeToStr((RTPFBType)report_count) << " " + << hexdump(getFciPtr(), getFciSize()); + break; + } + } + break; + } + default: /*不可达*/ assert(0); break; + } + return std::move(printer); +} + +void RtcpFB::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpFB); + CHECK_MIN_SIZE(size, kMinSize); + ssrc = ntohl(ssrc); + ssrc_media = ntohl(ssrc_media); +} + +//////////////////////////////////////////////////////////////////// + +std::shared_ptr RtcpBye::create(const std::vector &ssrcs, const string &reason) { + assert(reason.size() <= 0xFF); + auto real_size = sizeof(RtcpHeader) + sizeof(uint32_t) * ssrcs.size() + 1 + reason.size(); + auto bytes = alignSize(real_size); + auto ptr = (RtcpBye *)new char[bytes]; + setupHeader(ptr, RtcpType::RTCP_BYE, ssrcs.size(), bytes); + setupPadding(ptr, bytes - real_size); + + int i = 0; + for (auto ssrc : ssrcs) { + ((RtcpBye *)ptr)->ssrc[i++] = htonl(ssrc); + } + + if (!reason.empty()) { + uint8_t *reason_len_ptr = (uint8_t *)ptr + sizeof(RtcpHeader) + sizeof(uint32_t) * ssrcs.size(); + *reason_len_ptr = reason.size() & 0xFF; + memcpy(reason_len_ptr + 1, reason.data(), *reason_len_ptr); + } + + return std::shared_ptr(ptr, [](RtcpBye *ptr) { delete[](char *) ptr; }); +} + +vector RtcpBye::getSSRC() { + vector ret; + for (size_t i = 0; i < report_count; ++i) { + ret.emplace_back(&(ssrc[i])); + } + return ret; +} + +string RtcpBye::getReason() const { + auto *reason_len_ptr = &reason_len + sizeof(ssrc) * (report_count - 1); + if (reason_len_ptr + 1 >= (uint8_t *)this + getSize()) { + return ""; + } + return string((char *)reason_len_ptr + 1, *reason_len_ptr); +} + +string RtcpBye::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + for (auto ssrc : ((RtcpBye *)this)->getSSRC()) { + printer << "ssrc:" << *ssrc << "\r\n"; + } + printer << "reason:" << getReason(); + return std::move(printer); +} + +void RtcpBye::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpHeader); + CHECK_MIN_SIZE(size, kMinSize); + size_t offset = kMinSize; + size_t i = 0; + for (; i < report_count && offset + sizeof(ssrc) <= size; ++i) { + ssrc[i] = ntohl(ssrc[i]); + offset += sizeof(ssrc); + } + // 修正ssrc个数 [AUTO-TRANSLATED:57c74f58] + // Correct the number of ssrcs + CHECK_REPORT_COUNT(i); + + if (offset < size) { + uint8_t *reason_len_ptr = &reason_len + sizeof(ssrc) * (report_count - 1); + if (reason_len_ptr + 1 + *reason_len_ptr > (uint8_t *)this + size) { + WarnL << "invalid rtcp bye reason length"; + // 修正reason_len长度 [AUTO-TRANSLATED:1c0c9645] + // Correct the length of reason_len + *reason_len_ptr = ((uint8_t *)this + size - reason_len_ptr - 1) & 0xFF; + } + } +} +//////////////////////////////////////////// +string RtcpXRRRTR::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + printer << "ssrc :" << ssrc << "\r\n"; + printer << "bt :" << (int)bt << "\r\n"; + printer << "block_length : " << block_length << "\r\n"; + printer << "ntp msw : " << ntpmsw << "\r\n"; + printer << "ntp lsw : " << ntplsw << "\r\n"; + return std::move(printer); +} + +void RtcpXRRRTR::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpHeader); + CHECK_MIN_SIZE(size, kMinSize); + if (size != sizeof(RtcpXRRRTR)) { + throw std::invalid_argument( + StrPrinter << "rtcp xr Receiver Reference Time Report Block must is " << sizeof(RtcpXRRRTR) + << " actual size " << size); + } + ssrc = ntohl(ssrc); + block_length = ntohs(block_length); + ntpmsw = ntohl(ntpmsw); + ntplsw = ntohl(ntplsw); +} + +string RtcpXRDLRRReportItem::dumpString() const { + _StrPrinter printer; + + printer << "ssrc :" << ssrc << "\r\n"; + printer << "last RR (lrr) :" << lrr << "\r\n"; + printer << "delay since last RR (dlrr): " << dlrr << "\r\n"; + + return std::move(printer); +} + +void RtcpXRDLRRReportItem::net2Host() { + ssrc = ntohl(ssrc); + lrr = ntohl(lrr); + dlrr = ntohl(dlrr); +} + +std::vector RtcpXRDLRR::getItemList() { + auto count = block_length / 3; + RtcpXRDLRRReportItem *ptr = &items; + vector ret; + for (int i = 0; i < (int)count; ++i) { + ret.emplace_back(ptr); + ++ptr; + } + return ret; +} +string RtcpXRDLRR::dumpString() const { + _StrPrinter printer; + printer << RtcpHeader::dumpHeader(); + printer << "ssrc :" << ssrc << "\r\n"; + printer << "bt :" << (int)bt << "\r\n"; + printer << "block_length : " << block_length << "\r\n"; + auto items_list = ((RtcpXRDLRR *)this)->getItemList(); + auto i = 0; + for (auto &item : items_list) { + printer << "---- item:" << i++ << " ----\r\n"; + printer << item->dumpString(); + } + return std::move(printer); +} + +void RtcpXRDLRR::net2Host(size_t size) { + static const size_t kMinSize = sizeof(RtcpHeader); + CHECK_MIN_SIZE(size, kMinSize); + + ssrc = ntohl(ssrc); + block_length = ntohs(block_length); + + auto count = block_length / 3; + for (int i = 0; i < (int)count; ++i) { + RtcpXRDLRRReportItem *ptr = &items; + ptr->net2Host(); + ptr++; + } +} + +std::shared_ptr RtcpXRDLRR::create(size_t item_count) { + auto real_size = sizeof(RtcpXRDLRR) - sizeof(RtcpXRDLRRReportItem) + item_count * sizeof(RtcpXRDLRRReportItem); + auto bytes = alignSize(real_size); + auto ptr = (RtcpXRDLRR *)new char[bytes]; + setupHeader(ptr, RtcpType::RTCP_XR, 0, bytes); + setupPadding(ptr, bytes - real_size); + return std::shared_ptr(ptr, [](RtcpXRDLRR *ptr) { delete[](char *) ptr; }); +} + +#if 0 +#include "Util/onceToken.h" + +static toolkit::onceToken token([](){ + auto bye = RtcpBye::create({1,2,3,4,5,6}, "this is a bye reason"); + auto buffer = RtcpHeader::toBuffer(bye); + + auto rtcps = RtcpHeader::loadFromBytes(buffer->data(), buffer->size()); + for(auto rtcp : rtcps){ + std::cout << rtcp->dumpString() << std::endl; + } +}); +#endif + +} // namespace mediakit \ No newline at end of file diff --git a/MediaServer/Rtcp/Rtcp.h b/MediaServer/Rtcp/Rtcp.h new file mode 100644 index 0000000..53e6340 --- /dev/null +++ b/MediaServer/Rtcp/Rtcp.h @@ -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_RTCP_H +#define ZLMEDIAKIT_RTCP_H + +#include "Common/macros.h" +#include "Network/Buffer.h" +#include "Util/util.h" +#include +#include + +namespace mediakit { + +#pragma pack(push, 1) + +// http://www.networksorcery.com/enp/protocol/rtcp.htm +#define RTCP_PT_MAP(XX) \ + XX(RTCP_FIR, 192) \ + XX(RTCP_NACK, 193) \ + XX(RTCP_SMPTETC, 194) \ + XX(RTCP_IJ, 195) \ + XX(RTCP_SR, 200) \ + XX(RTCP_RR, 201) \ + XX(RTCP_SDES, 202) \ + XX(RTCP_BYE, 203) \ + XX(RTCP_APP, 204) \ + XX(RTCP_RTPFB, 205) \ + XX(RTCP_PSFB, 206) \ + XX(RTCP_XR, 207) \ + XX(RTCP_AVB, 208) \ + XX(RTCP_RSI, 209) \ + XX(RTCP_TOKEN, 210) + +// https://tools.ietf.org/html/rfc3550#section-6.5 +#define SDES_TYPE_MAP(XX) \ + XX(RTCP_SDES_END, 0) \ + XX(RTCP_SDES_CNAME, 1) \ + XX(RTCP_SDES_NAME, 2) \ + XX(RTCP_SDES_EMAIL, 3) \ + XX(RTCP_SDES_PHONE, 4) \ + XX(RTCP_SDES_LOC, 5) \ + XX(RTCP_SDES_TOOL, 6) \ + XX(RTCP_SDES_NOTE, 7) \ + XX(RTCP_SDES_PRIVATE, 8) + +// https://datatracker.ietf.org/doc/rfc4585/?include_text=1 +// 6.3. Payload-Specific Feedback Messages +// +// Payload-Specific FB messages are identified by the value PT=PSFB as +// RTCP message type. +// +// Three payload-specific FB messages are defined so far plus an +// application layer FB message. They are identified by means of the +// FMT parameter as follows: +// +// 0: unassigned +// 1: Picture Loss Indication (PLI) +// 2: Slice Loss Indication (SLI) +// 3: Reference Picture Selection Indication (RPSI) +// 4: FIR https://tools.ietf.org/html/rfc5104#section-4.3.1.1 +// 5: TSTR https://tools.ietf.org/html/rfc5104#section-4.3.2.1 +// 6: TSTN https://tools.ietf.org/html/rfc5104#section-4.3.2.1 +// 7: VBCM https://tools.ietf.org/html/rfc5104#section-4.3.4.1 +// 8-14: unassigned +// 15: REMB / Application layer FB (AFB) message, https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 +// 16-30: unassigned +// 31: reserved for future expansion of the sequence number space +#define PSFB_TYPE_MAP(XX) \ + XX(RTCP_PSFB_PLI, 1) \ + XX(RTCP_PSFB_SLI, 2) \ + XX(RTCP_PSFB_RPSI, 3) \ + XX(RTCP_PSFB_FIR, 4) \ + XX(RTCP_PSFB_TSTR, 5) \ + XX(RTCP_PSFB_TSTN, 6) \ + XX(RTCP_PSFB_VBCM, 7) \ + XX(RTCP_PSFB_REMB, 15) + +// https://tools.ietf.org/html/rfc4585#section-6.2 +// 6.2. Transport Layer Feedback Messages +// +// Transport layer FB messages are identified by the value RTPFB as RTCP +// message type. +// +// A single general purpose transport layer FB message is defined in +// this document: Generic NACK. It is identified by means of the FMT +// parameter as follows: +// +// 0: unassigned +// 1: Generic NACK +// 2: reserved https://tools.ietf.org/html/rfc5104#section-4.2 +// 3: TMMBR https://tools.ietf.org/html/rfc5104#section-4.2.1.1 +// 4: TMMBN https://tools.ietf.org/html/rfc5104#section-4.2.2.1 +// 5-14: unassigned +// 15 transport-cc https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 +// 16-30: unassigned +// 31: reserved for future expansion of the identifier number space +#define RTPFB_TYPE_MAP(XX) \ + XX(RTCP_RTPFB_NACK, 1) \ + XX(RTCP_RTPFB_TMMBR, 3) \ + XX(RTCP_RTPFB_TMMBN, 4) \ + XX(RTCP_RTPFB_TWCC, 15) + +// rtcp类型枚举 +enum class RtcpType : uint8_t { +#define XX(key, value) key = value, + RTCP_PT_MAP(XX) +#undef XX +}; + +// sdes类型枚举 +enum class SdesType : uint8_t { +#define XX(key, value) key = value, + SDES_TYPE_MAP(XX) +#undef XX +}; + +// psfb类型枚举 +enum class PSFBType : uint8_t { +#define XX(key, value) key = value, + PSFB_TYPE_MAP(XX) +#undef XX +}; + +// rtpfb类型枚举 +enum class RTPFBType : uint8_t { +#define XX(key, value) key = value, + RTPFB_TYPE_MAP(XX) +#undef XX +}; + +/** + * RtcpType转描述字符串 + */ +const char *rtcpTypeToStr(RtcpType type); + +/** + * SdesType枚举转描述字符串 + */ +const char *sdesTypeToStr(SdesType type); + +/** + * psfb枚举转描述字符串 + */ +const char *psfbTypeToStr(PSFBType type); + +/** + * rtpfb枚举转描述字符串 + */ +const char *rtpfbTypeToStr(RTPFBType type); + +class RtcpHeader { +public: +#if __BYTE_ORDER == __BIG_ENDIAN + // 版本号,固定为2 + uint32_t version : 2; + // padding,固定为0 + uint32_t padding : 1; + // reception report count + uint32_t report_count : 5; +#else + // reception report count + uint32_t report_count : 5; + // padding,末尾是否有追加填充 + uint32_t padding : 1; + // 版本号,固定为2 + uint32_t version : 2; +#endif + // rtcp类型,RtcpType + uint32_t pt : 8; + +private: + // 长度 + uint32_t length : 16; + +public: + /** + * 解析rtcp并转换网络字节序为主机字节序,返回RtcpHeader派生类列表 + * @param data 数据指针 + * @param size 数据总长度 + * @return rtcp对象列表,无需free + */ + static std::vector loadFromBytes(char *data, size_t size); + + /** + * rtcp包转Buffer对象 + * @param rtcp rtcp包对象智能指针 + * @return Buffer对象 + */ + static toolkit::Buffer::Ptr toBuffer(std::shared_ptr rtcp); + + /** + * 打印rtcp相关字段详情(调用派生类的dumpString函数) + * 内部会判断是什么类型的派生类 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 根据length字段获取rtcp总长度 + */ + size_t getSize() const; + + /** + * 后面追加padding数据长度 + */ + size_t getPaddingSize() const; + + /** + * 设置rtcp length字段 + * @param size rtcp总长度,单位字节 + */ + void setSize(size_t size); + +protected: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpHeader() const; + +private: + /** + * 调用派生类的net2Host函数 + * @param size rtcp字符长度 + */ + void net2Host(size_t size); + +}; + +///////////////////////////////////////////////////////////////////////////// + +// ReportBlock +class ReportItem { +public: + friend class RtcpSR; + friend class RtcpRR; + + uint32_t ssrc; + // Fraction lost + uint32_t fraction : 8; + // Cumulative number of packets lost + uint32_t cumulative : 24; + // Sequence number cycles count + uint16_t seq_cycles; + // Highest sequence number received + uint16_t seq_max; + // Interarrival jitter + uint32_t jitter; + // Last SR timestamp, NTP timestamp,(ntpmsw & 0xFFFF) << 16 | (ntplsw >> 16) & 0xFFFF) + uint32_t last_sr_stamp; + // Delay since last SR timestamp,expressed in units of 1/65536 seconds + uint32_t delay_since_last_sr; + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + */ + void net2Host(); +}; + +/* + * 6.4.1 SR: Sender Report RTCP Packet + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=SR=200 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +sender | NTP timestamp, most significant word | +info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RTP timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's packet count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's octet count | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +// sender report +class RtcpSR : public RtcpHeader { +public: + friend class RtcpHeader; + uint32_t ssrc; + // ntp timestamp MSW(in second) + uint32_t ntpmsw; + // ntp timestamp LSW(in picosecond) + uint32_t ntplsw; + // rtp timestamp + uint32_t rtpts; + // sender packet count + uint32_t packet_count; + // sender octet count + uint32_t octet_count; + // 可能有很多个 + ReportItem items; + +public: + /** + * 创建SR包,只赋值了RtcpHeader部分(网络字节序) + * @param item_count ReportItem对象个数 + * @return SR包 + */ + static std::shared_ptr create(size_t item_count); + + /** + * 设置ntpmsw与ntplsw字段为网络字节序 + * @param tv 时间 + */ + void setNtpStamp(struct timeval tv); + void setNtpStamp(uint64_t unix_stamp_ms); + + /** + * 返回ntp时间的字符串 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string getNtpStamp() const; + uint64_t getNtpUnixStampMS() const; + + /** + * 获取ReportItem对象指针列表 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::vector getItemList(); + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); +}; + +///////////////////////////////////////////////////////////////////////////// + +/* + * 6.4.2 RR: Receiver Report RTCP Packet + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=RR=201 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +// Receiver Report +class RtcpRR : public RtcpHeader { +public: + friend class RtcpHeader; + + uint32_t ssrc; + // 可能有很多个 + ReportItem items; + +public: + /** + * 创建RR包,只赋值了RtcpHeader部分 + * @param item_count ReportItem对象个数 + * @return RR包 + */ + static std::shared_ptr create(size_t item_count); + + /** + * 获取ReportItem对象指针列表 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::vector getItemList(); + +private: + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); + + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + +}; + +///////////////////////////////////////////////////////////////////////////// + +/* + * 6.5 SDES: Source Description RTCP Packet + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| SC | PT=SDES=202 | length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +chunk | SSRC/CSRC_1 | + 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SDES items | + | ... | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +chunk | SSRC/CSRC_2 | + 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SDES items | + | ... | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + +/* + +SDES items 定义 +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 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SdesType | length | user and domain name ... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// Source description Chunk +class SdesChunk { +public: + friend class RtcpSdes; + + uint32_t ssrc; + // SdesType + uint8_t type; + // text长度股,可以为0 + uint8_t txt_len; + // 不定长 + char text[1]; + // 最后以RTCP_SDES_END结尾 + // 只字段为占位字段,不代表真实位置 + uint8_t end; + +public: + /** + * 返回改对象字节长度 + */ + size_t totalBytes() const; + + /** + * 本对象最少长度 + */ + static size_t minSize(); + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + */ + void net2Host(); +}; + +// Source description +class RtcpSdes : public RtcpHeader { +public: + friend class RtcpHeader; + + // 可能有很多个 + SdesChunk chunks; + +public: + /** + * 创建SDES包,只赋值了RtcpHeader以及SdesChunk对象的length和text部分 + * @param item_text SdesChunk列表,只赋值length和text部分 + * @return SDES包 + */ + static std::shared_ptr create(const std::vector &item_text); + + /** + * 获取SdesChunk对象指针列表 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::vector getChunkList(); + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); +}; + +// https://tools.ietf.org/html/rfc4585#section-6.1 +// 6.1. Common Packet Format for Feedback Messages +// +// All FB messages MUST use a common packet format that is depicted in +// Figure 3: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// rtcpfb和psfb的数据结构一致 +class RtcpFB : public RtcpHeader { +public: + friend class RtcpHeader; + uint32_t ssrc; + uint32_t ssrc_media; + +public: + /** + * 创建psfb类型的反馈包 + */ + static std::shared_ptr create(PSFBType fmt, const void *fci = nullptr, size_t fci_len = 0); + + /** + * 创建rtpfb类型的反馈包 + */ + static std::shared_ptr create(RTPFBType fmt, const void *fci = nullptr, size_t fci_len = 0); + + /** + * fci转换成某对象指针 + * @tparam Type 对象类型 + * @return 对象指针 + */ + template + const Type &getFci() const { + auto fci_data = getFciPtr(); + auto fci_len = getFciSize(); + Type *fci = (Type *)fci_data; + fci->check(fci_len); + return *fci; + } + + /** + * 获取fci指针 + */ + const void *getFciPtr() const; + + /** + * 获取fci数据长度 + */ + size_t getFciSize() const; + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); + +private: + static std::shared_ptr create_l(RtcpType type, int fmt, const void *fci, size_t fci_len); +}; + +// BYE +/* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| SC | PT=BYE=203 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +(opt) | length | reason for leaving ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +class RtcpBye : public RtcpHeader { +public: + friend class RtcpHeader; + /* 变长,根据count决定有多少个ssrc */ + uint32_t ssrc[1]; + + /** 中间可能有若干个 ssrc **/ + + /* 可选 */ + uint8_t reason_len; + char reason[1]; + +public: + /** + * 创建bye包 + * @param ssrc ssrc列表 + * @param reason 原因 + * @return rtcp bye包 + */ + static std::shared_ptr create(const std::vector &ssrc, const std::string &reason); + + /** + * 获取ssrc列表 + */ + std::vector getSSRC(); + + /** + * 获取原因 + */ + std::string getReason() const; + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); +}; + +/* +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 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|reserved | PT=XR=207 | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +: report blocks : ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +*/ +/* + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=4 | reserved | block length = 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, most significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +*/ +class RtcpXRRRTR : public RtcpHeader { +public: + friend class RtcpHeader; + uint32_t ssrc; + // 4 + uint8_t bt; + uint8_t reserved; + // 2 + uint16_t block_length; + // ntp timestamp MSW(in second) + uint32_t ntpmsw; + // ntp timestamp LSW(in picosecond) + uint32_t ntplsw; + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); + +}; + +/* + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=5 | reserved | block length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_1 (SSRC of first receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + | last RR (LRR) | 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last RR (DLRR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_2 (SSRC of second receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + : ... : 2 + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +*/ +class RtcpXRDLRRReportItem { +public: + friend class RtcpXRDLRR; + uint32_t ssrc; + uint32_t lrr; + uint32_t dlrr; + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(); +}; + +class RtcpXRDLRR : public RtcpHeader { +public: + friend class RtcpHeader; + uint32_t ssrc; + uint8_t bt; + uint8_t reserved; + uint16_t block_length; + RtcpXRDLRRReportItem items; + + /** + * 创建RtcpXRDLRR包,只赋值了RtcpHeader部分(网络字节序) + * @param item_count RtcpXRDLRRReportItem对象个数 + * @return RtcpXRDLRR包 + */ + static std::shared_ptr create(size_t item_count); + + /** + * 获取RtcpXRDLRRReportItem对象指针列表 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::vector getItemList(); + +private: + /** + * 打印字段详情 + * 使用net2Host转换成主机字节序后才可使用此函数 + */ + std::string dumpString() const; + + /** + * 网络字节序转换为主机字节序 + * @param size 字节长度,防止内存越界 + */ + void net2Host(size_t size); + +}; + +#pragma pack(pop) + +} // namespace mediakit +#endif // ZLMEDIAKIT_RTCP_H diff --git a/MediaServer/Rtcp/RtcpContext.cpp b/MediaServer/Rtcp/RtcpContext.cpp new file mode 100644 index 0000000..64db7ac --- /dev/null +++ b/MediaServer/Rtcp/RtcpContext.cpp @@ -0,0 +1,284 @@ +/* + * 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 "RtcpContext.h" +#include "Util/logger.h" +using namespace toolkit; + +namespace mediakit { + +void RtcpContext::onRtp( + uint16_t /*seq*/, uint32_t stamp, uint64_t ntp_stamp_ms, uint32_t /*sample_rate*/, size_t bytes) { + ++_packets; + _bytes += bytes; + _last_rtp_stamp = stamp; + _last_ntp_stamp_ms = ntp_stamp_ms; +} + +size_t RtcpContext::getExpectedPackets() const { + throw std::runtime_error("没有实现, rtp发送者无法统计应收包数"); +} + +size_t RtcpContext::getExpectedPacketsInterval() { + throw std::runtime_error("没有实现, rtp发送者无法统计应收包数"); +} + +size_t RtcpContext::getLost() { + throw std::runtime_error("没有实现, rtp发送者无法统计丢包率"); +} + +size_t RtcpContext::getLostInterval() { + throw std::runtime_error("没有实现, rtp发送者无法统计丢包率"); +} + +Buffer::Ptr RtcpContext::createRtcpSR(uint32_t rtcp_ssrc) { + throw std::runtime_error("没有实现, rtp接收者尝试发送sr包"); +} + +Buffer::Ptr RtcpContext::createRtcpRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc) { + throw std::runtime_error("没有实现, rtp发送者尝试发送rr包"); +} + +Buffer::Ptr RtcpContext::createRtcpXRDLRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc) { + throw std::runtime_error("没有实现, rtp发送者尝试发送xr dlrr包"); +} + +//////////////////////////////////////////////////////////////////////////////////// + +void RtcpContextForSend::onRtcp(RtcpHeader *rtcp) { + switch ((RtcpType)rtcp->pt) { + case RtcpType::RTCP_RR: { + auto rtcp_rr = (RtcpRR *)rtcp; + for (auto item : rtcp_rr->getItemList()) { + if (!item->last_sr_stamp) { + continue; + } + auto it = _sender_report_ntp.find(item->last_sr_stamp); + if (it == _sender_report_ntp.end()) { + continue; + } + // 发送sr到收到rr之间的时间戳增量 [AUTO-TRANSLATED:da014bf1] + // Timestamp increment between sending sr and receiving rr + auto ms_inc = getCurrentMillisecond() - it->second; + // rtp接收端收到sr包后,回复rr包的延时,已转换为毫秒 [AUTO-TRANSLATED:bfab8622] + // Delay of the rtp receiver replying to the rr packet after receiving the sr packet, converted to milliseconds + auto delay_ms = (uint64_t)item->delay_since_last_sr * 1000 / 65536; + auto rtt = (int)(ms_inc - delay_ms); + if (rtt >= 0) { + // rtt不可能小于0 [AUTO-TRANSLATED:34914014] + // RTT cannot be less than 0 + _rtt[item->ssrc] = rtt; + // InfoL << "ssrc:" << item->ssrc << ",rtt:" << rtt; + } + } + break; + } + case RtcpType::RTCP_XR: { + auto rtcp_xr = (RtcpXRRRTR *)rtcp; + if (rtcp_xr->bt == 4) { + _xr_xrrtr_recv_last_rr[rtcp_xr->ssrc] + = ((rtcp_xr->ntpmsw & 0xFFFF) << 16) | ((rtcp_xr->ntplsw >> 16) & 0xFFFF); + _xr_rrtr_recv_sys_stamp[rtcp_xr->ssrc] = getCurrentMillisecond(); + } else if (rtcp_xr->bt == 5) { + TraceL << "for sender not recive dlrr"; + } else { + TraceL << "not support xr bt " << rtcp_xr->bt; + } + break; + } + default: + break; + } +} + +uint32_t RtcpContextForSend::getRtt(uint32_t ssrc) const { + auto it = _rtt.find(ssrc); + if (it == _rtt.end()) { + return 0; + } + return it->second; +} + +Buffer::Ptr RtcpContextForSend::createRtcpSR(uint32_t rtcp_ssrc) { + auto rtcp = RtcpSR::create(0); + rtcp->setNtpStamp(_last_ntp_stamp_ms); + rtcp->rtpts = htonl(_last_rtp_stamp); + rtcp->ssrc = htonl(rtcp_ssrc); + rtcp->packet_count = htonl((uint32_t)_packets); + rtcp->octet_count = htonl((uint32_t)_bytes); + + // 记录上次发送的sender report信息,用于后续统计rtt [AUTO-TRANSLATED:1d22d2c8] + // Record the last sent sender report information for subsequent RTT statistics + auto last_sr_lsr = ((ntohl(rtcp->ntpmsw) & 0xFFFF) << 16) | ((ntohl(rtcp->ntplsw) >> 16) & 0xFFFF); + _sender_report_ntp[last_sr_lsr] = getCurrentMillisecond(); + if (_sender_report_ntp.size() >= 5) { + // 删除最早的sr rtcp [AUTO-TRANSLATED:2457e08d] + // Delete the earliest sr rtcp + _sender_report_ntp.erase(_sender_report_ntp.begin()); + } + + return RtcpHeader::toBuffer(std::move(rtcp)); +} + +toolkit::Buffer::Ptr RtcpContextForSend::createRtcpXRDLRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc) { + auto rtcp = RtcpXRDLRR::create(1); + rtcp->bt = 5; + rtcp->reserved = 0; + rtcp->block_length = htons(3); + rtcp->ssrc = htonl(rtcp_ssrc); + rtcp->items.ssrc = htonl(rtp_ssrc); + + if (_xr_xrrtr_recv_last_rr.find(rtp_ssrc) == _xr_xrrtr_recv_last_rr.end()) { + rtcp->items.lrr = 0; + WarnL; + } else { + rtcp->items.lrr = htonl(_xr_xrrtr_recv_last_rr[rtp_ssrc]); + } + + if (_xr_rrtr_recv_sys_stamp.find(rtp_ssrc) == _xr_rrtr_recv_sys_stamp.end()) { + rtcp->items.dlrr = 0; + WarnL; + } else { + // now - Last SR time,单位毫秒 [AUTO-TRANSLATED:cc449199] + // now - Last SR time, in milliseconds + auto delay = getCurrentMillisecond() - _xr_rrtr_recv_sys_stamp[rtp_ssrc]; + // in units of 1/65536 seconds + auto dlsr = (uint32_t)(delay / 1000.0f * 65536); + rtcp->items.dlrr = htonl(dlsr); + } + return RtcpHeader::toBuffer(std::move(rtcp)); +} + +//////////////////////////////////////////////////////////////////////////////////// + +void RtcpContextForRecv::onRtp( + uint16_t seq, uint32_t stamp, uint64_t ntp_stamp_ms, uint32_t sample_rate, size_t bytes) { + { + // 接收者才做复杂的统计运算 [AUTO-TRANSLATED:853c68e0] + // The receiver performs complex statistical calculations + auto sys_stamp = getCurrentMillisecond(); + if (_last_rtp_sys_stamp) { + // 计算时间戳抖动值 [AUTO-TRANSLATED:cd3571b4] + // Calculate the timestamp jitter value + double diff = double( + (int64_t(sys_stamp) - int64_t(_last_rtp_sys_stamp)) * (sample_rate / double(1000.0)) + - (int64_t(stamp) - int64_t(_last_rtp_stamp))); + if (diff < 0) { + diff = -diff; + } + // 抖动单位为采样次数 [AUTO-TRANSLATED:b713633a] + // Jitter unit is the number of samples + _jitter += (diff - _jitter) / 16.0; + } else { + _jitter = 0; + } + + if (_last_rtp_seq > 0xFF00 && seq < 0xFF && (!_seq_cycles || _packets - _last_cycle_packets > 0x1FFF)) { + // 上次seq大于0xFF00且本次seq小于0xFF, [AUTO-TRANSLATED:82dd69fa] + // Last seq is greater than 0xFF00 and this seq is less than 0xFF, + // 且未发生回环或者距离上次回环间隔超过0x1FFF个包,则认为回环 [AUTO-TRANSLATED:2907b595] + // and no loopback occurs or the interval between the last loopback is greater than 0x1FFF packets, then it is considered a loopback + ++_seq_cycles; + _last_cycle_packets = _packets; + _seq_max = seq; + } else if (seq > _seq_max) { + // 本次回环前最大seq [AUTO-TRANSLATED:c02f6a87] + // Maximum seq before this loopback + _seq_max = seq; + } + + if (!_seq_base) { + // 记录第一个rtp的seq [AUTO-TRANSLATED:ce2bb7d7] + // Record the seq of the first rtp + _seq_base = seq; + } else if (!_seq_cycles && seq < _seq_base) { + // 未发生回环,那么取最新的seq为基准seq [AUTO-TRANSLATED:721b37fc] + // If no loopback occurs, then take the latest seq as the base seq + _seq_base = seq; + } + + _last_rtp_seq = seq; + _last_rtp_sys_stamp = sys_stamp; + } + RtcpContext::onRtp(seq, stamp, ntp_stamp_ms, sample_rate, bytes); +} + +void RtcpContextForRecv::onRtcp(RtcpHeader *rtcp) { + switch ((RtcpType)rtcp->pt) { + case RtcpType::RTCP_SR: { + auto rtcp_sr = (RtcpSR *)rtcp; + /** + last SR timestamp (LSR): 32 bits + The middle 32 bits out of 64 in the NTP timestamp (as explained in + Section 4) received as part of the most recent RTCP sender report + (SR) packet from source SSRC_n. If no SR has been received yet, + the field is set to zero. + */ + _last_sr_lsr = ((rtcp_sr->ntpmsw & 0xFFFF) << 16) | ((rtcp_sr->ntplsw >> 16) & 0xFFFF); + _last_sr_ntp_sys = getCurrentMillisecond(); + break; + } + default: + break; + } +} + +size_t RtcpContextForRecv::getExpectedPackets() const { + return (_seq_cycles << 16) + _seq_max - _seq_base + 1; +} + +size_t RtcpContextForRecv::getExpectedPacketsInterval() { + auto expected = getExpectedPackets(); + auto ret = expected - _last_expected; + _last_expected = expected; + return ret; +} + +size_t RtcpContextForRecv::getLost() { + return getExpectedPackets() - _packets; +} + +size_t RtcpContextForRecv::getLostInterval() { + auto lost = getLost(); + auto ret = lost - _last_lost; + _last_lost = lost; + return ret; +} + +Buffer::Ptr RtcpContextForRecv::createRtcpRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc) { + auto rtcp = RtcpRR::create(1); + rtcp->ssrc = htonl(rtcp_ssrc); + + ReportItem *item = (ReportItem *)&rtcp->items; + item->ssrc = htonl(rtp_ssrc); + + uint8_t fraction = 0; + auto expected_interval = getExpectedPacketsInterval(); + if (expected_interval) { + fraction = uint8_t(getLostInterval() << 8 / expected_interval); + } + + item->fraction = fraction; + item->cumulative = htonl(uint32_t(getLost())) >> 8; + item->seq_cycles = htons(_seq_cycles); + item->seq_max = htons(_seq_max); + item->jitter = htonl(uint32_t(_jitter)); + item->last_sr_stamp = htonl(_last_sr_lsr); + + // now - Last SR time,单位毫秒 [AUTO-TRANSLATED:cc449199] + // now - Last SR time, in milliseconds + auto delay = getCurrentMillisecond() - _last_sr_ntp_sys; + // in units of 1/65536 seconds + auto dlsr = (uint32_t)(delay / 1000.0f * 65536); + item->delay_since_last_sr = htonl(_last_sr_lsr ? dlsr : 0); + return RtcpHeader::toBuffer(rtcp); +} + +} // namespace mediakit \ No newline at end of file diff --git a/MediaServer/Rtcp/RtcpContext.h b/MediaServer/Rtcp/RtcpContext.h new file mode 100644 index 0000000..20d7ffe --- /dev/null +++ b/MediaServer/Rtcp/RtcpContext.h @@ -0,0 +1,211 @@ +/* + * 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_RTCPCONTEXT_H +#define ZLMEDIAKIT_RTCPCONTEXT_H + +#include "Rtcp.h" +#include +#include + +namespace mediakit { + +class RtcpContext { +public: + using Ptr = std::shared_ptr; + virtual ~RtcpContext() = default; + + /** + * 输出或输入rtp时调用 + * @param seq rtp的seq + * @param stamp rtp的时间戳,单位采样数(非毫秒) + * @param ntp_stamp_ms ntp时间戳 + * @param rtp rtp时间戳采样率,视频一般为90000,音频一般为采样率 + * @param bytes rtp数据长度 + * Called when outputting or inputting rtp + * @param seq rtp's seq + * @param stamp rtp's timestamp, unit is sample number (not millisecond) + * @param ntp_stamp_ms ntp timestamp + * @param rtp rtp timestamp sampling rate, video is generally 90000, audio is generally sampling rate + * @param bytes rtp data length + + * [AUTO-TRANSLATED:745772b5] + */ + virtual void onRtp(uint16_t seq, uint32_t stamp, uint64_t ntp_stamp_ms, uint32_t sample_rate, size_t bytes); + + /** + * 输入sr rtcp包 + * @param rtcp 输入一个rtcp + * Input sr rtcp packet + * @param rtcp input an rtcp + + * [AUTO-TRANSLATED:46f309ec] + */ + virtual void onRtcp(RtcpHeader *rtcp) = 0; + + /** + * 计算总丢包数 + * Calculate the total number of lost packets + + * [AUTO-TRANSLATED:084f3832] + */ + virtual size_t getLost(); + + /** + * 返回理应收到的rtp数 + * Return the number of rtp that should be received + + * [AUTO-TRANSLATED:ede367a0] + */ + virtual size_t getExpectedPackets() const; + + /** + * 创建SR rtcp包 + * @param rtcp_ssrc rtcp的ssrc + * @return rtcp包 + * Create SR rtcp packet + * @param rtcp_ssrc rtcp's ssrc + * @return rtcp packet + + * [AUTO-TRANSLATED:a9ec36d0] + */ + virtual toolkit::Buffer::Ptr createRtcpSR(uint32_t rtcp_ssrc); + + /** + * @brief 创建xr的dlrr包,用于接收者估算rtt + * + * @return toolkit::Buffer::Ptr + * @brief Create xr's dlrr packet, used by receiver to estimate rtt + * + * @return toolkit::Buffer::Ptr + + * [AUTO-TRANSLATED:a5094e1d] + */ + virtual toolkit::Buffer::Ptr createRtcpXRDLRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc); + + /** + * 创建RR rtcp包 + * @param rtcp_ssrc rtcp的ssrc + * @param rtp_ssrc rtp的ssrc + * @return rtcp包 + * Create RR rtcp packet + * @param rtcp_ssrc rtcp's ssrc + * @param rtp_ssrc rtp's ssrc + * @return rtcp packet + + * [AUTO-TRANSLATED:81ebbf81] + */ + virtual toolkit::Buffer::Ptr createRtcpRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc); + + /** + * 上次结果与本次结果间应收包数 + * Number of packets that should be received between the last result and the current result + + * [AUTO-TRANSLATED:3b2846ab] + */ + virtual size_t getExpectedPacketsInterval(); + + /** + * 上次结果与本次结果间丢包个数 + * Number of lost packets between the last result and the current result + + * [AUTO-TRANSLATED:fe5ac890] + */ + virtual size_t getLostInterval(); + +protected: + // 收到或发送的rtp的字节数 [AUTO-TRANSLATED:a38d88a9] + // Number of bytes of rtp received or sent + size_t _bytes = 0; + // 收到或发送的rtp的个数 [AUTO-TRANSLATED:b28c3c90] + // Number of rtp received or sent + size_t _packets = 0; + // 上次的rtp时间戳,毫秒 [AUTO-TRANSLATED:99eecec6] + // Last rtp timestamp, milliseconds + uint32_t _last_rtp_stamp = 0; + uint64_t _last_ntp_stamp_ms = 0; +}; + +class RtcpContextForSend : public RtcpContext { +public: + toolkit::Buffer::Ptr createRtcpSR(uint32_t rtcp_ssrc) override; + + void onRtcp(RtcpHeader *rtcp) override; + + toolkit::Buffer::Ptr createRtcpXRDLRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc) override; + + /** + * 获取rtt + * @param ssrc rtp ssrc + * @return rtt,单位毫秒 + * Get rtt + * @param ssrc rtp ssrc + * @return rtt, unit is millisecond + + * [AUTO-TRANSLATED:f0885551] + */ + uint32_t getRtt(uint32_t ssrc) const; + +private: + std::map _rtt; + std::map _sender_report_ntp; + + std::map _xr_rrtr_recv_sys_stamp; + std::map _xr_xrrtr_recv_last_rr; +}; + +class RtcpContextForRecv : public RtcpContext { +public: + void onRtp(uint16_t seq, uint32_t stamp, uint64_t ntp_stamp_ms, uint32_t sample_rate, size_t bytes) override; + toolkit::Buffer::Ptr createRtcpRR(uint32_t rtcp_ssrc, uint32_t rtp_ssrc) override; + size_t getExpectedPackets() const override; + size_t getExpectedPacketsInterval() override; + size_t getLost() override; + size_t getLostInterval() override; + void onRtcp(RtcpHeader *rtcp) override; + +private: + // 时间戳抖动值 [AUTO-TRANSLATED:8100680c] + // Timestamp jitter value + double _jitter = 0; + // 第一个seq的值 [AUTO-TRANSLATED:d893719d] + // The value of the first seq + uint16_t _seq_base = 0; + // rtp最大seq [AUTO-TRANSLATED:5cc9f775] + // Maximum rtp seq + uint16_t _seq_max = 0; + // rtp回环次数 [AUTO-TRANSLATED:9fe9c340] + // Rtp loopback times + uint16_t _seq_cycles = 0; + // 上次回环发生时,记录的rtp包数 [AUTO-TRANSLATED:c32cb555] + // Number of rtp packets recorded when the last loopback occurred + size_t _last_cycle_packets = 0; + // 上次的seq [AUTO-TRANSLATED:07364b7d] + // Last seq + uint16_t _last_rtp_seq = 0; + // 上次的rtp的系统时间戳(毫秒)用于统计抖动 [AUTO-TRANSLATED:b1e8c89b] + // Last rtp system timestamp (milliseconds) used for jitter statistics + uint64_t _last_rtp_sys_stamp = 0; + // 上次统计的丢包总数 [AUTO-TRANSLATED:242e75ed] + // Last total number of lost packets counted + size_t _last_lost = 0; + // 上次统计应收rtp包总数 [AUTO-TRANSLATED:eb2d5f4d] + // Last total number of rtp packets that should be received counted + size_t _last_expected = 0; + // 上次收到sr包时计算出的Last SR timestamp [AUTO-TRANSLATED:fdec069e] + // Last SR timestamp calculated when the last SR packet was received + uint32_t _last_sr_lsr = 0; + // 上次收到sr时的系统时间戳,单位毫秒 [AUTO-TRANSLATED:044fa0d5] + // System timestamp when the last SR was received, unit is millisecond + uint64_t _last_sr_ntp_sys = 0; +}; + +} // namespace mediakit +#endif // ZLMEDIAKIT_RTCPCONTEXT_H diff --git a/MediaServer/Rtcp/RtcpFCI.cpp b/MediaServer/Rtcp/RtcpFCI.cpp new file mode 100644 index 0000000..64e7b39 --- /dev/null +++ b/MediaServer/Rtcp/RtcpFCI.cpp @@ -0,0 +1,599 @@ +/* + * 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 "RtcpFCI.h" +#include "Util/logger.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +void FCI_SLI::check(size_t size) { + CHECK(size >= kSize); +} + +FCI_SLI::FCI_SLI(uint16_t first, uint16_t number, uint8_t pic_id) { + // 13 bits + first &= 0x1FFF; + // 13 bits + number &= 0x1FFF; + // 6 bits + pic_id &= 0x3F; + data = (first << 19) | (number << 6) | pic_id; + data = htonl(data); +} + +uint16_t FCI_SLI::getFirst() const { + return ntohl(data) >> 19; +} + +uint16_t FCI_SLI::getNumber() const { + return (ntohl(data) >> 6) & 0x1FFF; +} + +uint8_t FCI_SLI::getPicID() const { + return ntohl(data) & 0x3F; +} + +string FCI_SLI::dumpString() const { + return StrPrinter << "First:" << getFirst() << ", Number:" << getNumber() << ", PictureID:" << (int)getPicID(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void FCI_FIR::check(size_t size) { + CHECK(size >= kSize); +} + +uint32_t FCI_FIR::getSSRC() const { + return ntohl(ssrc); +} + +uint8_t FCI_FIR::getSeq() const { + return seq_number; +} + +uint32_t FCI_FIR::getReserved() const { + return (reserved[0] << 16) | (reserved[1] << 8) | reserved[2]; +} + +string FCI_FIR::dumpString() const { + return StrPrinter << "ssrc:" << getSSRC() << ", seq_number:" << (int)getSeq() << ", reserved:" << getReserved(); +} + +FCI_FIR::FCI_FIR(uint32_t ssrc, uint8_t seq_number, uint32_t reserved) { + this->ssrc = htonl(ssrc); + this->seq_number = seq_number; + this->reserved[0] = (reserved >> 16) & 0xFF; + this->reserved[1] = (reserved >> 8) & 0xFF; + this->reserved[2] = reserved & 0xFF; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const char kRembMagic[] = "REMB"; + +void FCI_REMB::check(size_t size) { + CHECK(size >= kSize); + CHECK(memcmp(magic, kRembMagic, sizeof(magic)) == 0); + auto num_ssrc = bitrate[0]; + auto expect_size = kSize + 4 * num_ssrc; + CHECK(size >= expect_size); +} + +string FCI_REMB::create(const vector &ssrcs, uint32_t bitrate) { + CHECK(ssrcs.size() > 0 && ssrcs.size() <= 0xFF); + string ret; + ret.resize(kSize + ssrcs.size() * 4); + FCI_REMB *thiz = (FCI_REMB *)ret.data(); + memcpy(thiz->magic, kRembMagic, sizeof(magic)); + + /* bitrate --> BR Exp/BR Mantissa */ + uint8_t b = 0; + uint8_t exp = 0; + uint32_t mantissa = 0; + for (b = 0; b < 32; b++) { + if (bitrate <= ((uint32_t)0x3FFFF << b)) { + exp = b; + break; + } + } + if (b > 31) { + b = 31; + } + mantissa = bitrate >> b; + // Num SSRC (8 bits) + thiz->bitrate[0] = ssrcs.size() & 0xFF; + // BR Exp (6 bits)/BR Mantissa (18 bits) + thiz->bitrate[1] = (uint8_t)((exp << 2) + ((mantissa >> 16) & 0x03)); + // BR Mantissa (18 bits) + thiz->bitrate[2] = (uint8_t)(mantissa >> 8); + // BR Mantissa (18 bits) + thiz->bitrate[3] = (uint8_t)(mantissa); + + // 设置ssrc列表 [AUTO-TRANSLATED:51a0df3a] + // Set ssrc list + int i = 0; + for (auto ssrc : ssrcs) { + thiz->ssrc_feedback[i++] = htonl(ssrc); + } + return ret; +} + +uint32_t FCI_REMB::getBitRate() const { + uint8_t exp = (bitrate[1] >> 2) & 0x3F; + uint32_t mantissa = (bitrate[1] & 0x03) << 16; + mantissa += (bitrate[2] << 8); + mantissa += (bitrate[3]); + return mantissa << exp; +} + +vector FCI_REMB::getSSRC() { + vector ret; + auto num_ssrc = bitrate[0]; + int i = 0; + while (num_ssrc--) { + ret.emplace_back(ntohl(ssrc_feedback[i])); + ++i; + } + return ret; +} + +string FCI_REMB::dumpString() const { + _StrPrinter printer; + printer << "bitrate:" << getBitRate() << ", ssrc:"; + for (auto &ssrc : ((FCI_REMB *)this)->getSSRC()) { + printer << ssrc << " "; + } + return std::move(printer); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +FCI_NACK::FCI_NACK(uint16_t pid_h, const vector &type) { + assert(type.size() <= kBitSize); + uint16_t blp_h = 0; + int i = 0; + for (auto item : type) { + if (item) { + blp_h |= (1 << i); + } + ++i; + } + blp = htons(blp_h); + pid = htons(pid_h); +} + +void FCI_NACK::check(size_t size) { + CHECK(size >= kSize); +} + +uint16_t FCI_NACK::getPid() const { + return ntohs(pid); +} + +uint16_t FCI_NACK::getBlp() const { + return ntohs(blp); +} + +vector FCI_NACK::getBitArray() const { + vector ret; + ret.resize(kBitSize + 1); + // nack第一个包丢包 [AUTO-TRANSLATED:357feeea] + // Nack the first packet loss + ret[0] = true; + + auto blp_h = getBlp(); + for (size_t i = 0; i < kBitSize; ++i) { + ret[i + 1] = blp_h & (1 << i); + } + return ret; +} + +string FCI_NACK::dumpString() const { + _StrPrinter printer; + auto pid = getPid(); + printer << "pid:" << pid << ",blp:" << getBlp() << ",dropped rtp seq:"; + for (auto flag : getBitArray()) { + if (flag) { + printer << pid << " "; + } + ++pid; + } + return std::move(printer); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma pack(push, 1) +class RunLengthChunk { +public: + static size_t constexpr kSize = 2; + // 0 1 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |T| S | Run Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +#if __BYTE_ORDER == __BIG_ENDIAN + uint16_t type : 1; + uint16_t symbol : 2; + uint16_t run_length_high : 5; +#else + // Run Length 高5位 [AUTO-TRANSLATED:a1426130] + // Run Length high 5 bits + uint16_t run_length_high : 5; + // 参考SymbolStatus定义 [AUTO-TRANSLATED:0268c65e] + // Refer to SymbolStatus definition + uint16_t symbol : 2; + // 固定为0 [AUTO-TRANSLATED:7b516577] + // Fixed to 0 + uint16_t type : 1; +#endif + // Run Length 低8位 [AUTO-TRANSLATED:8984c00d] + // Run Length low 8 bits + uint16_t run_length_low : 8; + + // 获取Run Length [AUTO-TRANSLATED:9fb792e6] + // Get Run Length + uint16_t getRunLength() const; + // 构造函数 [AUTO-TRANSLATED:b9f7407d] + // Constructor + RunLengthChunk(SymbolStatus status, uint16_t run_length); + // 打印本对象 [AUTO-TRANSLATED:e8bd8207] + // Print this object + string dumpString() const; +}; +#pragma pack(pop) + +RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) { + type = 0; + symbol = (uint8_t)status & 0x03; + run_length_high = (run_length >> 8) & 0x1F; + run_length_low = run_length & 0xFF; +} + +uint16_t RunLengthChunk::getRunLength() const { + CHECK(type == 0); + return run_length_high << 8 | run_length_low; +} + +string RunLengthChunk::dumpString() const { + _StrPrinter printer; + printer << "run length chunk, symbol:" << (int)symbol << ", run length:" << getRunLength(); + return std::move(printer); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma pack(push, 1) +class StatusVecChunk { +public: + static size_t constexpr kSize = 2; + // 0 1 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |T|S| symbol list | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +#if __BYTE_ORDER == __BIG_ENDIAN + uint16_t type : 1; + uint16_t symbol : 1; + uint16_t symbol_list_high : 6; +#else + // symbol_list 高6位 [AUTO-TRANSLATED:2ef1be51] + // symbol_list high 6 bits + uint16_t symbol_list_high : 6; + // symbol_list中元素是1个还是2个bit [AUTO-TRANSLATED:0f56756f] + // Whether the element in symbol_list is 1 or 2 bits + uint16_t symbol : 1; + // 固定为1 [AUTO-TRANSLATED:1fb4c9c8] + // Fixed to 1 + uint16_t type : 1; +#endif + // symbol_list 低8位 [AUTO-TRANSLATED:257bf13b] + // symbol_list low 8 bits + uint16_t symbol_list_low : 8; + + // 获取symbollist [AUTO-TRANSLATED:a24b8d73] + // Get symbollist + vector getSymbolList() const; + // 构造函数 [AUTO-TRANSLATED:b9f7407d] + // Constructor + StatusVecChunk(bool symbol_bit, const vector &status); + // 打印本对象 [AUTO-TRANSLATED:e8bd8207] + // Print this object + string dumpString() const; +}; +#pragma pack(pop) + +StatusVecChunk::StatusVecChunk(bool symbol_bit, const vector &status) { + CHECK(status.size() << symbol_bit <= 14); + uint16_t value = 0; + type = 1; + symbol = symbol_bit; + int i = 13; + for (auto &item : status) { + CHECK(item <= SymbolStatus::reserved); + if (!symbol) { + CHECK(item <= SymbolStatus::small_delta); + value |= (int)item << i; + --i; + } else { + value |= (int)item << (i - 1); + i -= 2; + } + } + symbol_list_low = value & 0xFF; + symbol_list_high = (value >> 8) & 0x3F; +} + +vector StatusVecChunk::getSymbolList() const { + CHECK(type == 1); + vector ret; + auto thiz = ntohs(*((uint16_t *)this)); + if (symbol == 0) { + // s = 0 时,表示symbollist的每一个bit能表示一个数据包的到达状态 [AUTO-TRANSLATED:89d8f104] + // When s = 0, each bit in symbollist represents the arrival status of a data packet + for (int i = 13; i >= 0; --i) { + SymbolStatus status = (SymbolStatus)((bool)(thiz & (1 << i))); + ret.emplace_back(status); + } + } else { + // s = 1 时,表示symbollist每两个bit表示一个数据包的状态 [AUTO-TRANSLATED:fd7eb5fe] + // When s = 1, every two bits in symbollist represent the status of a data packet + for (int i = 12; i >= 0; i -= 2) { + SymbolStatus status = (SymbolStatus)((thiz & (3 << i)) >> i); + ret.emplace_back(status); + } + } + return ret; +} + +string StatusVecChunk::dumpString() const { + _StrPrinter printer; + printer << "status vector chunk, symbol:" << (int)symbol << ", symbol list:"; + auto vec = getSymbolList(); + for (auto &item : vec) { + printer << (int)item << " "; + } + return std::move(printer); +} + +/////////////////////////////////////////////////////// + +void FCI_TWCC::check(size_t size) { + CHECK(size >= kSize); +} + +uint16_t FCI_TWCC::getBaseSeq() const { + return ntohs(base_seq); +} + +uint16_t FCI_TWCC::getPacketCount() const { + return ntohs(pkt_status_count); +} + +uint32_t FCI_TWCC::getReferenceTime() const { + uint32_t ret = 0; + ret |= ref_time[0] << 16; + ret |= ref_time[1] << 8; + ret |= ref_time[2]; + return ret; +} +// 3.1.5. Receive Delta +// +// Deltas are represented as multiples of 250us: +// +// o If the "Packet received, small delta" symbol has been appended to +// the status list, an 8-bit unsigned receive delta will be appended +// to recv delta list, representing a delta in the range [0, 63.75] +// ms. +// +// o If the "Packet received, large or negative delta" symbol has been +// appended to the status list, a 16-bit signed receive delta will be +// appended to recv delta list, representing a delta in the range +// [-8192.0, 8191.75] ms. +// +// o If the delta exceeds even the larger limits, a new feedback +// message must be used, where the 24-bit base receive delta can +// cover very large gaps. +// +// The smaller receive delta upper bound of 63.75 ms means that this is +// only viable at about 1000/25.5 ~= 16 packets per second and above. +// With a packet size of 1200 bytes/packet that amounts to a bitrate of +// about 150 kbit/s. +// +// The 0.25 ms resolution means that up to 4000 packets per second can +// be represented. With a 1200 bytes/packet payload, that amounts to +// 38.4 Mbit/s payload bandwidth. + +static int16_t getRecvDelta(SymbolStatus status, uint8_t *&ptr, const uint8_t *end) { + int16_t delta = 0; + switch (status) { + case SymbolStatus::not_received: { + // 丢包, recv delta为0个字节 [AUTO-TRANSLATED:4312b1ce] + // Packet loss, recv delta is 0 bytes + break; + } + case SymbolStatus::small_delta: { + CHECK(ptr + 1 <= end); + // 时间戳增量小于256, recv delta为1个字节 [AUTO-TRANSLATED:4b93efeb] + // Timestamp increment is less than 256, recv delta is 1 byte + delta = *ptr; + ptr += 1; + break; + } + case SymbolStatus::large_delta: { + CHECK(ptr + 2 <= end); + // 时间戳增量256~65535间,recv delta为2个字节 [AUTO-TRANSLATED:989c8340] + // Timestamp increment is between 256 and 65535, recv delta is 2 bytes + delta = *ptr << 8 | *(ptr + 1); + ptr += 2; + break; + } + case SymbolStatus::reserved: { + // 没有时间戳 [AUTO-TRANSLATED:0767909f] + // No timestamp + break; + } + default: + // 这个逻辑分支不可达到 [AUTO-TRANSLATED:451ba1eb] + // This logic branch cannot be reached + CHECK(0); + break; + } + return delta; +} + +FCI_TWCC::TwccPacketStatus FCI_TWCC::getPacketChunkList(size_t total_size) const { + TwccPacketStatus ret; + auto ptr = (uint8_t *)this + kSize; + auto end = (uint8_t *)this + total_size; + CHECK(ptr < end); + auto seq = getBaseSeq(); + auto rtp_count = getPacketCount(); + for (uint16_t i = 0; i < rtp_count;) { + CHECK(ptr + RunLengthChunk::kSize <= end); + RunLengthChunk *chunk = (RunLengthChunk *)ptr; + if (!chunk->type) { + // RunLengthChunk + for (auto j = 0; j < chunk->getRunLength(); ++j) { + ret.emplace(seq++, std::make_pair((SymbolStatus)chunk->symbol, 0)); + if (++i >= rtp_count) { + break; + } + } + } else { + // StatusVecChunk + StatusVecChunk *chunk = (StatusVecChunk *)ptr; + for (auto &symbol : chunk->getSymbolList()) { + ret.emplace(seq++, std::make_pair(symbol, 0)); + if (++i >= rtp_count) { + break; + } + } + } + ptr += 2; + } + for (auto &pr : ret) { + CHECK(ptr <= end); + pr.second.second = getRecvDelta(pr.second.first, ptr, end); + } + return ret; +} + +string FCI_TWCC::dumpString(size_t total_size) const { + _StrPrinter printer; + auto map = getPacketChunkList(total_size); + printer << "twcc fci, base_seq:" << getBaseSeq() << ", pkt_status_count:" << getPacketCount() + << ", ref time:" << getReferenceTime() << ", fb count:" << (int)fb_pkt_count << "\n"; + for (auto &pr : map) { + printer << "rtp seq:" << pr.first << ", packet status:" << (int)(pr.second.first) + << ", delta:" << pr.second.second << "\n"; + } + return std::move(printer); +} + +static void appendDeltaString(string &delta_str, FCI_TWCC::TwccPacketStatus &status, int count) { + for (auto it = status.begin(); it != status.end() && count--;) { + switch (it->second.first) { + // large delta模式先写高字节,再写低字节 [AUTO-TRANSLATED:cb25ff45] + // Large delta mode writes the high byte first, then the low byte + case SymbolStatus::large_delta: + delta_str.push_back((it->second.second >> 8) & 0xFF); + // small delta模式只写低字节 [AUTO-TRANSLATED:5caea5d6] + // Small delta mode only writes the low byte + case SymbolStatus::small_delta: + delta_str.push_back(it->second.second & 0xFF); + break; + default: + break; + } + // 移除已经处理过的数据 [AUTO-TRANSLATED:106fb6db] + // Remove the data that has been processed + it = status.erase(it); + } +} + +string FCI_TWCC::create(uint32_t ref_time, uint8_t fb_pkt_count, TwccPacketStatus &status) { + string fci; + fci.resize(FCI_TWCC::kSize); + FCI_TWCC *ptr = (FCI_TWCC *)(fci.data()); + ptr->base_seq = htons(status.begin()->first); + ptr->pkt_status_count = htons(status.size()); + ptr->fb_pkt_count = fb_pkt_count; + ptr->ref_time[0] = (ref_time >> 16) & 0xFF; + ptr->ref_time[1] = (ref_time >> 8) & 0xFF; + ptr->ref_time[2] = (ref_time >> 0) & 0xFF; + + string delta_str; + while (!status.empty()) { + { + // 第一个rtp的状态 [AUTO-TRANSLATED:b0efb9ad] + // The status of the first rtp + auto symbol = status.begin()->second.first; + int16_t count = 0; + for (auto &pr : status) { + if (pr.second.first != symbol) { + // 状态发送变更了,本chunk结束 [AUTO-TRANSLATED:f1c2c6d1] + // The status sending changed, this chunk ends + break; + } + if (++count >= (0xFFFF >> 3)) { + // RunLengthChunk 13个bit表明rtp个数,最多可以表述0xFFFF >> 3个rtp状态 [AUTO-TRANSLATED:c6c4de01] + // RunLengthChunk 13 bits indicate the number of rtp, at most 0xFFFF >> 3 rtp status can be expressed + break; + } + } + if (count >= 7) { + // 连续状态相同个数大于6个时,使用RunLengthChunk模式比较节省带宽 [AUTO-TRANSLATED:c243c73f] + // When the number of consecutive states is greater than 6, using RunLengthChunk mode is more bandwidth-saving + RunLengthChunk chunk(symbol, count); + fci.append((char *)&chunk, RunLengthChunk::kSize); + appendDeltaString(delta_str, status, count); + continue; + } + } + + { + // StatusVecChunk模式 [AUTO-TRANSLATED:612e548c] + // StatusVecChunk mode + // symbol_list中元素是1个bit [AUTO-TRANSLATED:e5f8cbf8] + // Elements in symbol_list are 1 bit + auto symbol = 0; + vector vec; + for (auto &pr : status) { + vec.push_back(pr.second.first); + if (pr.second.first >= SymbolStatus::large_delta) { + // symbol_list中元素是2个bit [AUTO-TRANSLATED:43429094] + // Elements in symbol_list are 2 bits + symbol = 1; + } + + if (vec.size() << symbol >= 14) { + // symbol为0时,最多存放14个rtp的状态 [AUTO-TRANSLATED:1da4fad6] + // When symbol is 0, at most 14 RTP statuses can be stored + // symbol为1时,最多存放7个rtp的状态 [AUTO-TRANSLATED:34f39e9a] + // When symbol is 1, at most 7 RTP statuses can be stored + break; + } + } + vec.resize(MIN(vec.size(), (size_t)14 >> symbol)); + StatusVecChunk chunk(symbol, vec); + fci.append((char *)&chunk, StatusVecChunk::kSize); + appendDeltaString(delta_str, status, vec.size()); + } + } + + // recv delta部分 [AUTO-TRANSLATED:f7e0d5bc] + // recv delta part + fci.append(delta_str); + return fci; +} + +} // namespace mediakit \ No newline at end of file diff --git a/MediaServer/Rtcp/RtcpFCI.h b/MediaServer/Rtcp/RtcpFCI.h new file mode 100644 index 0000000..4e21d79 --- /dev/null +++ b/MediaServer/Rtcp/RtcpFCI.h @@ -0,0 +1,389 @@ +/* + * 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_RTCPFCI_H +#define ZLMEDIAKIT_RTCPFCI_H + +#include "Rtcp.h" + +namespace mediakit { +#pragma pack(push, 1) + +/////////////////////////////////////////// PSFB //////////////////////////////////////////////////// + +// PSFB fmt = 2 +// https://tools.ietf.org/html/rfc4585#section-6.3.2.2 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | First | Number | PictureID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// First: 13 bits +// The macroblock (MB) address of the first lost macroblock. The MB +// numbering is done such that the macroblock in the upper left +// corner of the picture is considered macroblock number 1 and the +// number for each macroblock increases from left to right and then +// from top to bottom in raster-scan order (such that if there is a +// total of N macroblocks in a picture, the bottom right macroblock +// is considered macroblock number N). +// +// Number: 13 bits +// The number of lost macroblocks, in scan order as discussed above. +// +// PictureID: 6 bits +// The six least significant bits of the codec-specific identifier +// that is used to reference the picture in which the loss of the +// macroblock(s) has occurred. For many video codecs, the PictureID +// is identical to the Temporal Reference. +class FCI_SLI { +public: + static size_t constexpr kSize = 4; + + FCI_SLI(uint16_t first, uint16_t number, uint8_t pic_id); + + void check(size_t size); + uint16_t getFirst() const; + uint16_t getNumber() const; + uint8_t getPicID() const; + std::string dumpString() const; + +private: + uint32_t data; +}; + +#if 0 +//PSFB fmt = 3 +//https://tools.ietf.org/html/rfc4585#section-6.3.3.2 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PB |0| Payload Type| Native RPSI bit string | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | defined per codec ... | Padding (0) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_RPSI { +public: + //The number of unused bits required to pad the length of the RPSI + // message to a multiple of 32 bits. + uint8_t pb; + +#if __BYTE_ORDER == __BIG_ENDIAN + //0: 1 bit + // MUST be set to zero upon transmission and ignored upon reception. + uint8_t zero : 1; + //Payload Type: 7 bits + // Indicates the RTP payload type in the context of which the native + // RPSI bit string MUST be interpreted. + uint8_t pt : 7; +#else + uint8_t pt: 7; + uint8_t zero: 1; +#endif + + // Native RPSI bit string: variable length + // The RPSI information as natively defined by the video codec. + char bit_string[5]; + + //Padding: #PB bits + // A number of bits set to zero to fill up the contents of the RPSI + // message to the next 32-bit boundary. The number of padding bits + // MUST be indicated by the PB field. + uint8_t padding; + + static size_t constexpr kSize = 8; +}; +#endif + +// PSFB fmt = 4 +// https://tools.ietf.org/html/rfc5104#section-4.3.1.1 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Seq nr. | Reserved | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_FIR { +public: + static size_t constexpr kSize = 8; + + FCI_FIR(uint32_t ssrc, uint8_t seq_number, uint32_t reserved = 0); + + void check(size_t size); + uint32_t getSSRC() const; + uint8_t getSeq() const; + uint32_t getReserved() const; + std::string dumpString() const; + +private: + uint32_t ssrc; + uint8_t seq_number; + uint8_t reserved[3]; +}; + +#if 0 +//PSFB fmt = 5 +//https://tools.ietf.org/html/rfc5104#section-4.3.2.1 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Seq nr. | Reserved | Index | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_TSTR { +public: + static size_t constexpr kSize = 8; + + void check(size_t size) { + CHECK(size == kSize); + } + +private: + uint8_t data[kSize]; +}; + +//PSFB fmt = 6 +//https://tools.ietf.org/html/rfc5104#section-4.3.2.1 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Seq nr. | Reserved | Index | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_TSTN : public FCI_TSTR{ + +}; + +//PSFB fmt = 7 +//https://tools.ietf.org/html/rfc5104#section-4.3.4.1 +//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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Seq nr. |0| Payload Type| Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | VBCM Octet String.... | Padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_VBCM { +public: + static size_t constexpr kSize = 12; + + void check(size_t size) { + CHECK(size == kSize); + } + +private: + uint8_t data[kSize]; +}; + +#endif + +// PSFB fmt = 15 +// https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Unique identifier 'R' 'E' 'M' 'B' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Num SSRC | BR Exp | BR Mantissa | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC feedback | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... | +// Num SSRC (8 bits): Number of SSRCs in this message. +// +// BR Exp (6 bits): The exponential scaling of the mantissa for the +// maximum total media bit rate value, ignoring all packet +// overhead. The value is an unsigned integer [0..63], as +// in RFC 5104 section 4.2.2.1. +// +// BR Mantissa (18 bits): The mantissa of the maximum total media bit +// rate (ignoring all packet overhead) that the sender of +// the REMB estimates. The BR is the estimate of the +// traveled path for the SSRCs reported in this message. +// The value is an unsigned integer in number of bits per +// second. +// +// SSRC feedback (32 bits) Consists of one or more SSRC entries which +// this feedback message applies to. +class FCI_REMB { +public: + static size_t constexpr kSize = 8; + + static std::string create(const std::vector &ssrcs, uint32_t bitrate); + void check(size_t size); + std::string dumpString() const; + uint32_t getBitRate() const; + std::vector getSSRC(); + +private: + // Unique identifier 'R' 'E' 'M' 'B' + char magic[4]; + // Num SSRC (8 bits)/BR Exp (6 bits)/ BR Mantissa (18 bits) + uint8_t bitrate[4]; + // SSRC feedback (32 bits) Consists of one or more SSRC entries which + // this feedback message applies to. + uint32_t ssrc_feedback[1]; +}; + +/////////////////////////////////////////// RTPFB //////////////////////////////////////////////////// + +// RTPFB fmt = 1 +// https://tools.ietf.org/html/rfc4585#section-6.2.1 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PID | BLP | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_NACK { +public: + static constexpr size_t kSize = 4; + static constexpr size_t kBitSize = 16; + + FCI_NACK(uint16_t pid_h, const std::vector &type); + + void check(size_t size); + uint16_t getPid() const; + uint16_t getBlp() const; + // 返回丢包列表,总长度17,第一个包必丢 [AUTO-TRANSLATED:5d5cd4b8] + // Return the list of lost packets, total length 17, the first packet must be lost + // TODO: replace std::bitset + std::vector getBitArray() const; + std::string dumpString() const; + +private: + // The PID field is used to specify a lost packet. The PID field + // refers to the RTP sequence number of the lost packet. + uint16_t pid; + // bitmask of following lost packets (BLP): 16 bits + uint16_t blp; +}; + +#if 0 +//RTPFB fmt = 3 +//https://tools.ietf.org/html/rfc5104#section-4.2.1.1 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_TMMBR { +public: + static size_t constexpr kSize = 8; + + void check(size_t size) { + CHECK(size == kSize); + } + +private: + //SSRC (32 bits): The SSRC value of the media sender that is + // requested to obey the new maximum bit rate. + uint32_t ssrc; + + // MxTBR Exp (6 bits): The exponential scaling of the mantissa for the + // maximum total media bit rate value. The value is an + // unsigned integer [0..63]. + // MxTBR Mantissa (17 bits): The mantissa of the maximum total media + // bit rate value as an unsigned integer. + // Measured Overhead (9 bits): The measured average packet overhead + // value in bytes. The measurement SHALL be done according + // to the description in section 4.2.1.2. The value is an + // unsigned integer [0..511]. + uint32_t max_tbr; +}; + +//RTPFB fmt = 4 +// https://tools.ietf.org/html/rfc5104#section-4.2.2.1 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_TMMBN : public FCI_TMMBR{ +public: + +}; +#endif + +enum class SymbolStatus : uint8_t { + // Packet not received + not_received = 0, + // Packet received, small delta (所谓small detal是指能用一个字节表示的数值) [AUTO-TRANSLATED:50af3beb] + // Packet received, small delta (so-called small delta refers to a value that can be represented by one byte) + small_delta = 1, + // Packet received, large ornegative delta (large即是能用两个字节表示的数值) [AUTO-TRANSLATED:7a16594d] + // Packet received, large or negative delta (large is a value that can be represented by two bytes) + large_delta = 2, + // Reserved + reserved = 3 +}; + +// RTPFB fmt = 15 +// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1 +// https://zhuanlan.zhihu.com/p/206656654 +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | base sequence number | packet status count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | reference time | fb pkt. count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | packet chunk | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | recv delta | recv delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | recv delta | recv delta | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class FCI_TWCC { +public: + static size_t constexpr kSize = 8; + using TwccPacketStatus + = std::map>; + void check(size_t size); + std::string dumpString(size_t total_size) const; + uint16_t getBaseSeq() const; + // 单位64ms [AUTO-TRANSLATED:992ffed7] + // Unit 64ms + uint32_t getReferenceTime() const; + uint16_t getPacketCount() const; + TwccPacketStatus getPacketChunkList(size_t total_size) const; + + static std::string create(uint32_t ref_time, uint8_t fb_pkt_count, TwccPacketStatus &status); + +private: + // base sequence number,基础序号,本次反馈的第一个包的序号;也就是RTP扩展头的序列号 [AUTO-TRANSLATED:4e43ffcc] + // base sequence number, basic sequence number, the sequence number of the first packet in this feedback; that is, the sequence number of the RTP extension header + uint16_t base_seq; + // packet status count, 包个数,本次反馈包含多少个包的状态;从基础序号开始算 [AUTO-TRANSLATED:533efb94] + // packet status count, number of packets, how many packet statuses are included in this feedback; counted from the base sequence number + uint16_t pkt_status_count; + // reference time,基准时间,绝对时间;计算该包中每个媒体包的到达时间都要基于这个基准时间计算 [AUTO-TRANSLATED:5265d98e] + // reference time, reference time, absolute time; the arrival time of each media packet in this packet is calculated based on this reference time + uint8_t ref_time[3]; + // feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 | [AUTO-TRANSLATED:1ff6d73e] + // feedback packet count, feedback packet number, this packet is the nth transport-cc packet, incremented by 1 each time | + uint8_t fb_pkt_count; +}; +#pragma pack(pop) +} // namespace mediakit +#endif // ZLMEDIAKIT_RTCPFCI_H diff --git a/MediaServer/Rtmp/FlvMuxer.cpp b/MediaServer/Rtmp/FlvMuxer.cpp new file mode 100644 index 0000000..936a9b9 --- /dev/null +++ b/MediaServer/Rtmp/FlvMuxer.cpp @@ -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 "FlvMuxer.h" +#include "Util/File.h" +#include "Rtmp/utils.h" +#include "Http/HttpSession.h" + +#define FILE_BUF_SIZE (64 * 1024) + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +FlvMuxer::FlvMuxer(){ + _packet_pool.setSize(64); +} + +void FlvMuxer::start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &media, uint32_t start_pts) { + if (!media) { + throw std::runtime_error("RtmpMediaSource 无效"); + } + if (!poller->isCurrentThread()) { + weak_ptr weak_self = getSharedPtr(); + // 延时两秒启动录制,目的是为了等待config帧收集完毕 [AUTO-TRANSLATED:d359f59d] + // Start recording after a delay of two seconds, the purpose is to wait for the config frame to be collected. + poller->doDelayTask(2000, [weak_self, poller, media, start_pts]() { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->start(poller, media, start_pts); + } + return 0; + }); + return; + } + + onWriteFlvHeader(media); + + std::weak_ptr weak_self = getSharedPtr(); + media->pause(false); + _ring_reader = media->getRing()->attach(poller); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(dynamic_pointer_cast(weak_self.lock())); + return ret; + }); + _ring_reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onDetach(); + }); + + bool check = start_pts > 0; + _ring_reader->setReadCB([weak_self, start_pts, check](const RtmpMediaSource::RingDataType &pkt) mutable { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + size_t i = 0; + auto size = pkt->size(); + pkt->for_each([&](const RtmpPacket::Ptr &rtmp) { + if (check) { + if (rtmp->time_stamp < start_pts) { + return; + } + check = false; + } + strong_self->onWriteRtmp(rtmp, ++i == size); + }); + }); +} + +BufferRaw::Ptr FlvMuxer::obtainBuffer() { + return _packet_pool.obtain2(); +} + +BufferRaw::Ptr FlvMuxer::obtainBuffer(const void *data, size_t len) { + auto buffer = obtainBuffer(); + buffer->assign((const char *) data, len); + return buffer; +} + +void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) { + // 发送flv文件头 [AUTO-TRANSLATED:ee2c5556] + // Send the flv file header. + auto buffer = obtainBuffer(); + buffer->setCapacity(sizeof(FLVHeader)); + buffer->setSize(sizeof(FLVHeader)); + + FLVHeader *header = (FLVHeader *) buffer->data(); + memset(header, 0, sizeof(FLVHeader)); + header->flv[0] = 'F'; + header->flv[1] = 'L'; + header->flv[2] = 'V'; + header->version = FLVHeader::kFlvVersion; + header->length = htonl(FLVHeader::kFlvHeaderLength); + header->have_video = src->haveVideo(); + header->have_audio = src->haveAudio(); + // memset时已经赋值为0 [AUTO-TRANSLATED:0f71eef1] + // It has already been assigned to 0 during memset. + //header->previous_tag_size0 = 0; + + //flv header + onWrite(buffer, false); + + // metadata + src->getMetaData([&](const AMFValue &metadata) { + AMFEncoder invoke; + invoke << "onMetaData" << metadata; + onWriteFlvTag(MSG_DATA, std::make_shared(invoke.data()), 0, false); + }); + + //config frame + src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { + onWriteRtmp(pkt, true); + }); +} + +void FlvMuxer::onWriteFlvTag(const RtmpPacket::Ptr &pkt, uint32_t time_stamp, bool flush) { + onWriteFlvTag(pkt->type_id, pkt, time_stamp, flush); +} + +void FlvMuxer::onWriteFlvTag(uint8_t type, const Buffer::Ptr &buffer, uint32_t time_stamp, bool flush) { + RtmpTagHeader header; + header.type = type; + set_be24(header.data_size, (uint32_t) buffer->size()); + header.timestamp_ex = (time_stamp >> 24) & 0xff; + set_be24(header.timestamp, time_stamp & 0xFFFFFF); + + //tag header + onWrite(obtainBuffer((char *) &header, sizeof(header)), false); + + //tag data + onWrite(buffer, false); + + //PreviousTagSize + uint32_t size = htonl((uint32_t) (buffer->size() + sizeof(header))); + onWrite(obtainBuffer((char *) &size, 4), flush); +} + +void FlvMuxer::onWriteRtmp(const RtmpPacket::Ptr &pkt, bool flush) { + onWriteFlvTag(pkt, pkt->time_stamp, flush); +} + +void FlvMuxer::stop() { + if (_ring_reader) { + _ring_reader.reset(); + onDetach(); + } +} + +///////////////////////////////////////////////////////FlvRecorder///////////////////////////////////////////////////// + +void FlvRecorder::startRecord(const EventPoller::Ptr &poller, const string &vhost, const string &app, const string &stream, const string &file_path) { + startRecord(poller, dynamic_pointer_cast(MediaSource::find(RTMP_SCHEMA, vhost, app, stream)), file_path); +} + +void FlvRecorder::startRecord(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &media, + const string &file_path) { + stop(); + lock_guard lck(_file_mtx); + // 开辟文件写缓存 [AUTO-TRANSLATED:22d1c17f] + // Allocate file write cache. + std::shared_ptr fileBuf(new char[FILE_BUF_SIZE], [](char *ptr) { + if (ptr) { + delete[] ptr; + } + }); + // 新建文件 [AUTO-TRANSLATED:f3d512a6] + // Create a new file. + _file.reset(File::create_file(file_path, "wb"), [fileBuf](FILE *fp) { + if (fp) { + fflush(fp); + fclose(fp); + } + }); + if (!_file) { + throw std::runtime_error(StrPrinter << "打开文件失败:" << file_path); + } + + // 设置文件写缓存 [AUTO-TRANSLATED:a767e55c] + // Set the file write cache. + setvbuf(_file.get(), fileBuf.get(), _IOFBF, FILE_BUF_SIZE); + start(poller, media); +} + +void FlvRecorder::onWrite(const Buffer::Ptr &data, bool flush) { + lock_guard lck(_file_mtx); + if (_file) { + fwrite(data->data(), data->size(), 1, _file.get()); + } +} + +void FlvRecorder::onDetach() { + lock_guard lck(_file_mtx); + _file.reset(); +} + +std::shared_ptr FlvRecorder::getSharedPtr() { + return shared_from_this(); +} + +}//namespace mediakit diff --git a/MediaServer/Rtmp/FlvMuxer.h b/MediaServer/Rtmp/FlvMuxer.h new file mode 100644 index 0000000..01e34a8 --- /dev/null +++ b/MediaServer/Rtmp/FlvMuxer.h @@ -0,0 +1,65 @@ +/* + * 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_FLVMUXER_H +#define ZLMEDIAKIT_FLVMUXER_H + +#include "Rtmp/Rtmp.h" +#include "Rtmp/RtmpMediaSource.h" +#include "Poller/EventPoller.h" + +namespace mediakit { + +class FlvMuxer { +public: + using Ptr = std::shared_ptr; + FlvMuxer(); + virtual ~FlvMuxer() = default; + + void stop(); + +protected: + void start(const toolkit::EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &media, uint32_t start_pts = 0); + virtual void onWrite(const toolkit::Buffer::Ptr &data, bool flush) = 0; + virtual void onDetach() = 0; + virtual std::shared_ptr getSharedPtr() = 0; + +private: + void onWriteFlvHeader(const RtmpMediaSource::Ptr &src); + void onWriteRtmp(const RtmpPacket::Ptr &pkt, bool flush); + void onWriteFlvTag(const RtmpPacket::Ptr &pkt, uint32_t time_stamp, bool flush); + void onWriteFlvTag(uint8_t type, const toolkit::Buffer::Ptr &buffer, uint32_t time_stamp, bool flush); + toolkit::BufferRaw::Ptr obtainBuffer(const void *data, size_t len); + toolkit::BufferRaw::Ptr obtainBuffer(); + +private: + toolkit::ResourcePool _packet_pool; + RtmpMediaSource::RingType::RingReader::Ptr _ring_reader; +}; + +class FlvRecorder : public FlvMuxer , public std::enable_shared_from_this{ +public: + using Ptr = std::shared_ptr; + + void startRecord(const toolkit::EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &media, const std::string &file_path); + void startRecord(const toolkit::EventPoller::Ptr &poller, const std::string &vhost, const std::string &app, const std::string &stream, const std::string &file_path); + +private: + virtual void onWrite(const toolkit::Buffer::Ptr &data, bool flush) override ; + virtual void onDetach() override; + virtual std::shared_ptr getSharedPtr() override; + +private: + std::shared_ptr _file; + std::recursive_mutex _file_mtx; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVMUXER_H diff --git a/MediaServer/Rtmp/FlvPlayer.cpp b/MediaServer/Rtmp/FlvPlayer.cpp new file mode 100644 index 0000000..8349ab8 --- /dev/null +++ b/MediaServer/Rtmp/FlvPlayer.cpp @@ -0,0 +1,79 @@ +/* + * 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 "FlvPlayer.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) { + setPoller(poller); +} + +void FlvPlayer::play(const string &url) { + TraceL << "play http-flv: " << url; + _play_result = false; + setProxyUrl((*this)[Client::kProxyUrl]); + setHeaderTimeout((*this)[Client::kTimeoutMS].as()); + setBodyTimeout((*this)[Client::kMediaTimeoutMS].as()); + setMethod("GET"); + sendRequest(url); +} + +void FlvPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) { + if (status != "200" && status != "206") { + // http状态码不符合预期 [AUTO-TRANSLATED:2b6996f7] + // HTTP status code does not meet expectations + throw invalid_argument("bad http status code:" + status); + } + + auto content_type = const_cast(header)["Content-Type"]; + if (content_type.find("video/x-flv") != 0) { + throw invalid_argument("content type not http-flv: " + content_type); + } +} + +void FlvPlayer::teardown() { + HttpClientImp::shutdown(); +} + +void FlvPlayer::onResponseCompleted(const SockException &ex) { + if (!_play_result) { + _play_result = true; + onPlayResult(ex); + } else { + onShutdown(ex); + } +} + +void FlvPlayer::onResponseBody(const char *buf, size_t size) { + if (!_benchmark_mode) { + // 性能测试模式不做数据解析,节省cpu [AUTO-TRANSLATED:53e4af73] + // Performance test mode does not parse data to save CPU + FlvSplitter::input(buf, size); + } +} + +bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) { + return onMetadata(metadata); +} + +void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) { + if (!_play_result && !packet->isConfigFrame()) { + _play_result = true; + _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + onPlayResult(SockException(Err_success, "play http-flv success")); + } + onRtmpPacket(std::move(packet)); +} + +}//mediakit \ No newline at end of file diff --git a/MediaServer/Rtmp/FlvPlayer.h b/MediaServer/Rtmp/FlvPlayer.h new file mode 100644 index 0000000..12ba54e --- /dev/null +++ b/MediaServer/Rtmp/FlvPlayer.h @@ -0,0 +1,48 @@ +/* + * 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_FLVPLAYER_H +#define ZLMEDIAKIT_FLVPLAYER_H + +#include "FlvSplitter.h" +#include "Http/HttpClientImp.h" +#include "Player/PlayerBase.h" + +namespace mediakit { + +class FlvPlayer : public PlayerBase, public HttpClientImp, private FlvSplitter { +public: + FlvPlayer(const toolkit::EventPoller::Ptr &poller); + + void play(const std::string &url) override; + void teardown() override; + +protected: + void onResponseHeader(const std::string &status, const HttpHeader &header) override; + void onResponseCompleted(const toolkit::SockException &ex) override; + void onResponseBody(const char *buf, size_t size) override; + +protected: + virtual void onRtmpPacket(RtmpPacket::Ptr packet) = 0; + virtual bool onMetadata(const AMFValue &metadata) = 0; + +private: + bool onRecvMetadata(const AMFValue &metadata) override; + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override; + +private: + bool _play_result = false; + bool _benchmark_mode = false; +}; + +using FlvPlayerImp = FlvPlayerBase; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVPLAYER_H diff --git a/MediaServer/Rtmp/FlvSplitter.cpp b/MediaServer/Rtmp/FlvSplitter.cpp new file mode 100644 index 0000000..7898e23 --- /dev/null +++ b/MediaServer/Rtmp/FlvSplitter.cpp @@ -0,0 +1,136 @@ +/* + * 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 "FlvSplitter.h" +#include "utils.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +const char *FlvSplitter::onSearchPacketTail(const char *data, size_t len) { + if (!_flv_started) { + // 还没获取到flv头 [AUTO-TRANSLATED:d1c8deaa] + // Not yet got the flv header + if (len < sizeof(FLVHeader)) { + // 数据不够 [AUTO-TRANSLATED:72802244] + // Insufficient data + return nullptr; + } + return data + sizeof(FLVHeader); + } + + // 获取到flv头,处理tag数据 [AUTO-TRANSLATED:a15a91da] + // Got the flv header, processing tag data + if (len < sizeof(RtmpTagHeader)) { + // 数据不够 [AUTO-TRANSLATED:72802244] + // Insufficient data + return nullptr; + } + return data + sizeof(RtmpTagHeader); +} + +ssize_t FlvSplitter::onRecvHeader(const char *data, size_t len) { + if (!_flv_started) { + // 获取到flv头了 [AUTO-TRANSLATED:1da417c0] + // Got the flv header + auto header = reinterpret_cast(data); + if (memcmp(header->flv, "FLV", 3)) { + throw std::invalid_argument("不是flv容器格式!"); + } + if (header->version != FLVHeader::kFlvVersion) { + throw std::invalid_argument("flv头中version字段不正确"); + } + if (!header->have_video && !header->have_audio) { + throw std::invalid_argument("flv头中声明音频和视频都不存在"); + } + if (FLVHeader::kFlvHeaderLength != ntohl(header->length)) { + throw std::invalid_argument("flv头中length字段非法"); + } + if (0 != ntohl(header->previous_tag_size0)) { + throw std::invalid_argument("flv头中previous tag size字段非法"); + } + onRecvFlvHeader(*header); + _flv_started = true; + return 0; + } + + // 获取到flv头,处理tag数据 [AUTO-TRANSLATED:a15a91da] + // Got the flv header, processing tag data + auto tag = reinterpret_cast(data); + auto data_size = load_be24(tag->data_size); + _type = tag->type; + _time_stamp = load_be24(tag->timestamp); + _time_stamp |= (tag->timestamp_ex << 24); + return data_size + 4/*PreviousTagSize*/; +} + +void FlvSplitter::onRecvContent(const char *data, size_t len) { + len -= 4; + auto previous_tag_size = load_be32(data + len); + if (len != previous_tag_size - sizeof(RtmpTagHeader)) { + WarnL << "flv previous tag size 字段非法:" << len << " != " << previous_tag_size - sizeof(RtmpTagHeader); + } + RtmpPacket::Ptr packet; + switch (_type) { + case MSG_AUDIO : { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_AUDIO; + packet->stream_index = STREAM_MEDIA; + break; + } + case MSG_VIDEO: { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_VIDEO; + packet->stream_index = STREAM_MEDIA; + break; + } + + case MSG_DATA: + case MSG_DATA3: { + BufferLikeString buffer(string(data, len)); + AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0); + auto first = dec.load(); + bool flag = true; + if (first.type() == AMFType::AMF_STRING) { + auto type = first.as_string(); + if (type == "@setDataFrame") { + type = dec.load(); + if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown type:" << type; + } + } else if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown notify:" << type; + } + } else { + WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string(); + } + if (!flag) { + throw std::invalid_argument("check rtmp metadata failed"); + } + return; + } + + default: WarnL << "不识别的flv msg type:" << (int) _type; return; + } + + packet->time_stamp = _time_stamp; + packet->type_id = _type; + packet->body_size = len; + packet->buffer.assign(data, len); + onRecvRtmpPacket(std::move(packet)); +} + + +}//namespace mediakit \ No newline at end of file diff --git a/MediaServer/Rtmp/FlvSplitter.h b/MediaServer/Rtmp/FlvSplitter.h new file mode 100644 index 0000000..cde80d8 --- /dev/null +++ b/MediaServer/Rtmp/FlvSplitter.h @@ -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 ZLMEDIAKIT_FLVSPLITTER_H +#define ZLMEDIAKIT_FLVSPLITTER_H + +#include "Rtmp.h" +#include "Http/HttpRequestSplitter.h" +#include "RtmpPlayerImp.h" + +namespace mediakit { + +class FlvSplitter : public HttpRequestSplitter { +protected: + void onRecvContent(const char *data,size_t len) override; + ssize_t onRecvHeader(const char *data,size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + +protected: + virtual void onRecvFlvHeader(const FLVHeader &header) {}; + virtual bool onRecvMetadata(const AMFValue &metadata) = 0; + virtual void onRecvRtmpPacket(RtmpPacket::Ptr packet) = 0; + +private: + bool _flv_started = false; + uint8_t _type; + uint32_t _time_stamp; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVSPLITTER_H diff --git a/MediaServer/Rtmp/Rtmp.cpp b/MediaServer/Rtmp/Rtmp.cpp new file mode 100644 index 0000000..d1d44c1 --- /dev/null +++ b/MediaServer/Rtmp/Rtmp.cpp @@ -0,0 +1,311 @@ +/* + * 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 "Rtmp.h" +#include "Common/config.h" +#include "Extension/Factory.h" + +namespace mediakit { + +TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) { + _metadata.set("duration", dur_sec); + _metadata.set("fileSize", (int)fileSize); + _metadata.set("title", std::string("Streamed by ") + kServerName); + for (auto &pr : header) { + _metadata.set(pr.first, pr.second); + } +} + +VideoMeta::VideoMeta(const VideoTrack::Ptr &video) { + if (video->getVideoWidth() > 0) { + _metadata.set("width", video->getVideoWidth()); + } + if (video->getVideoHeight() > 0) { + _metadata.set("height", video->getVideoHeight()); + } + if (video->getVideoFps() > 0) { + _metadata.set("framerate", video->getVideoFps()); + } + if (video->getBitRate()) { + _metadata.set("videodatarate", video->getBitRate() / 1024); + } + _metadata.set("videocodecid", Factory::getAmfByCodecId(video->getCodecId())); +} + +AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) { + if (audio->getBitRate()) { + _metadata.set("audiodatarate", audio->getBitRate() / 1024); + } + if (audio->getAudioSampleRate() > 0) { + _metadata.set("audiosamplerate", audio->getAudioSampleRate()); + } + if (audio->getAudioSampleBit() > 0) { + _metadata.set("audiosamplesize", audio->getAudioSampleBit()); + } + if (audio->getAudioChannel() > 0) { + _metadata.set("stereo", audio->getAudioChannel() > 1); + } + _metadata.set("audiocodecid", Factory::getAmfByCodecId(audio->getCodecId())); +} + +uint8_t getAudioRtmpFlags(const Track::Ptr &track) { + track->update(); + switch (track->getTrackType()) { + case TrackAudio: { + auto audioTrack = std::dynamic_pointer_cast(track); + if (!audioTrack) { + WarnL << "获取AudioTrack失败"; + return 0; + } + auto iSampleRate = audioTrack->getAudioSampleRate(); + auto iChannel = audioTrack->getAudioChannel(); + auto iSampleBit = audioTrack->getAudioSampleBit(); + + uint8_t flvAudioType; + switch (track->getCodecId()) { + case CodecG711A: flvAudioType = (uint8_t)RtmpAudioCodec::g711a; break; + case CodecG711U: flvAudioType = (uint8_t)RtmpAudioCodec::g711u; break; + case CodecOpus: { + flvAudioType = (uint8_t)RtmpAudioCodec::opus; + // opus不通过flags获取音频相关信息 [AUTO-TRANSLATED:0ddf328b] + // opus does not get audio information through flags + iSampleRate = 44100; + iSampleBit = 16; + iChannel = 2; + break; + } + case CodecAAC: { + flvAudioType = (uint8_t)RtmpAudioCodec::aac; + // aac不通过flags获取音频相关信息 [AUTO-TRANSLATED:63ac5081] + // aac does not get audio information through flags + iSampleRate = 44100; + iSampleBit = 16; + iChannel = 2; + break; + } + default: WarnL << "该编码格式不支持转换为RTMP: " << track->getCodecName(); return 0; + } + + uint8_t flvSampleRate; + switch (iSampleRate) { + case 44100: flvSampleRate = 3; break; + case 22050: flvSampleRate = 2; break; + case 11025: flvSampleRate = 1; break; + case 16000: // nellymoser only + case 8000: // nellymoser only + case 5512: // not MP3 + flvSampleRate = 0; + break; + default: WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; return 0; + } + + uint8_t flvStereoOrMono = (iChannel > 1); + uint8_t flvSampleBit = iSampleBit == 16; + return (flvAudioType << 4) | (flvSampleRate << 2) | (flvSampleBit << 1) | flvStereoOrMono; + } + + default: return 0; + } +} + +void Metadata::addTrack(AMFValue &metadata, const Track::Ptr &track) { + Metadata::Ptr new_metadata; + track->update(); + switch (track->getTrackType()) { + case TrackVideo: { + new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); + break; + } + case TrackAudio: { + new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); + break; + } + default: return; + } + + new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { metadata.set(key, value); }); +} + +RtmpPacket::Ptr RtmpPacket::create() { +#if 0 + static ResourcePool packet_pool; + static onceToken token([]() { + packet_pool.setSize(1024); + }); + auto ret = packet_pool.obtain2(); + ret->clear(); + return ret; +#else + return Ptr(new RtmpPacket); +#endif +} + +void RtmpPacket::clear() { + is_abs_stamp = false; + time_stamp = 0; + ts_field = 0; + body_size = 0; + buffer.clear(); +} + +bool RtmpPacket::isVideoKeyFrame() const { + if (type_id != MSG_VIDEO) { + return false; + } + RtmpFrameType frame_type; + if (buffer[0] >> 7) { + // IsExHeader == 1 + frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07); + } else { + // IsExHeader == 0 + frame_type = (RtmpFrameType)(buffer[0] >> 4); + } + return frame_type == RtmpFrameType::key_frame; +} + +bool RtmpPacket::isConfigFrame() const { + switch (type_id) { + case MSG_AUDIO: { + return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; + } + case MSG_VIDEO: { + if (!isVideoKeyFrame()) { + return false; + } + if (buffer[0] >> 7) { + // IsExHeader == 1 + return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart; + } + // IsExHeader == 0 + switch ((RtmpVideoCodec)getRtmpCodecId()) { + case RtmpVideoCodec::h265: + case RtmpVideoCodec::h264: { + return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header; + } + default: return false; + } + } + default: return false; + } +} + +int RtmpPacket::getRtmpCodecId() const { + switch (type_id) { + case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; + case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; + default: return 0; + } +} + +int RtmpPacket::getAudioSampleRate() const { + if (type_id != MSG_AUDIO) { + return 0; + } + int flvSampleRate = ((uint8_t)buffer[0] & 0x0C) >> 2; + const static int sampleRate[] = { 5512, 11025, 22050, 44100 }; + return sampleRate[flvSampleRate]; +} + +int RtmpPacket::getAudioSampleBit() const { + if (type_id != MSG_AUDIO) { + return 0; + } + int flvSampleBit = ((uint8_t)buffer[0] & 0x02) >> 1; + const static int sampleBit[] = { 8, 16 }; + return sampleBit[flvSampleBit]; +} + +int RtmpPacket::getAudioChannel() const { + if (type_id != MSG_AUDIO) { + return 0; + } + int flvStereoOrMono = (uint8_t)buffer[0] & 0x01; + const static int channel[] = { 1, 2 }; + return channel[flvStereoOrMono]; +} + +RtmpPacket &RtmpPacket::operator=(const RtmpPacket &that) { + is_abs_stamp = that.is_abs_stamp; + stream_index = that.stream_index; + body_size = that.body_size; + type_id = that.type_id; + ts_field = that.ts_field; + time_stamp = that.time_stamp; + return *this; +} + +RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) { + _time = htonl(_time); + memcpy(time_stamp, &_time, 4); + if (!_random) { + random_generate((char *)random, sizeof(random)); + } else { + memcpy(random, _random, sizeof(random)); + } +} + +void RtmpHandshake::random_generate(char *bytes, int size) { + static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x6e, + 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6d }; + for (int i = 0; i < size; i++) { + bytes[i] = cdata[rand() % (sizeof(cdata) - 1)]; + } +} + +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) { + RtmpPacketInfo save; + info = info ? info : &save; + info->codec = CodecInvalid; + + CHECK(size > 0); + RtmpVideoHeaderEnhanced *enhanced_header = (RtmpVideoHeaderEnhanced *)data; + if (enhanced_header->enhanced) { + // IsExHeader == 1 + CHECK(size > RtmpPacketInfo::kEnhancedRtmpHeaderSize, "Invalid rtmp buffer size: ", size); + info->is_enhanced = true; + info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type); + info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type); + + switch ((RtmpVideoCodec)ntohl(enhanced_header->fourcc)) { + case RtmpVideoCodec::fourcc_av1: info->codec = CodecAV1; break; + case RtmpVideoCodec::fourcc_vp9: info->codec = CodecVP9; break; + case RtmpVideoCodec::fourcc_hevc: info->codec = CodecH265; break; + default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); + } + } else { + // IsExHeader == 0 + RtmpVideoHeaderClassic *classic_header = (RtmpVideoHeaderClassic *)data; + info->is_enhanced = false; + info->video.frame_type = (RtmpFrameType)(classic_header->frame_type); + switch ((RtmpVideoCodec)(classic_header->codec_id)) { + case RtmpVideoCodec::h264: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH264; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; + break; + } + case RtmpVideoCodec::h265: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH265; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; + break; + } + default: WarnL << "Rtmp video codec not supported: " << (int)classic_header->codec_id; break; + } + } + return info->codec; +} + +} // namespace mediakit + +namespace toolkit { +StatisticImp(mediakit::RtmpPacket); +} \ No newline at end of file diff --git a/MediaServer/Rtmp/Rtmp.h b/MediaServer/Rtmp/Rtmp.h new file mode 100644 index 0000000..fd02da7 --- /dev/null +++ b/MediaServer/Rtmp/Rtmp.h @@ -0,0 +1,405 @@ +/* + * 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 __rtmp_h +#define __rtmp_h + +#include +#include +#include +#include "amf.h" +#include "Network/Buffer.h" +#include "Extension/Track.h" + +#define DEFAULT_CHUNK_LEN 128 +#define HANDSHAKE_PLAINTEXT 0x03 +#define RANDOM_LEN (1536 - 8) + +#define MSG_SET_CHUNK 1 /*Set Chunk Size (1)*/ +#define MSG_ABORT 2 /*Abort Message (2)*/ +#define MSG_ACK 3 /*Acknowledgement (3)*/ +#define MSG_USER_CONTROL 4 /*User Control Messages (4)*/ +#define MSG_WIN_SIZE 5 /*Window Acknowledgement Size (5)*/ +#define MSG_SET_PEER_BW 6 /*Set Peer Bandwidth (6)*/ +#define MSG_AUDIO 8 /*Audio Message (8)*/ +#define MSG_VIDEO 9 /*Video Message (9)*/ +#define MSG_DATA 18 /*Data Message (18, 15) AMF0*/ +#define MSG_DATA3 15 /*Data Message (18, 15) AMF3*/ +#define MSG_CMD 20 /*Command Message AMF0 */ +#define MSG_CMD3 17 /*Command Message AMF3 */ +#define MSG_OBJECT3 16 /*Shared Object Message (19, 16) AMF3*/ +#define MSG_OBJECT 19 /*Shared Object Message (19, 16) AMF0*/ +#define MSG_AGGREGATE 22 /*Aggregate Message (22)*/ + +#define CONTROL_STREAM_BEGIN 0 +#define CONTROL_STREAM_EOF 1 +#define CONTROL_STREAM_DRY 2 +#define CONTROL_SETBUFFER 3 +#define CONTROL_STREAM_ISRECORDED 4 +#define CONTROL_PING_REQUEST 6 +#define CONTROL_PING_RESPONSE 7 + +#define STREAM_CONTROL 0 +#define STREAM_MEDIA 1 + +#define CHUNK_NETWORK 2 /*网络相关的消息(参见 Protocol Control Messages)*/ +#define CHUNK_SYSTEM 3 /*向服务器发送控制消息(反之亦可)*/ +#define CHUNK_CLIENT_REQUEST_BEFORE 3 /*客户端在createStream前,向服务器发出请求的chunkID*/ +#define CHUNK_CLIENT_REQUEST_AFTER 4 /*客户端在createStream后,向服务器发出请求的chunkID*/ +#define CHUNK_AUDIO 6 /*音频chunkID*/ +#define CHUNK_VIDEO 7 /*视频chunkID*/ + +namespace mediakit { + +#pragma pack(push, 1) + +class RtmpHandshake { +public: + RtmpHandshake(uint32_t _time, uint8_t *_random = nullptr); + + uint8_t time_stamp[4]; + uint8_t zero[4] = {0}; + uint8_t random[RANDOM_LEN]; + + void random_generate(char *bytes, int size); + + void create_complex_c0c1(); + +}; + +class RtmpHeader { +public: +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t fmt : 2; + uint8_t chunk_id : 6; +#else + uint8_t chunk_id : 6; + // 0、1、2、3分别对应 12、8、4、1长度 [AUTO-TRANSLATED:31d67e40] + // 0, 1, 2, 3 correspond to lengths of 12, 8, 4, 1 respectively + uint8_t fmt : 2; +#endif + uint8_t time_stamp[3]; + uint8_t body_size[3]; + uint8_t type_id; + uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */ +}; + +class FLVHeader { +public: + static constexpr uint8_t kFlvVersion = 1; + static constexpr uint8_t kFlvHeaderLength = 9; + //FLV + char flv[3]; + //File version (for example, 0x01 for FLV version 1) + uint8_t version; +#if __BYTE_ORDER == __BIG_ENDIAN + // 保留,置0 [AUTO-TRANSLATED:46985374] + // Preserve, set to 0 + uint8_t : 5; + // 是否有音频 [AUTO-TRANSLATED:9467870a] + // Whether there is audio + uint8_t have_audio: 1; + // 保留,置0 [AUTO-TRANSLATED:46985374] + // Preserve, set to 0 + uint8_t : 1; + // 是否有视频 [AUTO-TRANSLATED:42d0ed81] + // Whether there is video + uint8_t have_video: 1; +#else + // 是否有视频 [AUTO-TRANSLATED:42d0ed81] + // Whether there is video + uint8_t have_video: 1; + // 保留,置0 [AUTO-TRANSLATED:46985374] + // Preserve, set to 0 + uint8_t : 1; + // 是否有音频 [AUTO-TRANSLATED:9467870a] + // Whether there is audio + uint8_t have_audio: 1; + // 保留,置0 [AUTO-TRANSLATED:46985374] + // Preserve, set to 0 + uint8_t : 5; +#endif + // The length of this header in bytes,固定为9 [AUTO-TRANSLATED:126988fc] + // The length of this header in bytes, fixed to 9 + uint32_t length; + // 固定为0 [AUTO-TRANSLATED:d266c0a7] + // Fixed to 0 + uint32_t previous_tag_size0; +}; + +class RtmpTagHeader { +public: + uint8_t type = 0; + uint8_t data_size[3] = {0}; + uint8_t timestamp[3] = {0}; + uint8_t timestamp_ex = 0; + uint8_t streamid[3] = {0}; /* Always 0. */ +}; + +struct RtmpVideoHeaderEnhanced { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t enhanced : 1; + uint8_t frame_type : 3; + uint8_t pkt_type : 4; + uint32_t fourcc; +#else + uint8_t pkt_type : 4; + uint8_t frame_type : 3; + uint8_t enhanced : 1; + uint32_t fourcc; +#endif +}; + +struct RtmpVideoHeaderClassic { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t frame_type : 4; + uint8_t codec_id : 4; + uint8_t h264_pkt_type; +#else + uint8_t codec_id : 4; + uint8_t frame_type : 4; + uint8_t h264_pkt_type; +#endif +}; + +#pragma pack(pop) + +class RtmpPacket : public toolkit::Buffer{ +public: + friend class RtmpProtocol; + using Ptr = std::shared_ptr; + bool is_abs_stamp; + uint8_t type_id; + uint32_t time_stamp; + uint32_t ts_field; + uint32_t stream_index; + uint32_t chunk_id; + size_t body_size; + toolkit::BufferLikeString buffer; + +public: + static Ptr create(); + + char *data() const override{ + return (char*)buffer.data(); + } + size_t size() const override { + return buffer.size(); + } + + void clear(); + + // video config frame和key frame都返回true [AUTO-TRANSLATED:de025c52] + // video config frame and key frame both return true + // 用于gop缓存定位 [AUTO-TRANSLATED:828204e5] + // Used for gop cache positioning + bool isVideoKeyFrame() const; + + // aac config或h264/h265 config返回true,支持增强型rtmp [AUTO-TRANSLATED:221955ec] + // aac config or h264/h265 config returns true, supports enhanced rtmp + // 用于缓存解码配置信息 [AUTO-TRANSLATED:19304f64] + // Used to cache decoding configuration information + bool isConfigFrame() const; + + int getRtmpCodecId() const; + int getAudioSampleRate() const; + int getAudioSampleBit() const; + int getAudioChannel() const; + +private: + friend class toolkit::ResourcePool_l; + RtmpPacket(){ + clear(); + } + + RtmpPacket &operator=(const RtmpPacket &that); + +private: + // 对象个数统计 [AUTO-TRANSLATED:3b43e8c2] + // Object count statistics + toolkit::ObjectStatistic _statistic; +}; + +/** + * rtmp metadata基类,用于描述rtmp格式信息 + * rtmp metadata base class, used to describe rtmp format information + + * [AUTO-TRANSLATED:8ced489c] + */ +class Metadata { +public: + using Ptr = std::shared_ptr; + + Metadata(): _metadata(AMF_OBJECT) {} + const AMFValue &getMetadata() const{ + return _metadata; + } + + static void addTrack(AMFValue &metadata, const Track::Ptr &track); + +protected: + AMFValue _metadata; +}; + +/** +* metadata中除音视频外的其他描述部分 + * Other descriptive parts in metadata besides audio and video + + * [AUTO-TRANSLATED:e11f031f] +*/ +class TitleMeta : public Metadata { +public: + using Ptr = std::shared_ptr; + + TitleMeta(float dur_sec = 0, + size_t fileSize = 0, + const std::map &header = std::map()); + +}; + +class VideoMeta : public Metadata { +public: + using Ptr = std::shared_ptr; + + VideoMeta(const VideoTrack::Ptr &video); +}; + +class AudioMeta : public Metadata { +public: + using Ptr = std::shared_ptr; + + AudioMeta(const AudioTrack::Ptr &audio); +}; + +// 根据音频track获取flags [AUTO-TRANSLATED:a25fdd07] +// Get flags based on audio track +uint8_t getAudioRtmpFlags(const Track::Ptr &track); + +////////////////// rtmp video ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Type of video frame. +enum class RtmpFrameType : uint8_t { + reserved = 0, + key_frame = 1, // key frame (for AVC, a seekable frame) + inter_frame = 2, // inter frame (for AVC, a non-seekable frame) + disposable_inter_frame = 3, // disposable inter frame (H.263 only) + generated_key_frame = 4, // generated key frame (reserved for server use only) + video_info_frame = 5, // video info/command frame +}; + +#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + +// UB [4]; Codec Identifier. +enum class RtmpVideoCodec : uint32_t { + h263 = 2, // Sorenson H.263 + screen_video = 3, // Screen video + vp6 = 4, // On2 VP6 + vp6_alpha = 5, // On2 VP6 with alpha channel + screen_video2 = 6, // Screen video version 2 + h264 = 7, // avc + h265 = 12, // 国内扩展 + + // 增强型rtmp FourCC [AUTO-TRANSLATED:442b77fb] + // Enhanced rtmp FourCC + fourcc_vp9 = MKBETAG('v', 'p', '0', '9'), + fourcc_av1 = MKBETAG('a', 'v', '0', '1'), + fourcc_hevc = MKBETAG('h', 'v', 'c', '1') +}; + +// UI8; +enum class RtmpH264PacketType : uint8_t { + h264_config_header = 0, // AVC or HEVC sequence header(sps/pps) + h264_nalu = 1, // AVC or HEVC NALU + h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) +}; + +// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf +// UB[4] +enum class RtmpPacketType : uint8_t { + PacketTypeSequenceStart = 0, + PacketTypeCodedFrames = 1, + PacketTypeSequenceEnd = 2, + + // CompositionTime Offset is implied to equal zero. This is + // an optimization to save putting SI24 composition time value of zero on + // the wire. See pseudo code below in the VideoTagBody section + PacketTypeCodedFramesX = 3, + + // VideoTagBody does not contain video data. VideoTagBody + // instead contains an AMF encoded metadata. See Metadata Frame + // section for an illustration of its usage. As an example, the metadata + // can be HDR information. This is a good way to signal HDR + // information. This also opens up future ways to express additional + // metadata that is meant for the next video sequence. + // + // note: presence of PacketTypeMetadata means that FrameType + // flags at the top of this table should be ignored + PacketTypeMetadata = 4, + + // Carriage of bitstream in MPEG-2 TS format + // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart + // are mutually exclusive + PacketTypeMPEG2TSSequenceStart = 5, +}; + +////////////////// rtmp audio ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Format of SoundData +enum class RtmpAudioCodec : uint8_t { + /** + 0 = Linear PCM, platform endian + 1 = ADPCM + 2 = MP3 + 3 = Linear PCM, little endian + 4 = Nellymoser 16 kHz mono + 5 = Nellymoser 8 kHz mono + 6 = Nellymoser + 7 = G.711 A-law logarithmic PCM + 8 = G.711 mu-law logarithmic PCM + 9 = reserved + 10 = AAC + 11 = Speex + 14 = MP3 8 kHz + 15 = Device-specific sound + */ + g711a = 7, + g711u = 8, + aac = 10, + opus = 13 // 国内扩展 +}; + +// UI8; +enum class RtmpAACPacketType : uint8_t { + aac_config_header = 0, // AAC sequence header + aac_raw = 1, // AAC raw +}; + +//////////////////////////////////////////// + +struct RtmpPacketInfo { + enum { kEnhancedRtmpHeaderSize = sizeof(RtmpVideoHeaderEnhanced) }; + + CodecId codec = CodecInvalid; + bool is_enhanced; + union { + struct { + RtmpFrameType frame_type; + RtmpPacketType pkt_type; // enhanced = true + RtmpH264PacketType h264_pkt_type; // enhanced = false + } video; + }; +}; +// https://github.com/veovera/enhanced-rtmp +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); + +}//namespace mediakit +#endif//__rtmp_h diff --git a/MediaServer/Rtmp/RtmpCodec.h b/MediaServer/Rtmp/RtmpCodec.h new file mode 100644 index 0000000..17caae1 --- /dev/null +++ b/MediaServer/Rtmp/RtmpCodec.h @@ -0,0 +1,73 @@ +/* + * 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_RTMPCODEC_H +#define ZLMEDIAKIT_RTMPCODEC_H + +#include "Rtmp/Rtmp.h" +#include "Extension/Frame.h" +#include "Util/RingBuffer.h" + +namespace mediakit{ + +class RtmpRing { +public: + using Ptr = std::shared_ptr; + using RingType = toolkit::RingBuffer; + + virtual ~RtmpRing() = default; + + /** + * 设置rtmp环形缓存 + * Set rtmp ring buffer + + * [AUTO-TRANSLATED:0a25f795] + */ + void setRtmpRing(const RingType::Ptr &ring) { + _ring = ring; + } + + /** + * 输入rtmp包 + * @param rtmp rtmp包 + * Input rtmp packet + * @param rtmp rtmp packet + + + * [AUTO-TRANSLATED:3a0f0599] + */ + virtual void inputRtmp(const RtmpPacket::Ptr &rtmp) { + if (_ring) { + _ring->write(rtmp, rtmp->isVideoKeyFrame()); + } + } + +protected: + RingType::Ptr _ring; +}; + +class RtmpCodec : public RtmpRing, public FrameWriterInterface { +public: + using Ptr = std::shared_ptr; + RtmpCodec(Track::Ptr track) { _track = std::move(track); } + + virtual void makeConfigPacket() {} + + bool inputFrame(const Frame::Ptr &frame) override { return _track->inputFrame(frame); } + + const Track::Ptr &getTrack() const { return _track; } + +private: + Track::Ptr _track; +}; + + +}//namespace mediakit +#endif //ZLMEDIAKIT_RTMPCODEC_H diff --git a/MediaServer/Rtmp/RtmpDemuxer.cpp b/MediaServer/Rtmp/RtmpDemuxer.cpp new file mode 100644 index 0000000..faa31f6 --- /dev/null +++ b/MediaServer/Rtmp/RtmpDemuxer.cpp @@ -0,0 +1,194 @@ +/* + * 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 "RtmpCodec.h" +#include "RtmpDemuxer.h" +#include "Extension/Factory.h" + +using namespace std; + +namespace mediakit { + +size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { + size_t ret = 0; + metadata.object_for_each([&](const string &key, const AMFValue &val) { + if (key == "videocodecid") { + // 找到视频 [AUTO-TRANSLATED:e66249fc] + // Find video + ++ret; + return; + } + if (key == "audiocodecid") { + // 找到音频 [AUTO-TRANSLATED:126ce656] + // Find audio + ++ret; + return; + } + }); + return ret; +} + +bool RtmpDemuxer::loadMetaData(const AMFValue &val) { + bool ret = false; + try { + int audiosamplerate = 0; + int audiochannels = 0; + int audiosamplesize = 0; + int videodatarate = 0; + int audiodatarate = 0; + const AMFValue *audiocodecid = nullptr; + const AMFValue *videocodecid = nullptr; + val.object_for_each([&](const string &key, const AMFValue &val) { + if (key == "duration") { + _duration = (float)val.as_number(); + return; + } + if (key == "audiosamplerate") { + audiosamplerate = val.as_integer(); + return; + } + if (key == "audiosamplesize") { + audiosamplesize = val.as_integer(); + return; + } + if (key == "stereo") { + audiochannels = val.as_boolean() ? 2 : 1; + return; + } + if (key == "videocodecid") { + // 找到视频 [AUTO-TRANSLATED:e66249fc] + // Find video + videocodecid = &val; + return; + } + if (key == "audiocodecid") { + // 找到音频 [AUTO-TRANSLATED:126ce656] + // Find audio + audiocodecid = &val; + return; + } + if (key == "audiodatarate") { + audiodatarate = val.as_integer(); + return; + } + if (key == "videodatarate") { + videodatarate = val.as_integer(); + return; + } + }); + if (videocodecid) { + // 有视频 [AUTO-TRANSLATED:8d6ad811] + // Has video + ret = true; + makeVideoTrack(*videocodecid, videodatarate * 1024); + } + if (audiocodecid) { + // 有音频 [AUTO-TRANSLATED:8f9ac7f1] + // Has audio + ret = true; + makeAudioTrack(*audiocodecid, audiosamplerate, audiochannels, audiosamplesize, audiodatarate * 1024); + } + } catch (std::exception &ex) { + WarnL << ex.what(); + } + + if (ret) { + // metadata中存在track相关的描述,那么我们根据metadata判断有多少个track [AUTO-TRANSLATED:47e02e95] + // If there is a track-related description in the metadata, we determine the number of tracks based on the metadata + addTrackCompleted(); + } + return ret; +} + +float RtmpDemuxer::getDuration() const { + return _duration; +} + +void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { + switch (pkt->type_id) { + case MSG_VIDEO: { + if (!_try_get_video_track) { + _try_get_video_track = true; + auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size()); + makeVideoTrack(Factory::getTrackByCodecId(codec_id), 0); + } + if (_video_rtmp_decoder) { + _video_rtmp_decoder->inputRtmp(pkt); + } + break; + } + + case MSG_AUDIO: { + if (!_try_get_audio_track) { + _try_get_audio_track = true; + auto codec = AMFValue(pkt->getRtmpCodecId()); + makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit(), 0); + } + if (_audio_rtmp_decoder) { + _audio_rtmp_decoder->inputRtmp(pkt); + } + break; + } + default: break; + } +} + +void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec, int bit_rate) { + makeVideoTrack(Factory::getVideoTrackByAmf(videoCodec), bit_rate); +} + +void RtmpDemuxer::makeVideoTrack(const Track::Ptr &track, int bit_rate) { + if (_video_rtmp_decoder) { + return; + } + // 生成Track对象 [AUTO-TRANSLATED:8c7aee28] + // Generate Track object + _video_track = dynamic_pointer_cast(track); + if (!_video_track) { + return; + } + // 生成rtmpCodec对象以便解码rtmp [AUTO-TRANSLATED:a3c81353] + // Generate rtmpCodec object to decode rtmp + _video_rtmp_decoder = Factory::getRtmpDecoderByTrack(_video_track); + if (!_video_rtmp_decoder) { + // 找不到相应的rtmp解码器,该track无效 [AUTO-TRANSLATED:bbea0d74] + // Cannot find the corresponding rtmp decoder, the track is invalid + _video_track.reset(); + return; + } + _video_track->setBitRate(bit_rate); + addTrack(_video_track); + _try_get_video_track = true; +} + +void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec, int sample_rate, int channels, int sample_bit, int bit_rate) { + if (_audio_rtmp_decoder) { + return; + } + // 生成Track对象 [AUTO-TRANSLATED:8c7aee28] + // Generate Track object + _audio_track = dynamic_pointer_cast(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit)); + if (!_audio_track) { + return; + } + // 生成rtmpCodec对象以便解码rtmp [AUTO-TRANSLATED:a3c81353] + // Generate rtmpCodec object to decode rtmp + _audio_rtmp_decoder = Factory::getRtmpDecoderByTrack(_audio_track); + if (!_audio_rtmp_decoder) { + // 找不到相应的rtmp解码器,该track无效 [AUTO-TRANSLATED:bbea0d74] + // Cannot find the corresponding rtmp decoder, the track is invalid + _audio_track.reset(); + return; + } + _audio_track->setBitRate(bit_rate); + addTrack(_audio_track); + _try_get_audio_track = true; +} + +} /* namespace mediakit */ \ No newline at end of file diff --git a/MediaServer/Rtmp/RtmpDemuxer.h b/MediaServer/Rtmp/RtmpDemuxer.h new file mode 100644 index 0000000..e61bf87 --- /dev/null +++ b/MediaServer/Rtmp/RtmpDemuxer.h @@ -0,0 +1,69 @@ +/* + * 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_RTMP_RTMPDEMUXER_H_ +#define SRC_RTMP_RTMPDEMUXER_H_ + +#include +#include +#include "Rtmp/amf.h" +#include "Rtmp/Rtmp.h" +#include "Common/MediaSink.h" +#include "RtmpCodec.h" + +namespace mediakit { + +class RtmpDemuxer : public Demuxer { +public: + using Ptr = std::shared_ptr; + + static size_t trackCount(const AMFValue &metadata); + + bool loadMetaData(const AMFValue &metadata); + + /** + * 开始解复用 + * @param pkt rtmp包 + * Start demultiplexing + * @param pkt rtmp packet + + * [AUTO-TRANSLATED:3a6f81de] + */ + void inputRtmp(const RtmpPacket::Ptr &pkt); + + /** + * 获取节目总时长 + * @return 节目总时长,单位秒 + * Get the total duration of the program + * @return Total duration of the program, in seconds + + + * [AUTO-TRANSLATED:6b2ec56c] + */ + float getDuration() const; + +private: + void makeVideoTrack(const AMFValue &val, int bit_rate); + void makeVideoTrack(const Track::Ptr &val, int bit_rate); + void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit, int bit_rate); + +private: + bool _try_get_video_track = false; + bool _try_get_audio_track = false; + float _duration = 0; + AudioTrack::Ptr _audio_track; + VideoTrack::Ptr _video_track; + RtmpCodec::Ptr _audio_rtmp_decoder; + RtmpCodec::Ptr _video_rtmp_decoder; +}; + +} /* namespace mediakit */ + +#endif /* SRC_RTMP_RTMPDEMUXER_H_ */ diff --git a/MediaServer/Rtmp/RtmpMediaSource.h b/MediaServer/Rtmp/RtmpMediaSource.h new file mode 100644 index 0000000..0baab48 --- /dev/null +++ b/MediaServer/Rtmp/RtmpMediaSource.h @@ -0,0 +1,198 @@ +/* + * 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_RTMP_RTMPMEDIASOURCE_H_ +#define SRC_RTMP_RTMPMEDIASOURCE_H_ + +#include +#include +#include +#include +#include +#include "amf.h" +#include "Rtmp.h" +#include "Common/MediaSource.h" +#include "Common/PacketCache.h" +#include "Util/RingBuffer.h" + +#define RTMP_GOP_SIZE 512 + +namespace mediakit { + +/** + * rtmp媒体源的数据抽象 + * rtmp有关键的三要素,分别是metadata、config帧,普通帧 + * 其中metadata是非必须的,有些编码格式也没有config帧(比如MP3) + * 只要生成了这三要素,那么要实现rtmp推流、rtmp服务器就很简单了 + * rtmp推拉流协议中,先传递metadata,然后传递config帧,然后一直传递普通帧 + * Data abstraction of rtmp media source + * rtmp has three key elements: metadata, config frame, and ordinary frame + * Metadata is optional, and some encoding formats do not have config frames (such as MP3) + * As long as these three elements are generated, it is very simple to implement rtmp push stream and rtmp server + * In the rtmp push and pull stream protocol, metadata is transmitted first, then config frame, and then ordinary frames are continuously transmitted + + * [AUTO-TRANSLATED:72d515c8] + */ +class RtmpMediaSource : public MediaSource, public toolkit::RingDelegate, private PacketCache{ +public: + using Ptr = std::shared_ptr; + using RingDataType = std::shared_ptr >; + using RingType = toolkit::RingBuffer; + + /** + * 构造函数 + * @param vhost 虚拟主机名 + * @param app 应用名 + * @param stream_id 流id + * @param ring_size 可以设置固定的环形缓冲大小,0则自适应 + * Constructor + * @param vhost Virtual host name + * @param app Application name + * @param stream_id Stream id + * @param ring_size You can set a fixed ring buffer size, 0 is adaptive + + * [AUTO-TRANSLATED:5dd23423] + */ + RtmpMediaSource(const MediaTuple& tuple, int ring_size = RTMP_GOP_SIZE): MediaSource(RTMP_SCHEMA, tuple), _ring_size(ring_size) {} + + ~RtmpMediaSource() override { + try { + flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + + /** + * 获取媒体源的环形缓冲 + * Get the ring buffer of the media source + + * [AUTO-TRANSLATED:75ac76b6] + */ + const RingType::Ptr &getRing() const { + return _ring; + } + + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { + _ring->getInfoList(cb, on_change); + } + + /** + * 获取播放器个数 + * @return + * Get the number of players + * @return + + * [AUTO-TRANSLATED:0ba31e32] + */ + int readerCount() override { + return _ring ? _ring->readerCount() : 0; + } + + /** + * 获取metadata + * Get metadata + + * [AUTO-TRANSLATED:270cc022] + */ + template + void getMetaData(const FUNC &func) const { + std::lock_guard lock(_mtx); + if (_metadata) { + func(_metadata); + } + } + + /** + * 获取所有的config帧 + * Get all config frames + + * [AUTO-TRANSLATED:cc3f5b85] + */ + template + void getConfigFrame(const FUNC &func) { + std::lock_guard lock(_mtx); + for (auto &pr : _config_frame_map) { + func(pr.second); + } + } + + /** + * 设置metadata + * Set metadata + + * [AUTO-TRANSLATED:e32234cf] + */ + virtual void setMetaData(const AMFValue &metadata); + + /** + * 输入rtmp包 + * @param pkt rtmp包 + * Input rtmp packet + * @param pkt rtmp packet + + * [AUTO-TRANSLATED:ac020e50] + */ + void onWrite(RtmpPacket::Ptr pkt, bool = true) override; + + /** + * 获取当前时间戳 + * Get the current timestamp + + * [AUTO-TRANSLATED:42e38069] + */ + uint32_t getTimeStamp(TrackType trackType) override; + + void clearCache() override{ + PacketCache::clearCache(); + _ring->clearCache(); + } + + bool haveVideo() const { + return _have_video; + } + + bool haveAudio() const { + return _have_audio; + } + +private: + /** + * 批量flush rtmp包时触发该函数 + * @param rtmp_list rtmp包列表 + * @param key_pos 是否包含关键帧 + * Trigger this function when batch flushing rtmp packets + * @param rtmp_list rtmp packet list + * @param key_pos Whether it contains key frames + + * [AUTO-TRANSLATED:581fe3a4] + */ + void onFlush(std::shared_ptr > rtmp_list, bool key_pos) override { + // 如果不存在视频,那么就没有存在GOP缓存的意义,所以is_key一直为true确保一直清空GOP缓存 [AUTO-TRANSLATED:5818a8d8] + // If there is no video, then there is no point in having a GOP cache, so is_key is always true to ensure that the GOP cache is always cleared + _ring->write(std::move(rtmp_list), _have_video ? key_pos : true); + } + +private: + bool _have_video = false; + bool _have_audio = false; + int _ring_size; + uint32_t _track_stamps[TrackMax] = {0}; + AMFValue _metadata; + RingType::Ptr _ring; + + mutable std::recursive_mutex _mtx; + std::unordered_map _config_frame_map; +}; + +} /* namespace mediakit */ + +#endif /* SRC_RTMP_RTMPMEDIASOURCE_H_ */ diff --git a/MediaServer/Rtmp/RtmpMediaSourceImp.cpp b/MediaServer/Rtmp/RtmpMediaSourceImp.cpp new file mode 100644 index 0000000..74c993f --- /dev/null +++ b/MediaServer/Rtmp/RtmpMediaSourceImp.cpp @@ -0,0 +1,192 @@ +#include "RtmpDemuxer.h" +#include "RtmpMediaSourceImp.h" + +namespace mediakit { + +uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) { + assert(trackType >= TrackInvalid && trackType < TrackMax); + if (trackType != TrackInvalid) { + // 获取某track的时间戳 [AUTO-TRANSLATED:2735ed10] + // Get the timestamp of a track + return _track_stamps[trackType]; + } + + // 获取所有track的最小时间戳 [AUTO-TRANSLATED:179a75cd] + // Get the minimum timestamp of all tracks + uint32_t ret = UINT32_MAX; + for (auto &stamp : _track_stamps) { + if (stamp > 0 && stamp < ret) { + ret = stamp; + } + } + return ret; +} + +void RtmpMediaSource::setMetaData(const AMFValue &metadata) { + { + std::lock_guard lock(_mtx); + _metadata = metadata; + _metadata.set("title", std::string("Streamed by ") + kServerName); + } + + _have_video = _metadata["videocodecid"]; + _have_audio = _metadata["audiocodecid"]; + if (_ring) { + regist(); + + AMFEncoder enc; + enc << "onMetaData" << _metadata; + RtmpPacket::Ptr packet = RtmpPacket::create(); + packet->buffer = enc.data(); + packet->type_id = MSG_DATA; + packet->time_stamp = 0; + packet->chunk_id = CHUNK_CLIENT_REQUEST_AFTER; + packet->stream_index = STREAM_MEDIA; + onWrite(std::move(packet)); + } +} + +void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { + bool is_video = pkt->type_id == MSG_VIDEO; + _speed[is_video ? TrackVideo : TrackAudio] += pkt->size(); + // 保存当前时间戳 [AUTO-TRANSLATED:2b09ff42] + // Save the current timestamp + switch (pkt->type_id) { + case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break; + case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break; + default: break; + } + + if (pkt->isConfigFrame()) { + std::lock_guard lock(_mtx); + _config_frame_map[pkt->type_id] = pkt; + if (!_ring) { + // 注册后收到config帧更新到各播放器 [AUTO-TRANSLATED:7200693d] + // After registration, receive the config frame and update it to each player + return; + } + } + + if (!_ring) { + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + auto lam = [weak_self](int size) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onReaderChanged(size); + }; + + // GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包), [AUTO-TRANSLATED:4a372774] + // GOP defaults to buffering 512 groups of RTMP packets, each group of RTMP packets has the same timestamp (if merge writing is enabled, then each group is the RTMP packet within the merge writing time), + // 每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开) [AUTO-TRANSLATED:dee67297] + // Every time a key frame's first RTMP packet is encountered, the GOP cache will be cleared (because there is a new key frame, which can also achieve instant opening) + _ring = std::make_shared(_ring_size, std::move(lam)); + if (_metadata) { + regist(); + } + } + bool key = pkt->isVideoKeyFrame(); + auto stamp = pkt->time_stamp; + PacketCache::inputPacket(stamp, is_video, std::move(pkt), key); +} + +RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple &tuple, int ringSize) + : RtmpMediaSource(tuple, ringSize) { + _demuxer = std::make_shared(); + _demuxer->setTrackListener(this); +} + +void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) { + if (!_demuxer->loadMetaData(metadata)) { + // 该metadata无效,需要重新生成 [AUTO-TRANSLATED:44a65c0c] + // This metadata is invalid and needs to be regenerated + _metadata = metadata; + _recreate_metadata = true; + } + RtmpMediaSource::setMetaData(metadata); +} + +void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { + if (!_all_track_ready || _muxer->isEnabled()) { + // 未获取到所有Track后,或者开启转协议,那么需要解复用rtmp [AUTO-TRANSLATED:76f6f56e] + // If all Tracks are not obtained, or protocol conversion is enabled, then demultiplexing rtmp is required + _demuxer->inputRtmp(pkt); + } + GET_CONFIG(bool, directProxy, Rtmp::kDirectProxy); + if (directProxy) { + // 直接代理模式才直接使用原始rtmp [AUTO-TRANSLATED:ece580ea] + // Only direct proxy mode uses the original rtmp directly + RtmpMediaSource::onWrite(std::move(pkt)); + } +} + +int RtmpMediaSourceImp::totalReaderCount() { + return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0); +} + +void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) { + GET_CONFIG(bool, direct_proxy, Rtmp::kDirectProxy); + _option = option; + _option.enable_rtmp = !direct_proxy; + _muxer = std::make_shared(_tuple, _demuxer->getDuration(), _option); + _muxer->setMediaListener(getListener()); + _muxer->setTrackListener(std::static_pointer_cast(shared_from_this())); + // 让_muxer对象拦截一部分事件(比如说录像相关事件) [AUTO-TRANSLATED:7d27c400] + // Let the _muxer object intercept some events (such as recording related events) + MediaSource::setListener(_muxer); + + for (auto &track : _demuxer->getTracks(false)) { + _muxer->addTrack(track); + track->addDelegate(_muxer); + } +} + +bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) { + if (_muxer) { + if (_muxer->addTrack(track)) { + track->addDelegate(_muxer); + return true; + } + } + return false; +} + +void RtmpMediaSourceImp::addTrackCompleted() { + if (_muxer) { + _muxer->addTrackCompleted(); + } +} + +void RtmpMediaSourceImp::resetTracks() { + if (_muxer) { + _muxer->resetTracks(); + } +} + +void RtmpMediaSourceImp::onAllTrackReady() { + _all_track_ready = true; + + if (_recreate_metadata) { + // 更新metadata [AUTO-TRANSLATED:396f34c7] + // Update metadata + for (auto &track : _muxer->getTracks()) { + Metadata::addTrack(_metadata, track); + } + RtmpMediaSource::setMetaData(_metadata); + } +} + +void RtmpMediaSourceImp::setListener(const std::weak_ptr &listener) { + if (_muxer) { + // _muxer对象不能处理的事件再给listener处理 [AUTO-TRANSLATED:47858305] + // Events that the _muxer object cannot handle are then handled by the listener + _muxer->setMediaListener(listener); + } else { + // 未创建_muxer对象,事件全部给listener处理 [AUTO-TRANSLATED:54efbd82] + // If the _muxer object is not created, all events are handled by the listener + MediaSource::setListener(listener); + } +} + +} // namespace mediakit diff --git a/MediaServer/Rtmp/RtmpMediaSourceImp.h b/MediaServer/Rtmp/RtmpMediaSourceImp.h new file mode 100644 index 0000000..925b864 --- /dev/null +++ b/MediaServer/Rtmp/RtmpMediaSourceImp.h @@ -0,0 +1,131 @@ +/* + * 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_RTMP_RTMPTORTSPMEDIASOURCE_H_ +#define SRC_RTMP_RTMPTORTSPMEDIASOURCE_H_ + +#include +#include +#include +#include +#include +#include "amf.h" +#include "Rtmp.h" +#include "RtmpDemuxer.h" +#include "RtmpMediaSource.h" +#include "Common/MultiMediaSourceMuxer.h" + +namespace mediakit { + +class RtmpMediaSourceImp final : public RtmpMediaSource, private TrackListener, public MultiMediaSourceMuxer::Listener { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param id 流id + * @param ringSize 环形缓存大小 + * Constructor + * @param vhost Virtual host + * @param app Application name + * @param id Stream id + * @param ringSize Ring buffer size + + * [AUTO-TRANSLATED:7679d212] + */ + RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize = RTMP_GOP_SIZE); + + /** + * 设置metadata + * Set metadata + + * [AUTO-TRANSLATED:e32234cf] + */ + void setMetaData(const AMFValue &metadata) override; + + /** + * 输入rtmp并解析 + * Input rtmp and parse + + * [AUTO-TRANSLATED:de255b79] + */ + void onWrite(RtmpPacket::Ptr pkt, bool = true) override; + + /** + * 获取观看总人数,包括(hls/rtsp/rtmp) + * Get total number of viewers, including (hls/rtsp/rtmp) + + * [AUTO-TRANSLATED:19a26d5a] + */ + int totalReaderCount() override; + + /** + * 设置协议转换 + * Set protocol conversion + + * [AUTO-TRANSLATED:f7206bf3] + */ + void setProtocolOption(const ProtocolOption &option); + + const ProtocolOption &getProtocolOption() const { + return _option; + } + + /** + * _demuxer触发的添加Track事件 + * _demuxer triggered add Track event + + * [AUTO-TRANSLATED:80dbcf16] + */ + bool addTrack(const Track::Ptr &track) override; + + /** + * _demuxer触发的Track添加完毕事件 + * _demuxer triggered Track add complete event + + * [AUTO-TRANSLATED:939cb312] + */ + void addTrackCompleted() override; + + void resetTracks() override; + + /** + * _muxer触发的所有Track就绪的事件 + * _muxer triggered all Track ready event + + * [AUTO-TRANSLATED:1d34b7e0] + */ + void onAllTrackReady() override; + + /** + * 设置事件监听器 + * @param listener 监听器 + * Set event listener + * @param listener Listener + + + * [AUTO-TRANSLATED:d829419b] + */ + void setListener(const std::weak_ptr &listener) override; + +private: + bool _all_track_ready = false; + bool _recreate_metadata = false; + ProtocolOption _option; + AMFValue _metadata; + RtmpDemuxer::Ptr _demuxer; + MultiMediaSourceMuxer::Ptr _muxer; + +}; +} /* namespace mediakit */ + +#endif /* SRC_RTMP_RTMPTORTSPMEDIASOURCE_H_ */ diff --git a/MediaServer/Rtmp/RtmpMediaSourceMuxer.h b/MediaServer/Rtmp/RtmpMediaSourceMuxer.h new file mode 100644 index 0000000..88accf6 --- /dev/null +++ b/MediaServer/Rtmp/RtmpMediaSourceMuxer.h @@ -0,0 +1,93 @@ +/* + * 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_RTMPMEDIASOURCEMUXER_H +#define ZLMEDIAKIT_RTMPMEDIASOURCEMUXER_H + +#include "RtmpMuxer.h" +#include "Rtmp/RtmpMediaSource.h" + +namespace mediakit { + +class RtmpMediaSourceMuxer final : public RtmpMuxer, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + RtmpMediaSourceMuxer(const MediaTuple& tuple, + const ProtocolOption &option, + const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title) { + _option = option; + _media_src = std::make_shared(tuple); + getRtmpRing()->setDelegate(_media_src); + } + + ~RtmpMediaSourceMuxer() override { + try { + RtmpMuxer::flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + + void setListener(const std::weak_ptr &listener){ + setDelegate(listener); + _media_src->setListener(shared_from_this()); + } + + void setTimeStamp(uint32_t stamp){ + _media_src->setTimeStamp(stamp); + } + + int readerCount() const{ + return _media_src->readerCount(); + } + + void addTrackCompleted() override { + RtmpMuxer::addTrackCompleted(); + makeConfigPacket(); + _media_src->setMetaData(getMetadata()); + } + + void onReaderChanged(MediaSource &sender, int size) override { + _enabled = _option.rtmp_demand ? size : true; + if (!size && _option.rtmp_demand) { + _clear_cache = true; + } + MediaSourceEventInterceptor::onReaderChanged(sender, size); + } + + bool inputFrame(const Frame::Ptr &frame) override { + if (_clear_cache && _option.rtmp_demand) { + _clear_cache = false; + _media_src->clearCache(); + } + if (_enabled || !_option.rtmp_demand) { + return RtmpMuxer::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.rtmp_demand ? (_clear_cache ? true : _enabled) : true; + } + +private: + bool _enabled = true; + bool _clear_cache = false; + ProtocolOption _option; + RtmpMediaSource::Ptr _media_src; +}; + + +}//namespace mediakit +#endif //ZLMEDIAKIT_RTMPMEDIASOURCEMUXER_H diff --git a/MediaServer/Rtmp/RtmpMuxer.cpp b/MediaServer/Rtmp/RtmpMuxer.cpp new file mode 100644 index 0000000..6ea1c57 --- /dev/null +++ b/MediaServer/Rtmp/RtmpMuxer.cpp @@ -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. + */ + +#include "RtmpMuxer.h" +#include "Extension/Factory.h" + +namespace mediakit { + +RtmpMuxer::RtmpMuxer(const TitleMeta::Ptr &title) { + if (!title) { + _metadata = std::make_shared()->getMetadata(); + } else { + _metadata = title->getMetadata(); + } + _rtmp_ring = std::make_shared(); +} + +bool RtmpMuxer::addTrack(const Track::Ptr &track) { + if (_track_existed[track->getTrackType()]) { + // rtmp不支持多个同类型track [AUTO-TRANSLATED:c69a7864] + // rtmp does not support multiple tracks of the same type + WarnL << "Already add a track kind of: " << track->getTrackTypeStr() << ", ignore track: " << track->getCodecName(); + return false; + } + + auto &encoder = _encoders[track->getIndex()]; + CHECK(!encoder); + encoder = Factory::getRtmpEncoderByTrack(track); + if (!encoder) { + return false; + } + + // 标记已经存在该类型track [AUTO-TRANSLATED:ed79ebb5] + // Mark that a track of this type already exists + _track_existed[track->getTrackType()] = true; + + // 设置rtmp输出环形缓存 [AUTO-TRANSLATED:d65af70c] + // Set the rtmp output circular buffer + encoder->setRtmpRing(_rtmp_ring); + + // 添加metadata [AUTO-TRANSLATED:eaf2f5ae] + // Add metadata + Metadata::addTrack(_metadata, track); + return true; +} + +bool RtmpMuxer::inputFrame(const Frame::Ptr &frame) { + auto &encoder = _encoders[frame->getIndex()]; + return encoder ? encoder->inputFrame(frame) : false; +} + +void RtmpMuxer::flush() { + for (auto &pr : _encoders) { + if (pr.second) { + pr.second->flush(); + } + } +} + +void RtmpMuxer::makeConfigPacket() { + for (auto &pr : _encoders) { + if (pr.second) { + pr.second->makeConfigPacket(); + } + } +} + +const AMFValue &RtmpMuxer::getMetadata() const { + return _metadata; +} + +RtmpRing::RingType::Ptr RtmpMuxer::getRtmpRing() const { + return _rtmp_ring; +} + +void RtmpMuxer::resetTracks() { + _metadata.clear(); + _encoders.clear(); + CLEAR_ARR(_track_existed); +} + +} /* namespace mediakit */ \ No newline at end of file diff --git a/MediaServer/Rtmp/RtmpMuxer.h b/MediaServer/Rtmp/RtmpMuxer.h new file mode 100644 index 0000000..6cc50ab --- /dev/null +++ b/MediaServer/Rtmp/RtmpMuxer.h @@ -0,0 +1,107 @@ +/* + * 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_RTMPMUXER_H +#define ZLMEDIAKIT_RTMPMUXER_H + +#include "Rtmp/Rtmp.h" +#include "Extension/Frame.h" +#include "Common/MediaSink.h" +#include "RtmpCodec.h" + +namespace mediakit{ + +class RtmpMuxer : public MediaSinkInterface { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * Constructor + + * [AUTO-TRANSLATED:41469869] + */ + RtmpMuxer(const TitleMeta::Ptr &title); + + /** + * 获取完整的SDP字符串 + * @return SDP字符串 + * Get the complete SDP string + * @return SDP string + + * [AUTO-TRANSLATED:f5d1b0a6] + */ + const AMFValue &getMetadata() const ; + + /** + * 获取rtmp环形缓存 + * @return + * Get the rtmp ring buffer + * @return + + * [AUTO-TRANSLATED:81679f3c] + */ + RtmpRing::RingType::Ptr getRtmpRing() const; + + /** + * 添加ready状态的track + * Add a ready state track + + * [AUTO-TRANSLATED:2d8138b3] + */ + bool addTrack(const Track::Ptr & track) override; + + /** + * 写入帧数据 + * @param frame 帧 + * Write frame data + * @param frame frame + + * [AUTO-TRANSLATED:b7c92013] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame buffers + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + + /** + * 重置所有track + * Reset all tracks + + * [AUTO-TRANSLATED:f203fa3e] + */ + void resetTracks() override ; + + /** + * 生成config包 + * Generate config package + + + * [AUTO-TRANSLATED:8f851364] + */ + void makeConfigPacket(); + +private: + bool _track_existed[2] = { false, false }; + + AMFValue _metadata; + RtmpRing::RingType::Ptr _rtmp_ring; + std::unordered_map _encoders; +}; + + +} /* namespace mediakit */ + +#endif //ZLMEDIAKIT_RTMPMUXER_H diff --git a/MediaServer/Rtmp/RtmpPlayer.cpp b/MediaServer/Rtmp/RtmpPlayer.cpp new file mode 100644 index 0000000..c758d98 --- /dev/null +++ b/MediaServer/Rtmp/RtmpPlayer.cpp @@ -0,0 +1,455 @@ +/* + * 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 "RtmpPlayer.h" +#include "Rtmp/utils.h" +#include "Util/util.h" +#include "Util/onceToken.h" +#include "Thread/ThreadPool.h" +#include "Common/config.h" +#include "Common/Parser.h" + +#include "RtmpDemuxer.h" +#include "RtmpPlayerImp.h" + +using namespace toolkit; +using namespace std; + +namespace mediakit { + +RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {} + +RtmpPlayer::~RtmpPlayer() { + DebugL; +} + +void RtmpPlayer::teardown() { + if (alive()) { + shutdown(SockException(Err_shutdown,"teardown")); + } + _app.clear(); + _stream_id.clear(); + _tc_url.clear(); + _beat_timer.reset(); + _play_timer.reset(); + _rtmp_recv_timer.reset(); + _seek_ms = 0; + RtmpProtocol::reset(); + + CLEAR_ARR(_fist_stamp); + CLEAR_ARR(_now_stamp); + + _map_on_result.clear(); + _deque_on_status.clear(); +} + +void RtmpPlayer::play(const string &url) { + teardown(); + auto schema = findSubString(url.data(), nullptr, "://"); + auto host_url = findSubString(url.data(), "://", "/"); + _app = findSubString(url.data(), (host_url + "/").data(), "/"); + _stream_id = findSubString(url.data(), (host_url + "/" + _app + "/").data(), NULL); + auto app_second = findSubString(_stream_id.data(), nullptr, "/"); + if (!app_second.empty() && app_second.find('?') == std::string::npos) { + // _stream_id存在多级;不包含'?', 说明分割符'/'不是url参数的一部分 [AUTO-TRANSLATED:ef820905] + // _stream_id exists at multiple levels; it does not contain '?', indicating that the delimiter '/' is not part of the url parameter + _app += "/" + app_second; + _stream_id.erase(0, app_second.size() + 1); + } + _tc_url = schema + "://" + host_url + "/" + _app; + if (_app.empty() || _stream_id.empty()) { + onPlayResult_l(SockException(Err_other, "rtmp url非法"), false); + return; + } + DebugL << host_url << " " << _app << " " << _stream_id; + + uint16_t port = start_with(url, "rtmps") ? 443 : 1935; + splitUrl(host_url, host_url, port); + + if (!(*this)[Client::kNetAdapter].empty()) { + setNetAdapter((*this)[Client::kNetAdapter]); + } + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + float play_timeout_sec = (*this)[Client::kTimeoutMS].as() / 1000.0f; + _play_timer.reset(new Timer(play_timeout_sec, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onPlayResult_l(SockException(Err_timeout, "play rtmp timeout"), false); + return false; + }, getPoller())); + + _metadata_got = false; + startConnect(host_url, port, play_timeout_sec); +} + +void RtmpPlayer::onError(const SockException &ex){ + // 定时器_pPlayTimer为空后表明握手结束了 [AUTO-TRANSLATED:6e2661f4] + // The timer _pPlayTimer is empty after the handshake is finished + onPlayResult_l(ex, !_play_timer); +} + +void RtmpPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { + if (ex.getErrCode() == Err_shutdown) { + // 主动shutdown的,不触发回调 [AUTO-TRANSLATED:bd97b1c1] + // Active shutdown does not trigger a callback + return; + } + + WarnL << ex.getErrCode() << " " << ex; + if (!handshake_done) { + // 开始播放阶段 [AUTO-TRANSLATED:a246c5ee] + // Start playback stage + _play_timer.reset(); + // 是否为性能测试模式 [AUTO-TRANSLATED:1fde8234] + // Whether it is a performance test mode + _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + onPlayResult(ex); + } else if (ex) { + // 播放成功后异常断开回调 [AUTO-TRANSLATED:b5c5fa80] + // Callback for abnormal disconnection after successful playback + onShutdown(ex); + } else { + // 恢复播放 [AUTO-TRANSLATED:19a73f21] + // Resume playback + onResume(); + } + + if (!ex) { + // 播放成功,恢复rtmp接收超时定时器 [AUTO-TRANSLATED:29b58110] + // After successful playback, restore the rtmp receive timeout timer + _rtmp_recv_ticker.resetTime(); + auto timeout_ms = (*this)[Client::kMediaTimeoutMS].as(); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + auto lam = [weak_self, timeout_ms]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + if (strong_self->_rtmp_recv_ticker.elapsedTime() > timeout_ms) { + // 接收rtmp媒体数据超时 [AUTO-TRANSLATED:e14bc1fe] + // Receive rtmp media data timeout + SockException ex(Err_timeout, "receive rtmp timeout"); + strong_self->onPlayResult_l(ex, true); + return false; + } + return true; + }; + // 创建rtmp数据接收超时检测定时器 [AUTO-TRANSLATED:d255312b] + // Create an rtmp data receive timeout detection timer + _rtmp_recv_timer = std::make_shared(timeout_ms / 2000.0f, lam, getPoller()); + } else { + shutdown(SockException(Err_shutdown,"teardown")); + } +} + +void RtmpPlayer::onConnect(const SockException &err) { + if (err.getErrCode() != Err_success) { + onPlayResult_l(err, false); + return; + } + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + startClientSession([weak_self]() { + if (auto strong_self = weak_self.lock()) { + strong_self->send_connect(); + } + },_app.find("vod") != 0); // 实测发现vod点播时,使用复杂握手fms无响应:issue #2007 +} + +void RtmpPlayer::onRecv(const Buffer::Ptr &buf){ + try { + if (_benchmark_mode && !_play_timer) { + // 在性能测试模式下,如果rtmp握手完毕后,不再解析rtmp包 [AUTO-TRANSLATED:a39356cc] + // In performance test mode, if the rtmp handshake is complete, the rtmp packet will no longer be parsed + _rtmp_recv_ticker.resetTime(); + return; + } + onParseRtmp(buf->data(), buf->size()); + } catch (exception &e) { + SockException ex(Err_other, e.what()); + // 定时器_pPlayTimer为空后表明握手结束了 [AUTO-TRANSLATED:6e2661f4] + // The timer _pPlayTimer is empty after the handshake is finished + onPlayResult_l(ex, !_play_timer); + } +} + +void RtmpPlayer::pause(bool bPause) { + send_pause(bPause); +} + +void RtmpPlayer::speed(float speed) { + //todo +} + +void RtmpPlayer::send_connect() { + AMFValue obj(AMF_OBJECT); + obj.set("app", _app); + obj.set("tcUrl", _tc_url); + // 未使用代理 [AUTO-TRANSLATED:fa1ef5d7] + // No proxy used + obj.set("fpad", false); + // 参考librtmp,什么作用? [AUTO-TRANSLATED:c6e3349f] + // Refer to librtmp, what is the role? + obj.set("capabilities", 15); + // SUPPORT_VID_CLIENT_SEEK 支持seek [AUTO-TRANSLATED:81d2bb06] + // SUPPORT_VID_CLIENT_SEEK supports seek + obj.set("videoFunction", 1); + // 只支持aac [AUTO-TRANSLATED:ab086b5b] + // Only supports aac + obj.set("audioCodecs", (double) (0x0400)); + // 只支持H264 [AUTO-TRANSLATED:d8fb8696] + // Only supports H264 + obj.set("videoCodecs", (double) (0x0080)); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + + sendInvoke("connect", obj); + addOnResultCB([this](AMFDecoder &dec) { + //TraceL << "connect result"; + dec.load(); + auto val = dec.load(); + auto level = val["level"].as_string(); + auto code = val["code"].as_string(); + if (level != "status") { + throw std::runtime_error(StrPrinter << "connect 失败:" << level << " " << code << endl); + } + send_createStream(); + }); +} + +void RtmpPlayer::send_createStream() { + AMFValue obj(AMF_NULL); + sendInvoke("createStream", obj); + addOnResultCB([this](AMFDecoder &dec) { + //TraceL << "createStream result"; + dec.load(); + _stream_index = dec.load(); + send_play(); + }); +} + +void RtmpPlayer::send_play() { + AMFEncoder enc; + enc << "play" << ++_send_req_id << nullptr << _stream_id << -2000; + sendRequest(MSG_CMD, enc.data()); + auto fun = [](AMFValue &val) { + //TraceL << "play onStatus"; + auto level = val["level"].as_string(); + auto code = val["code"].as_string(); + if (level != "status") { + throw std::runtime_error(StrPrinter << "play 失败:" << level << " " << code << endl); + } + }; + addOnStatusCB(fun); + addOnStatusCB(fun); +} + +void RtmpPlayer::send_pause(bool pause) { + AMFEncoder enc; + enc << "pause" << ++_send_req_id << nullptr << pause; + sendRequest(MSG_CMD, enc.data()); + auto fun = [this, pause](AMFValue &val) { + //TraceL << "pause onStatus"; + auto level = val["level"].as_string(); + auto code = val["code"].as_string(); + if (level != "status") { + if (!pause) { + throw std::runtime_error(StrPrinter << "pause 恢复播放失败:" << level << " " << code << endl); + } + } else { + _paused = pause; + if (!pause) { + onPlayResult_l(SockException(Err_success, "resum rtmp success"), true); + } else { + // 暂停播放 [AUTO-TRANSLATED:09cc521a] + // Pause playback + _rtmp_recv_timer.reset(); + } + } + }; + addOnStatusCB(fun); + + _beat_timer.reset(); + if (pause) { + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _beat_timer.reset(new Timer((*this)[Client::kBeatIntervalMS].as() / 1000.0f, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + uint32_t timeStamp = (uint32_t)::time(NULL); + strong_self->sendUserControl(CONTROL_PING_REQUEST, timeStamp); + return true; + }, getPoller())); + } +} + +void RtmpPlayer::onCmd_result(AMFDecoder &dec){ + auto req_id = dec.load(); + auto it = _map_on_result.find(req_id); + if (it != _map_on_result.end()) { + it->second(dec); + _map_on_result.erase(it); + } else { + WarnL << "unhandled _result"; + } +} + +void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) { + AMFValue val; + while (true) { + val = dec.load(); + if (val.type() == AMF_OBJECT) { + break; + } + } + if (val.type() != AMF_OBJECT) { + throw std::runtime_error("onStatus:the result object was not found"); + } + + if (_deque_on_status.size()) { + _deque_on_status.front()(val); + _deque_on_status.pop_front(); + } else { + auto level = val["level"]; + auto code = val["code"].as_string(); + if (level.type() == AMF_STRING) { + // warning 不应该断开 [AUTO-TRANSLATED:6db13b98] + // warning should not be disconnected + if (level.as_string() != "status" && level.as_string() != "warning") { + throw std::runtime_error(StrPrinter << "onStatus 失败:" << level.as_string() << " " << code << endl); + } + } + //WarnL << "unhandled onStatus:" << code; + } +} + +void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) { + //TraceL; + auto val = dec.load(); + if (!onMetadata(val)) { + throw std::runtime_error("onMetadata failed"); + } + _metadata_got = true; +} + +void RtmpPlayer::onStreamDry(uint32_t stream_index) { + //TraceL << stream_index; + onPlayResult_l(SockException(Err_other, "rtmp stream dry"), true); +} + +void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) { + _rtmp_recv_ticker.resetTime(); + if (!_play_timer) { + // 已经触发了onPlayResult事件,直接触发onMediaData事件 [AUTO-TRANSLATED:5c12bd46] + // The onPlayResult event has been triggered, directly trigger the onMediaData event + onRtmpPacket(chunk_data); + return; + } + + if (chunk_data->isConfigFrame()) { + // 输入配置帧以便初始化完成各个track [AUTO-TRANSLATED:2f571d31] + // Input configuration frame to initialize each track + onRtmpPacket(chunk_data); + } else { + // 先触发onPlayResult事件,这个时候解码器才能初始化完毕 [AUTO-TRANSLATED:403c9195] + // Trigger the onPlayResult event first, at this time the decoder can be initialized + onPlayResult_l(SockException(Err_success, "play rtmp success"), false); + // 触发onPlayResult事件后,再把帧数据输入到解码器 [AUTO-TRANSLATED:bf058334] + // After triggering the onPlayResult event, input the frame data to the decoder + onRtmpPacket(chunk_data); + } +} + +void RtmpPlayer::onRtmpChunk(RtmpPacket::Ptr packet) { + auto &chunk_data = *packet; + typedef void (RtmpPlayer::*rtmp_func_ptr)(AMFDecoder &dec); + static unordered_map s_func_map; + static onceToken token([]() { + s_func_map.emplace("_error", &RtmpPlayer::onCmd_result); + s_func_map.emplace("_result", &RtmpPlayer::onCmd_result); + s_func_map.emplace("onStatus", &RtmpPlayer::onCmd_onStatus); + s_func_map.emplace("onMetaData", &RtmpPlayer::onCmd_onMetaData); + }); + + switch (chunk_data.type_id) { + case MSG_CMD: + case MSG_CMD3: + case MSG_DATA: + case MSG_DATA3: { + AMFDecoder dec(chunk_data.buffer, 0, (chunk_data.type_id == MSG_DATA3 || chunk_data.type_id == MSG_CMD3) ? 3 : 0); + std::string type = dec.load(); + auto it = s_func_map.find(type); + if (it != s_func_map.end()) { + auto fun = it->second; + (this->*fun)(dec); + } else { + WarnL << "can not support cmd:" << type; + } + break; + } + + case MSG_AUDIO: + case MSG_VIDEO: { + auto idx = chunk_data.type_id % 2; + if (_now_stamp_ticker[idx].elapsedTime() > 500) { + // 计算播放进度时间轴用 [AUTO-TRANSLATED:383fd62c] + // Used to calculate the playback progress timeline + _now_stamp[idx] = chunk_data.time_stamp; + } + if (!_metadata_got) { + if (!onMetadata(TitleMeta().getMetadata())) { + throw std::runtime_error("onMetadata failed"); + } + _metadata_got = true; + } + onMediaData_l(std::move(packet)); + break; + } + + default: break; + } +} + +uint32_t RtmpPlayer::getProgressMilliSecond() const{ + uint32_t stamp[2] = {0, 0}; + for (auto i = 0; i < 2; i++) { + stamp[i] = _now_stamp[i] - _fist_stamp[i]; + } + return _seek_ms + MAX(stamp[0], stamp[1]); +} + +void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){ + if (_paused) { + pause(false); + } + AMFEncoder enc; + enc << "seek" << ++_send_req_id << nullptr << seekMS * 1.0; + sendRequest(MSG_CMD, enc.data()); + addOnStatusCB([this, seekMS](AMFValue &val) { + //TraceL << "seek result"; + _now_stamp_ticker[0].resetTime(); + _now_stamp_ticker[1].resetTime(); + int iTimeInc = seekMS - getProgressMilliSecond(); + for (auto i = 0; i < 2; i++) { + _fist_stamp[i] = _now_stamp[i] + iTimeInc; + _now_stamp[i] = _fist_stamp[i]; + } + _seek_ms = seekMS; + }); +} + +} /* namespace mediakit */ diff --git a/MediaServer/Rtmp/RtmpPlayer.h b/MediaServer/Rtmp/RtmpPlayer.h new file mode 100644 index 0000000..d11cda2 --- /dev/null +++ b/MediaServer/Rtmp/RtmpPlayer.h @@ -0,0 +1,116 @@ +/* + * 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_RTMP_RtmpPlayer_H_ +#define SRC_RTMP_RtmpPlayer_H_ + +#include +#include +#include +#include "amf.h" +#include "Rtmp.h" +#include "RtmpProtocol.h" +#include "Player/PlayerBase.h" +#include "Util/TimeTicker.h" +#include "Network/Socket.h" +#include "Network/TcpClient.h" + +namespace mediakit { + +// 实现了rtmp播放器协议部分的功能,及数据接收功能 [AUTO-TRANSLATED:1548a233] +// Implemented the rtmp player protocol part functionality, and data receiving functionality +class RtmpPlayer : public PlayerBase, public toolkit::TcpClient, public RtmpProtocol { +public: + using Ptr = std::shared_ptr; + RtmpPlayer(const toolkit::EventPoller::Ptr &poller); + ~RtmpPlayer() override; + + void play(const std::string &strUrl) override; + void pause(bool bPause) override; + void speed(float speed) override; + void teardown() override; + +protected: + virtual bool onMetadata(const AMFValue &val) = 0; + virtual void onRtmpPacket(RtmpPacket::Ptr chunk_data) = 0; + uint32_t getProgressMilliSecond() const; + void seekToMilliSecond(uint32_t ms); + +protected: + void onMediaData_l(RtmpPacket::Ptr chunk_data); + // 在获取config帧后才触发onPlayResult_l(而不是收到play命令回复),所以此时所有track都初始化完毕了 [AUTO-TRANSLATED:c2d8c7a2] + // Trigger onPlayResult_l after getting the config frame (instead of receiving the play command reply), so all tracks are initialized at this time + void onPlayResult_l(const toolkit::SockException &ex, bool handshake_done); + + //form Tcpclient + void onRecv(const toolkit::Buffer::Ptr &buf) override; + void onConnect(const toolkit::SockException &err) override; + void onError(const toolkit::SockException &ex) override; + //from RtmpProtocol + void onRtmpChunk(RtmpPacket::Ptr chunk_data) override; + void onStreamDry(uint32_t stream_index) override; + void onSendRawData(toolkit::Buffer::Ptr buffer) override { + send(std::move(buffer)); + } + + template + void addOnResultCB(const FUNC &func) { + _map_on_result.emplace(_send_req_id, func); + } + template + void addOnStatusCB(const FUNC &func) { + _deque_on_status.emplace_back(func); + } + + void onCmd_result(AMFDecoder &dec); + void onCmd_onStatus(AMFDecoder &dec); + void onCmd_onMetaData(AMFDecoder &dec); + + void send_connect(); + void send_createStream(); + void send_play(); + void send_pause(bool pause); + +private: + std::string _app; + std::string _stream_id; + std::string _tc_url; + + bool _paused = false; + bool _metadata_got = false; + // 是否为性能测试模式 [AUTO-TRANSLATED:1fde8234] + // Whether it is performance test mode + bool _benchmark_mode = false; + + // 播放进度控制 [AUTO-TRANSLATED:62b3ee44] + // Playback progress control + uint32_t _seek_ms = 0; + uint32_t _fist_stamp[2] = {0, 0}; + uint32_t _now_stamp[2] = {0, 0}; + toolkit::Ticker _now_stamp_ticker[2]; + std::deque > _deque_on_status; + std::unordered_map > _map_on_result; + + // rtmp接收超时计时器 [AUTO-TRANSLATED:292af22c] + // Rtmp receive timeout timer + toolkit::Ticker _rtmp_recv_ticker; + // 心跳发送定时器 [AUTO-TRANSLATED:04ac8e65] + // Heartbeat send timer + std::shared_ptr _beat_timer; + // 播放超时定时器 [AUTO-TRANSLATED:74d33688] + // Playback timeout timer + std::shared_ptr _play_timer; + // rtmp接收超时定时器 [AUTO-TRANSLATED:8c121e8c] + // Rtmp receive timeout timer + std::shared_ptr _rtmp_recv_timer; +}; + +} /* namespace mediakit */ +#endif /* SRC_RTMP_RtmpPlayer_H_ */ diff --git a/MediaServer/Rtmp/RtmpPlayerImp.h b/MediaServer/Rtmp/RtmpPlayerImp.h new file mode 100644 index 0000000..33784a3 --- /dev/null +++ b/MediaServer/Rtmp/RtmpPlayerImp.h @@ -0,0 +1,136 @@ +/* + * 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_RTMP_RTMPPLAYERIMP_H_ +#define SRC_RTMP_RTMPPLAYERIMP_H_ + +#include +#include +#include "Common/config.h" +#include "RtmpPlayer.h" +#include "RtmpMediaSource.h" +#include "RtmpDemuxer.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" + +namespace mediakit { + +template +class FlvPlayerBase: public PlayerImp, private TrackListener { +public: + using Ptr = std::shared_ptr; + using Super = PlayerImp; + + FlvPlayerBase(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; + + ~FlvPlayerBase() override { + DebugL << std::endl; + } + + float getDuration() const override { + return _demuxer ? _demuxer->getDuration() : 0; + } + + std::vector getTracks(bool ready = true) const override { + return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); + } + +private: + // 派生类回调函数 [AUTO-TRANSLATED:61e20903] + // Derived class callback function + bool onMetadata(const AMFValue &val) override { + // 无metadata或metadata中无track信息时,需要从数据包中获取track [AUTO-TRANSLATED:92a71803] + // When there is no metadata or no track information in the metadata, it is necessary to obtain the track from the data packet + _wait_track_ready = this->Super::operator[](Client::kWaitTrackReady).template as() || RtmpDemuxer::trackCount(val) == 0; + onCheckMeta_l(val); + return true; + } + + void onRtmpPacket(RtmpPacket::Ptr chunkData) override { + if (!_demuxer) { + // 有些rtmp流没metadata [AUTO-TRANSLATED:2509786f] + // Some rtmp streams do not have metadata + onCheckMeta_l(TitleMeta().getMetadata()); + } + _demuxer->inputRtmp(chunkData); + if (_rtmp_src) { + _rtmp_src->onWrite(std::move(chunkData)); + } + } + + void onPlayResult(const toolkit::SockException &ex) override { + if (!_wait_track_ready || ex) { + Super::onPlayResult(ex); + return; + } + } + + bool addTrack(const Track::Ptr &track) override { return true; } + + void addTrackCompleted() override { + if (_wait_track_ready) { + Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); + } + } + +private: + void onCheckMeta_l(const AMFValue &val) { + _rtmp_src = std::dynamic_pointer_cast(this->Super::_media_src); + if (_rtmp_src) { + _rtmp_src->setMetaData(val); + } + if(_demuxer){ + return; + } + _demuxer = std::make_shared(); + //TraceL<<" _wait_track_ready "<<_wait_track_ready; + _demuxer->setTrackListener(this, _wait_track_ready); + _demuxer->loadMetaData(val); + } + +private: + bool _wait_track_ready = true; + RtmpDemuxer::Ptr _demuxer; + RtmpMediaSource::Ptr _rtmp_src; +}; + +class RtmpPlayerImp: public FlvPlayerBase { +public: + using Ptr = std::shared_ptr; + using Super = FlvPlayerBase; + + RtmpPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; + + ~RtmpPlayerImp() override { + DebugL; + } + + float getProgress() const override { + if (getDuration() > 0) { + return getProgressMilliSecond() / (getDuration() * 1000); + } + return PlayerBase::getProgress(); + } + + void seekTo(float fProgress) override { + fProgress = MAX(float(0), MIN(fProgress, float(1.0))); + seekToMilliSecond((uint32_t)(fProgress * getDuration() * 1000)); + } + + void seekTo(uint32_t seekPos) override { + uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000; + seekToMilliSecond(pos); + } +}; + + +} /* namespace mediakit */ + +#endif /* SRC_RTMP_RTMPPLAYERIMP_H_ */ diff --git a/MediaServer/Rtmp/RtmpProtocol.cpp b/MediaServer/Rtmp/RtmpProtocol.cpp new file mode 100644 index 0000000..191fcbe --- /dev/null +++ b/MediaServer/Rtmp/RtmpProtocol.cpp @@ -0,0 +1,896 @@ +/* + * 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 +#include "RtmpProtocol.h" +#include "Rtmp/utils.h" +#include "RtmpMediaSource.h" +#include "Util/util.h" + +using namespace std; +using namespace toolkit; + +#define C1_DIGEST_SIZE 32 +#define C1_KEY_SIZE 128 +#define C1_SCHEMA_SIZE 764 +#define C1_HANDSHARK_SIZE (RANDOM_LEN + 8) +#define C1_FPKEY_SIZE 30 +#define S1_FMS_KEY_SIZE 36 +#define S2_FMS_KEY_SIZE 68 +#define C1_OFFSET_SIZE 4 + +#ifdef ENABLE_OPENSSL +#include "Util/SSLBox.h" +#include +#include + +static string openssl_HMACsha256(const void *key, size_t key_len, const void *data, size_t data_len){ + std::shared_ptr out(new char[32], [](char *ptr) { delete[] ptr; }); + unsigned int out_len; + +#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) + // openssl 1.1.0新增api,老版本api作废 [AUTO-TRANSLATED:4c7b59a8] + // OpenSSL 1.1.0 added new APIs, old APIs are deprecated + HMAC_CTX *ctx = HMAC_CTX_new(); + HMAC_CTX_reset(ctx); + HMAC_Init_ex(ctx, key, (int)key_len, EVP_sha256(), NULL); + HMAC_Update(ctx, (unsigned char*)data, data_len); + HMAC_Final(ctx, (unsigned char *)out.get(), &out_len); + HMAC_CTX_reset(ctx); + HMAC_CTX_free(ctx); +#else + HMAC_CTX ctx; + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, key, key_len, EVP_sha256(), NULL); + HMAC_Update(&ctx, (unsigned char*)data, data_len); + HMAC_Final(&ctx, (unsigned char *)out.get(), &out_len); + HMAC_CTX_cleanup(&ctx); +#endif //defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) + return string(out.get(),out_len); +} +#endif //ENABLE_OPENSSL + +namespace mediakit { + +RtmpProtocol::RtmpProtocol() { + _packet_pool.setSize(64); + _next_step_func = [this](const char *data, size_t len) { + return handle_C0C1(data, len); + }; +} + +RtmpProtocol::~RtmpProtocol() { + reset(); +} + +void RtmpProtocol::reset() { + ////////////ChunkSize//////////// + _chunk_size_in = DEFAULT_CHUNK_LEN; + _chunk_size_out = DEFAULT_CHUNK_LEN; + ////////////Acknowledgement//////////// + _bytes_sent = 0; + _bytes_sent_last = 0; + _windows_size = 0; + ///////////PeerBandwidth/////////// + _bandwidth = 2500000; + _band_limit_type = 2; + ////////////Chunk//////////// + _map_chunk_data.clear(); + _now_stream_index = 0; + _now_chunk_id = 0; + //////////Invoke Request////////// + _send_req_id = 0; + //////////Rtmp parser////////// + HttpRequestSplitter::reset(); + _stream_index = STREAM_CONTROL; + _next_step_func = [this](const char *data, size_t len) { + return handle_C0C1(data, len); + }; +} + +void RtmpProtocol::sendAcknowledgement(uint32_t size) { + size = htonl(size); + std::string acknowledgement((char *) &size, 4); + sendRequest(MSG_ACK, acknowledgement); +} + +void RtmpProtocol::sendAcknowledgementSize(uint32_t size) { + size = htonl(size); + std::string set_windowSize((char *) &size, 4); + sendRequest(MSG_WIN_SIZE, set_windowSize); +} + +void RtmpProtocol::sendPeerBandwidth(uint32_t size) { + size = htonl(size); + std::string set_peerBandwidth((char *) &size, 4); + set_peerBandwidth.push_back((char) 0x02); + sendRequest(MSG_SET_PEER_BW, set_peerBandwidth); +} + +void RtmpProtocol::sendChunkSize(uint32_t size) { + uint32_t len = htonl(size); + std::string set_chunk((char *) &len, 4); + sendRequest(MSG_SET_CHUNK, set_chunk); + _chunk_size_out = size; +} + +void RtmpProtocol::sendPingRequest(uint32_t stamp) { + sendUserControl(CONTROL_PING_REQUEST, stamp); +} + +void RtmpProtocol::sendPingResponse(uint32_t time_stamp) { + sendUserControl(CONTROL_PING_RESPONSE, time_stamp); +} + +void RtmpProtocol::sendSetBufferLength(uint32_t stream_index, uint32_t len) { + std::string control; + stream_index = htonl(stream_index); + control.append((char *) &stream_index, 4); + + len = htonl(len); + control.append((char *) &len, 4); + sendUserControl(CONTROL_SETBUFFER, control); +} + +void RtmpProtocol::sendUserControl(uint16_t event_type, uint32_t event_data) { + std::string control; + event_type = htons(event_type); + control.append((char *) &event_type, 2); + + event_data = htonl(event_data); + control.append((char *) &event_data, 4); + sendRequest(MSG_USER_CONTROL, control); +} + +void RtmpProtocol::sendUserControl(uint16_t event_type, const string &event_data) { + std::string control; + event_type = htons(event_type); + control.append((char *) &event_type, 2); + control.append(event_data); + sendRequest(MSG_USER_CONTROL, control); +} + +void RtmpProtocol::sendResponse(int type, const string &str) { + if(!_data_started && (type == MSG_DATA)){ + _data_started = true; + } + sendRtmp(type, _now_stream_index, str, 0, _data_started ? CHUNK_CLIENT_REQUEST_AFTER : CHUNK_CLIENT_REQUEST_BEFORE); +} + +void RtmpProtocol::sendInvoke(const string &cmd, const AMFValue &val) { + AMFEncoder enc; + enc << cmd << ++_send_req_id << val; + sendRequest(MSG_CMD, enc.data()); +} + +void RtmpProtocol::sendRequest(int cmd, const string& str) { + if (cmd <= MSG_SET_PEER_BW) { + // 若 cmd 属于 Protocol Control Messages ,则应使用 chunk id 2 发送 [AUTO-TRANSLATED:3f17e21f] + // If cmd belongs to Protocol Control Messages, it should be sent using chunk id 2 + sendRtmp(cmd, _stream_index, str, 0, CHUNK_NETWORK); + } else { + // 否则使用 chunk id 发送(任意值3-128,参见 obs 及 ffmpeg 选取 3) [AUTO-TRANSLATED:65f8d861] + // Otherwise, use chunk id to send (any value 3-128, see obs and ffmpeg select 3) + sendRtmp(cmd, _stream_index, str, 0, CHUNK_SYSTEM); + } +} + +class BufferPartial : public Buffer { +public: + BufferPartial(const Buffer::Ptr &buffer, size_t offset, size_t size) { + _buffer = buffer; + _data = buffer->data() + offset; + _size = size; + } + + char *data() const override { + return _data; + } + + size_t size() const override{ + return _size; + } + +private: + char *_data; + size_t _size; + Buffer::Ptr _buffer; +}; + +void RtmpProtocol::sendRtmp(uint8_t type, uint32_t stream_index, const std::string &buffer, uint32_t stamp, int chunk_id) { + sendRtmp(type, stream_index, std::make_shared(buffer), stamp, chunk_id); +} + +void RtmpProtocol::sendRtmp(uint8_t type, uint32_t stream_index, const Buffer::Ptr &buf, uint32_t stamp, int chunk_id){ + if (chunk_id < 2 || chunk_id > 63) { + auto strErr = StrPrinter << "不支持发送该类型的块流 ID:" << chunk_id << endl; + throw std::runtime_error(strErr); + } + // 是否有扩展时间戳 [AUTO-TRANSLATED:85bae69f] + // Does it have an extended timestamp + bool ext_stamp = stamp >= 0xFFFFFF; + + // rtmp头 [AUTO-TRANSLATED:915b278c] + // RTMP header + BufferRaw::Ptr buffer_header = obtainBuffer(); + buffer_header->setCapacity(sizeof(RtmpHeader)); + buffer_header->setSize(sizeof(RtmpHeader)); + // 对rtmp头赋值,如果使用整形赋值,在arm android上可能由于数据对齐导致总线错误的问题 [AUTO-TRANSLATED:90c79d70] + // Assign values to the RTMP header. If using integer assignment, it may cause bus errors on ARM Android due to data alignment issues + RtmpHeader *header = (RtmpHeader *) buffer_header->data(); + header->fmt = 0; + header->chunk_id = chunk_id; + header->type_id = type; + set_be24(header->time_stamp, ext_stamp ? 0xFFFFFF : stamp); + set_be24(header->body_size, (uint32_t)buf->size()); + set_le32(header->stream_index, stream_index); + // 发送rtmp头 [AUTO-TRANSLATED:3c038cd5] + // Send RTMP header + onSendRawData(std::move(buffer_header)); + + // 扩展时间戳字段 [AUTO-TRANSLATED:6f79a475] + // Extended timestamp field + BufferRaw::Ptr buffer_ext_stamp; + if (ext_stamp) { + // 生成扩展时间戳 [AUTO-TRANSLATED:cd22977a] + // Generate extended timestamp + buffer_ext_stamp = obtainBuffer(); + buffer_ext_stamp->setCapacity(4); + buffer_ext_stamp->setSize(4); + set_be32(buffer_ext_stamp->data(), stamp); + } + + // 生成一个字节的flag,标明是什么chunkId [AUTO-TRANSLATED:fbdbf476] + // Generate a one-byte flag to indicate what chunkId it is + BufferRaw::Ptr buffer_flags = obtainBuffer(); + buffer_flags->setCapacity(1); + buffer_flags->setSize(1); + header = (RtmpHeader *) buffer_flags->data(); + header->fmt = 3; + header->chunk_id = chunk_id; + + size_t offset = 0; + size_t totalSize = sizeof(RtmpHeader); + while (offset < buf->size()) { + if (offset) { + onSendRawData(buffer_flags); + totalSize += 1; + } + if (ext_stamp) { + // 扩展时间戳 [AUTO-TRANSLATED:f263b9bf] + // Extended timestamp + onSendRawData(buffer_ext_stamp); + totalSize += 4; + } + size_t chunk = min(_chunk_size_out, buf->size() - offset); + onSendRawData(std::make_shared(buf, offset, chunk)); + totalSize += chunk; + offset += chunk; + } + _bytes_sent += (uint32_t)totalSize; + if (_windows_size > 0 && _bytes_sent - _bytes_sent_last >= _windows_size) { + _bytes_sent_last = _bytes_sent; + sendAcknowledgement(_bytes_sent); + } +} + +void RtmpProtocol::onParseRtmp(const char *data, size_t size) { + input(data, size); +} + +const char *RtmpProtocol::onSearchPacketTail(const char *data,size_t len){ + // 移动拷贝提高性能 [AUTO-TRANSLATED:1e6fce8d] + // Move copy to improve performance + auto next_step_func(std::move(_next_step_func)); + // 执行下一步 [AUTO-TRANSLATED:4199a3e3] + // Execute the next step + auto ret = next_step_func(data, len); + if (!_next_step_func) { + // 为设置下一步,恢复之 [AUTO-TRANSLATED:b5494ef3] + // Set the next step, restore it + next_step_func.swap(_next_step_func); + } + return ret; +} + +////for client//// +void RtmpProtocol::startClientSession(const function &func, bool complex) { + // 发送 C0C1 [AUTO-TRANSLATED:5da06136] + // Send C0C1 + char handshake_head = HANDSHAKE_PLAINTEXT; + onSendRawData(obtainBuffer(&handshake_head, 1)); + RtmpHandshake c1(0); + if (complex) { + c1.create_complex_c0c1(); + } + onSendRawData(obtainBuffer((char *) (&c1), sizeof(c1))); + _next_step_func = [this, func](const char *data, size_t len) { + // 等待 S0+S1+S2 [AUTO-TRANSLATED:3f95dd6d] + // Wait for S0+S1+S2 + return handle_S0S1S2(data, len, func); + }; +} + +const char* RtmpProtocol::handle_S0S1S2(const char *data, size_t len, const function &func) { + if (len < 1 + 2 * C1_HANDSHARK_SIZE) { + // 数据不够 [AUTO-TRANSLATED:72802244] + // Not enough data + return nullptr; + } + if (data[0] != HANDSHAKE_PLAINTEXT) { + throw std::runtime_error("only plaintext[0x03] handshake supported"); + } + // 发送 C2 [AUTO-TRANSLATED:e51c339e] + // Send C2 + const char *pcC2 = data + 1; + onSendRawData(obtainBuffer(pcC2, C1_HANDSHARK_SIZE)); + // 握手结束 [AUTO-TRANSLATED:9df763ff] + // Handshake finished + _next_step_func = [this](const char *data, size_t len) { + // 握手结束并且开始进入解析命令模式 [AUTO-TRANSLATED:3b4e0886] + // Handshake finished and start entering command parsing mode + return handle_rtmp(data, len); + }; + func(); + return data + 1 + 2 * C1_HANDSHARK_SIZE; +} + +////for server //// +const char * RtmpProtocol::handle_C0C1(const char *data, size_t len) { + if (len < 1 + C1_HANDSHARK_SIZE) { + //need more data! + return nullptr; + } + if (data[0] != HANDSHAKE_PLAINTEXT) { + throw std::runtime_error("only plaintext[0x03] handshake supported"); + } + if (memcmp(data + 5, "\x00\x00\x00\x00", 4) == 0) { + //simple handsharke + handle_C1_simple(data); + } else { +#ifdef ENABLE_OPENSSL + //complex handsharke + handle_C1_complex(data); +#else + WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理,flash播放器可能无法播放!"; + handle_C1_simple(data); +#endif//ENABLE_OPENSSL + } + return data + 1 + C1_HANDSHARK_SIZE; +} + +void RtmpProtocol::handle_C1_simple(const char *data){ + // 发送S0 [AUTO-TRANSLATED:79eca118] + // Send S0 + char handshake_head = HANDSHAKE_PLAINTEXT; + onSendRawData(obtainBuffer(&handshake_head, 1)); + // 发送S1 [AUTO-TRANSLATED:a86847cf] + // Send S1 + RtmpHandshake s1(0); + onSendRawData(obtainBuffer((char *) &s1, C1_HANDSHARK_SIZE)); + // 发送S2 [AUTO-TRANSLATED:dbcf960a] + // Send S2 + onSendRawData(obtainBuffer(data + 1, C1_HANDSHARK_SIZE)); + // 等待C2 [AUTO-TRANSLATED:b9900831] + // Wait for C2 + _next_step_func = [this](const char *data, size_t len) { + // 握手结束并且开始进入解析命令模式 [AUTO-TRANSLATED:3b4e0886] + // Handshake finished and start entering command parsing mode + return handle_C2(data, len); + }; +} + +#ifdef ENABLE_OPENSSL +void RtmpProtocol::handle_C1_complex(const char *data){ + // 参考自:http://blog.csdn.net/win_lin/article/details/13006803 [AUTO-TRANSLATED:97a05bd3] + // Refer to: http://blog.csdn.net/win_lin/article/details/13006803 + //skip c0,time,version + const char *c1_start = data + 1; + const char *schema_start = c1_start + 8; + char *digest_start; + try { + /* c1s1 schema0 + time: 4bytes + version: 4bytes + key: 764bytes + digest: 764bytes + */ + auto digest = get_C1_digest((uint8_t *) schema_start + C1_SCHEMA_SIZE, &digest_start); + string c1_joined(c1_start, C1_HANDSHARK_SIZE); + c1_joined.erase(digest_start - c1_start, C1_DIGEST_SIZE); + check_C1_Digest(digest, c1_joined); + + send_complex_S0S1S2(0, digest); +// InfoL << "schema0"; + } catch (std::exception &) { + // 貌似flash从来都不用schema1 [AUTO-TRANSLATED:2c6d140f] + // It seems that flash never uses schema1 +// WarnL << "try rtmp complex schema0 failed:" << ex.what(); + try { + /* c1s1 schema1 + time: 4bytes + version: 4bytes + digest: 764bytes + key: 764bytes + */ + auto digest = get_C1_digest((uint8_t *) schema_start, &digest_start); + string c1_joined(c1_start, C1_HANDSHARK_SIZE); + c1_joined.erase(digest_start - c1_start, C1_DIGEST_SIZE); + check_C1_Digest(digest, c1_joined); + + send_complex_S0S1S2(1, digest); +// InfoL << "schema1"; + } catch (std::exception &) { +// WarnL << "try rtmp complex schema1 failed:" << ex.what(); + handle_C1_simple(data); + } + } +} + +#if !defined(u_int8_t) +#define u_int8_t unsigned char +#endif // !defined(u_int8_t) + +static u_int8_t FMSKey[] = { + 0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, + 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c, + 0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69, + 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001 + 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, + 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57, + 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, + 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae +}; // 68 + +static u_int8_t FPKey[] = { + 0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20, + 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C, + 0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79, + 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001 + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, + 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, + 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; // 62 + +void RtmpProtocol::check_C1_Digest(const string &digest,const string &data){ + auto sha256 = openssl_HMACsha256(FPKey, C1_FPKEY_SIZE, data.data(), data.size()); + if (sha256 != digest) { + throw std::runtime_error("digest mismatched"); + } else { + InfoL << "check rtmp complex handshark success!"; + } +} + +string RtmpProtocol::get_C1_digest(const uint8_t *ptr,char **digestPos){ + /* 764bytes digest结构 + offset: 4bytes + random-data: (offset)bytes + digest-data: 32bytes + random-data: (764-4-offset-32)bytes + *764bytes digest structure + offset: 4bytes + random-data: (offset)bytes + digest-data: 32bytes + random-data: (764-4-offset-32)bytes + + * [AUTO-TRANSLATED:99455c22] + */ + int offset = 0; + for (int i = 0; i < C1_OFFSET_SIZE; ++i) { + offset += ptr[i]; + } + offset %= (C1_SCHEMA_SIZE - C1_DIGEST_SIZE - C1_OFFSET_SIZE); + *digestPos = (char *) ptr + C1_OFFSET_SIZE + offset; + string digest(*digestPos, C1_DIGEST_SIZE); + //DebugL << "digest offset:" << offset << ",digest:" << hexdump(digest.data(),digest.size()); + return digest; +} + +string RtmpProtocol::get_C1_key(const uint8_t *ptr){ + /* 764bytes key结构 + random-data: (offset)bytes + key-data: 128bytes + random-data: (764-offset-128-4)bytes + offset: 4bytes + *764bytes key structure + random-data: (offset)bytes + key-data: 128bytes + random-data: (764-offset-128-4)bytes + offset: 4bytes + + * [AUTO-TRANSLATED:25b8d7c7] + */ + int offset = 0; + for (int i = C1_SCHEMA_SIZE - C1_OFFSET_SIZE; i < C1_SCHEMA_SIZE; ++i) { + offset += ptr[i]; + } + offset %= (C1_SCHEMA_SIZE - C1_KEY_SIZE - C1_OFFSET_SIZE); + string key((char *) ptr + offset, C1_KEY_SIZE); + //DebugL << "key offset:" << offset << ",key:" << hexdump(key.data(),key.size()); + return key; +} + +void RtmpProtocol::send_complex_S0S1S2(int schemeType,const string &digest){ + // S1S2计算参考自:https://github.com/hitYangfei/golang/blob/master/rtmpserver.go [AUTO-TRANSLATED:8a372a12] + // S1S2 calculation refers to: https://github.com/hitYangfei/golang/blob/master/rtmpserver.go + // 发送S0 [AUTO-TRANSLATED:79eca118] + // Send S0 + char handshake_head = HANDSHAKE_PLAINTEXT; + onSendRawData(obtainBuffer(&handshake_head, 1)); + //S1 + RtmpHandshake s1(0); + memcpy(s1.zero, "\x04\x05\x00\x01", 4); + char *digestPos; + if (schemeType == 0) { + /* c1s1 schema0 + time: 4bytes + version: 4bytes + key: 764bytes + digest: 764bytes + */ + get_C1_digest(s1.random + C1_SCHEMA_SIZE, &digestPos); + } else { + /* c1s1 schema1 + time: 4bytes + version: 4bytes + digest: 764bytes + key: 764bytes + */ + get_C1_digest(s1.random, &digestPos); + } + char *s1_start = (char *) &s1; + string s1_joined(s1_start, sizeof(s1)); + s1_joined.erase(digestPos - s1_start, C1_DIGEST_SIZE); + string s1_digest = openssl_HMACsha256(FMSKey, S1_FMS_KEY_SIZE, s1_joined.data(), s1_joined.size()); + memcpy(digestPos, s1_digest.data(), s1_digest.size()); + onSendRawData(obtainBuffer((char *) &s1, sizeof(s1))); + + //S2 + string s2_key = openssl_HMACsha256(FMSKey, S2_FMS_KEY_SIZE, digest.data(), digest.size()); + RtmpHandshake s2(0); + s2.random_generate((char *) &s2, 8); + string s2_digest = openssl_HMACsha256(s2_key.data(), s2_key.size(), &s2, sizeof(s2) - C1_DIGEST_SIZE); + memcpy((char *) &s2 + C1_HANDSHARK_SIZE - C1_DIGEST_SIZE, s2_digest.data(), C1_DIGEST_SIZE); + onSendRawData(obtainBuffer((char *) &s2, sizeof(s2))); + // 等待C2 [AUTO-TRANSLATED:b9900831] + // Wait for C2 + _next_step_func = [this](const char *data, size_t len) { + return handle_C2(data, len); + }; +} +#endif //ENABLE_OPENSSL + +// 发送复杂握手c0c1 [AUTO-TRANSLATED:f6f646dc] +// Send complex handshake c0c1 +void RtmpHandshake::create_complex_c0c1() { +#ifdef ENABLE_OPENSSL + memcpy(zero, "\x80\x00\x07\x02", 4); + // digest随机偏移长度 [AUTO-TRANSLATED:758606f0] + // Digest random offset length + auto offset_value = rand() % (C1_SCHEMA_SIZE - C1_OFFSET_SIZE - C1_DIGEST_SIZE); + // 设置digest偏移长度 [AUTO-TRANSLATED:1a4caf92] + // Set digest offset length + auto offset_ptr = random + C1_SCHEMA_SIZE; + offset_ptr[0] = offset_ptr[1] = offset_ptr[2] = offset_value / 4; + offset_ptr[3] = offset_value - 3 * (offset_value / 4); + // 去除digest后的剩余随机数据 [AUTO-TRANSLATED:133cc74e] + // Remove the remaining random data after digest + string str((char *) this, sizeof(*this)); + str.erase(8 + C1_SCHEMA_SIZE + C1_OFFSET_SIZE + offset_value, C1_DIGEST_SIZE); + // 获取摘要 [AUTO-TRANSLATED:2a7aab55] + // Get digest + auto digest_value = openssl_HMACsha256(FPKey, C1_FPKEY_SIZE, str.data(), str.size()); + // 插入摘要 [AUTO-TRANSLATED:3f12641b] + // Insert digest + memcpy(random + C1_SCHEMA_SIZE + C1_OFFSET_SIZE + offset_value, digest_value.data(), digest_value.size()); +#endif +} + +const char* RtmpProtocol::handle_C2(const char *data, size_t len) { + if (len < C1_HANDSHARK_SIZE) { + //need more data! + return nullptr; + } + _next_step_func = [this](const char *data, size_t len) { + return handle_rtmp(data, len); + }; + + // 握手结束,进入命令模式 [AUTO-TRANSLATED:2b22d604] + // Handshake ends, enter command mode + return handle_rtmp(data + C1_HANDSHARK_SIZE, len - C1_HANDSHARK_SIZE); +} + +static constexpr size_t HEADER_LENGTH[] = {12, 8, 4, 1}; + +const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) { + auto ptr = data; + while (len) { + size_t offset = 0; + auto header = (RtmpHeader *) ptr; + auto header_len = HEADER_LENGTH[header->fmt]; + _now_chunk_id = header->chunk_id; + switch (_now_chunk_id) { + case 0: { + // 0 值表示二字节形式,并且 ID 范围 64 - 319 [AUTO-TRANSLATED:46207a57] + // 0 value represents two-byte form, and ID range is 64 - 319 + // (第二个字节 + 64)。 [AUTO-TRANSLATED:ab640c90] + // (second byte + 64). + if (len < 2) { + //need more data + return ptr; + } + _now_chunk_id = 64 + (uint8_t) (ptr[1]); + offset = 1; + break; + } + + case 1: { + // 1 值表示三字节形式,并且 ID 范围为 64 - 65599 [AUTO-TRANSLATED:e7f24d54] + // 1 value represents three-byte form, and ID range is 64 - 65599 + // ((第三个字节) * 256 + 第二个字节 + 64)。 [AUTO-TRANSLATED:6e631032] + // ((third byte) * 256 + second byte + 64). + if (len < 3) { + //need more data + return ptr; + } + _now_chunk_id = 64 + ((uint8_t) (ptr[2]) << 8) + (uint8_t) (ptr[1]); + offset = 2; + break; + } + + // 带有 2 值的块流 ID 被保留,用于下层协议控制消息和命令。 [AUTO-TRANSLATED:b0987fdb] + // Block stream IDs with a value of 2 are reserved for lower-level protocol control messages and commands. + default : break; + } + + if (len < header_len + offset) { + //need more data + return ptr; + } + header = (RtmpHeader *) (ptr + offset); + auto &pr = _map_chunk_data[_now_chunk_id]; + auto &now_packet = pr.first; + auto &last_packet = pr.second; + if (!now_packet) { + now_packet = RtmpPacket::create(); + if (last_packet) { + // 恢复chunk上下文 [AUTO-TRANSLATED:84bf0621] + // Restore chunk context + *now_packet = *last_packet; + } + // 绝对时间戳标记复位 [AUTO-TRANSLATED:a0d50bad] + // Absolute timestamp marker reset + now_packet->is_abs_stamp = false; + } + auto &chunk_data = *now_packet; + chunk_data.chunk_id = _now_chunk_id; + switch (header_len) { + case 12: + chunk_data.is_abs_stamp = true; + chunk_data.stream_index = load_le32(header->stream_index); + case 8: + chunk_data.body_size = load_be24(header->body_size); + chunk_data.type_id = header->type_id; + case 4: + chunk_data.ts_field = load_be24(header->time_stamp); + } + + auto time_stamp = chunk_data.ts_field; + if (chunk_data.ts_field == 0xFFFFFF) { + if (len < header_len + offset + 4) { + //need more data + return ptr; + } + time_stamp = load_be32(ptr + offset + header_len); + offset += 4; + } + + if (chunk_data.body_size < chunk_data.buffer.size()) { + throw std::runtime_error("非法的bodySize"); + } + + auto more = min(_chunk_size_in, (size_t) (chunk_data.body_size - chunk_data.buffer.size())); + if (len < header_len + offset + more) { + //need more data + return ptr; + } + if (more) { + chunk_data.buffer.append(ptr + header_len + offset, more); + } + ptr += header_len + offset + more; + len -= header_len + offset + more; + if (chunk_data.buffer.size() == chunk_data.body_size) { + //frame is ready + _now_stream_index = chunk_data.stream_index; + chunk_data.time_stamp = time_stamp + (chunk_data.is_abs_stamp ? 0 : chunk_data.time_stamp); + // 保存chunk上下文 [AUTO-TRANSLATED:4ed4fbb0] + // Save chunk context + last_packet = now_packet; + if (chunk_data.body_size) { + handle_chunk(std::move(now_packet)); + } else { + now_packet = nullptr; + } + } + } + return ptr; +} + +void RtmpProtocol::handle_chunk(RtmpPacket::Ptr packet) { + auto &chunk_data = *packet; + switch (chunk_data.type_id) { + case MSG_ACK: { + if (chunk_data.buffer.size() < 4) { + throw std::runtime_error("MSG_ACK: Not enough data"); + } + //auto bytePeerRecv = load_be32(&chunk_data.buffer[0]); + //TraceL << "MSG_ACK:" << bytePeerRecv; + break; + } + + case MSG_SET_CHUNK: { + if (chunk_data.buffer.size() < 4) { + throw std::runtime_error("MSG_SET_CHUNK :Not enough data"); + } + _chunk_size_in = load_be32(&chunk_data.buffer[0]); + TraceL << "MSG_SET_CHUNK:" << _chunk_size_in; + break; + } + + case MSG_USER_CONTROL: { + //user control message + if (chunk_data.buffer.size() < 2) { + throw std::runtime_error("MSG_USER_CONTROL: Not enough data."); + } + uint16_t event_type = load_be16(&chunk_data.buffer[0]); + chunk_data.buffer.erase(0, 2); + switch (event_type) { + case CONTROL_PING_REQUEST: { + if (chunk_data.buffer.size() < 4) { + throw std::runtime_error("CONTROL_PING_REQUEST: Not enough data."); + } + uint32_t timeStamp = load_be32(&chunk_data.buffer[0]); + //TraceL << "CONTROL_PING_REQUEST:" << time_stamp; + sendUserControl(CONTROL_PING_RESPONSE, timeStamp); + break; + } + + case CONTROL_PING_RESPONSE: { + if (chunk_data.buffer.size() < 4) { + throw std::runtime_error("CONTROL_PING_RESPONSE: Not enough data."); + } + //uint32_t time_stamp = load_be32(&chunk_data.buffer[0]); + //TraceL << "CONTROL_PING_RESPONSE:" << time_stamp; + break; + } + + case CONTROL_STREAM_BEGIN: { + // 开始播放 [AUTO-TRANSLATED:1d82b293] + // Start playback + if (chunk_data.buffer.size() < 4) { + WarnL << "CONTROL_STREAM_BEGIN: Not enough data:" << chunk_data.buffer.size(); + break; + } + uint32_t stream_index = load_be32(&chunk_data.buffer[0]); + onStreamBegin(stream_index); + TraceL << "CONTROL_STREAM_BEGIN:" << stream_index; + break; + } + + case CONTROL_STREAM_EOF: { + // 暂停 [AUTO-TRANSLATED:73a3f145] + // Pause + if (chunk_data.buffer.size() < 4) { + throw std::runtime_error("CONTROL_STREAM_EOF: Not enough data."); + } + uint32_t stream_index = load_be32(&chunk_data.buffer[0]); + onStreamEof(stream_index); + TraceL << "CONTROL_STREAM_EOF:" << stream_index; + break; + } + + case CONTROL_STREAM_DRY: { + // 停止播放 [AUTO-TRANSLATED:1292c1b5] + // Stop playback + if (chunk_data.buffer.size() < 4) { + throw std::runtime_error("CONTROL_STREAM_DRY: Not enough data."); + } + uint32_t stream_index = load_be32(&chunk_data.buffer[0]); + onStreamDry(stream_index); + TraceL << "CONTROL_STREAM_DRY:" << stream_index; + break; + } + + default: /*WarnL << "unhandled user control:" << event_type; */ break; + } + break; + } + + case MSG_WIN_SIZE: { + // 如果窗口太小,会导致发送sendAcknowledgement时无限递归:https://github.com/ZLMediaKit/ZLMediaKit/issues/1839 [AUTO-TRANSLATED:05267962] + // If the window is too small, it will cause infinite recursion when sending sendAcknowledgement: https://github.com/ZLMediaKit/ZLMediaKit/issues/1839 + // 窗口太大,也可能导致fms服务器认为播放器心跳超时 [AUTO-TRANSLATED:30147e88] + // If the window is too large, it may also cause the fms server to consider the player heartbeat timeout + _windows_size = min(max(load_be32(&chunk_data.buffer[0]), 32 * 1024U), 1280 * 1024U); + TraceL << "MSG_WIN_SIZE:" << _windows_size; + break; + } + + case MSG_SET_PEER_BW: { + _bandwidth = load_be32(&chunk_data.buffer[0]); + _band_limit_type = chunk_data.buffer[4]; + TraceL << "MSG_SET_PEER_BW:" << _bandwidth << " " << (int)_band_limit_type; + break; + } + + case MSG_AGGREGATE: { + auto ptr = (uint8_t *) chunk_data.buffer.data(); + auto ptr_tail = ptr + chunk_data.buffer.size(); + uint32_t latest_ts, timestamp; + timestamp = chunk_data.time_stamp; + bool first_message = true; + while (ptr + 8 + 3 < ptr_tail) { + auto type = *ptr; + ptr += 1; + auto size = load_be24(ptr); + ptr += 3; + auto ts = load_be24(ptr); + ptr += 3; + ts |= (*ptr << 24); + ptr += 1; + ptr += 3; + // 参考FFmpeg多拷贝了4个字节 [AUTO-TRANSLATED:d8aae4ae] + // Reference FFmpeg copied 4 more bytes + size += 4; + if (ptr + size > ptr_tail) { + break; + } + if (!first_message) { + timestamp += ts - latest_ts; + } + first_message = false; + latest_ts = ts; + auto sub_packet_ptr = RtmpPacket::create(); + auto &sub_packet = *sub_packet_ptr; + sub_packet.buffer.assign((char *)ptr, size); + sub_packet.type_id = type; + sub_packet.body_size = size; + sub_packet.time_stamp = timestamp; + sub_packet.stream_index = chunk_data.stream_index; + sub_packet.chunk_id = chunk_data.chunk_id; + handle_chunk(std::move(sub_packet_ptr)); + ptr += size; + } + break; + } + + default: { + _bytes_recv += packet->size(); + if (_windows_size > 0 && _bytes_recv - _bytes_recv_last >= _windows_size) { + _bytes_recv_last = _bytes_recv; + sendAcknowledgement(_bytes_recv); + } + onRtmpChunk(std::move(packet)); + break; + } + } +} + +BufferRaw::Ptr RtmpProtocol::obtainBuffer(const void *data, size_t len) { + auto buffer = _packet_pool.obtain2(); + if (data && len) { + buffer->assign((const char *) data, len); + } + return buffer; +} + +} /* namespace mediakit */ diff --git a/MediaServer/Rtmp/RtmpProtocol.h b/MediaServer/Rtmp/RtmpProtocol.h new file mode 100644 index 0000000..7a12495 --- /dev/null +++ b/MediaServer/Rtmp/RtmpProtocol.h @@ -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 SRC_RTMP_RTMPPROTOCOL_H_ +#define SRC_RTMP_RTMPPROTOCOL_H_ + +#include +#include +#include +#include +#include "amf.h" +#include "Rtmp.h" +#include "Util/ResourcePool.h" +#include "Http/HttpRequestSplitter.h" + +namespace mediakit { + +class RtmpProtocol : public HttpRequestSplitter{ +public: + RtmpProtocol(); + virtual ~RtmpProtocol(); + + void onParseRtmp(const char *data, size_t size); + // 作为客户端发送c0c1,等待s0s1s2并且回调 [AUTO-TRANSLATED:fed23902] + // Send c0c1 as a client, wait for s0s1s2 and callback + void startClientSession(const std::function &cb, bool complex = true); + +protected: + virtual void onSendRawData(toolkit::Buffer::Ptr buffer) = 0; + virtual void onRtmpChunk(RtmpPacket::Ptr chunk_data) = 0; + virtual void onStreamBegin(uint32_t stream_index){ + _stream_index = stream_index; + } + virtual void onStreamEof(uint32_t stream_index){}; + virtual void onStreamDry(uint32_t stream_index){}; + +protected: + //// HttpRequestSplitter override //// + ssize_t onRecvHeader(const char *data, size_t len) override { return 0; } + const char *onSearchPacketTail(const char *data, size_t len) override; + +protected: + void reset(); + void sendAcknowledgement(uint32_t size); + void sendAcknowledgementSize(uint32_t size); + void sendPeerBandwidth(uint32_t size); + void sendChunkSize(uint32_t size); + void sendPingRequest(uint32_t ti = ::time(NULL)); + void sendPingResponse(uint32_t time_stamp = ::time(NULL)); + void sendSetBufferLength(uint32_t stream_index, uint32_t len); + void sendUserControl(uint16_t event_type, uint32_t event_data); + void sendUserControl(uint16_t event_type, const std::string &event_data); + void sendInvoke(const std::string &cmd, const AMFValue &val); + void sendRequest(int cmd, const std::string &str); + void sendResponse(int type, const std::string &str); + void sendRtmp(uint8_t type, uint32_t stream_index, const std::string &buffer, uint32_t stamp, int chunk_id); + void sendRtmp(uint8_t type, uint32_t stream_index, const toolkit::Buffer::Ptr &buffer, uint32_t stamp, int chunk_id); + toolkit::BufferRaw::Ptr obtainBuffer(const void *data = nullptr, size_t len = 0); + +private: + void handle_C1_simple(const char *data); +#ifdef ENABLE_OPENSSL + void handle_C1_complex(const char *data); + std::string get_C1_digest(const uint8_t *ptr,char **digestPos); + std::string get_C1_key(const uint8_t *ptr); + void check_C1_Digest(const std::string &digest,const std::string &data); + void send_complex_S0S1S2(int schemeType,const std::string &digest); +#endif //ENABLE_OPENSSL + + const char* handle_S0S1S2(const char *data, size_t len, const std::function &func); + const char* handle_C0C1(const char *data, size_t len); + const char* handle_C2(const char *data, size_t len); + const char* handle_rtmp(const char *data, size_t len); + void handle_chunk(RtmpPacket::Ptr chunk_data); + +protected: + int _send_req_id = 0; + int _now_stream_index = 0; + uint32_t _stream_index = STREAM_CONTROL; + +private: + bool _data_started = false; + int _now_chunk_id = 0; + ////////////ChunkSize//////////// + size_t _chunk_size_in = DEFAULT_CHUNK_LEN; + size_t _chunk_size_out = DEFAULT_CHUNK_LEN; + ////////////Acknowledgement//////////// + uint64_t _bytes_sent = 0; + uint64_t _bytes_sent_last = 0; + uint64_t _bytes_recv = 0; + uint64_t _bytes_recv_last = 0; + uint32_t _windows_size = 0; + ///////////PeerBandwidth/////////// + uint32_t _bandwidth = 2500000; + uint8_t _band_limit_type = 2; + //////////Rtmp parser////////// + std::function _next_step_func; + ////////////Chunk//////////// + std::unordered_map > _map_chunk_data; + // 循环池 [AUTO-TRANSLATED:cf2e86c5] + // Thread pool + toolkit::ResourcePool _packet_pool; +}; + +} /* namespace mediakit */ +#endif /* SRC_RTMP_RTMPPROTOCOL_H_ */ diff --git a/MediaServer/Rtmp/RtmpPusher.cpp b/MediaServer/Rtmp/RtmpPusher.cpp new file mode 100644 index 0000000..badcd01 --- /dev/null +++ b/MediaServer/Rtmp/RtmpPusher.cpp @@ -0,0 +1,337 @@ +/* + * 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 "RtmpPusher.h" +#include "Rtmp/utils.h" +#include "Util/util.h" +#include "Util/onceToken.h" +#include "Thread/ThreadPool.h" +#include "Common/Parser.h" +#include "Common/config.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &src) : TcpClient(poller){ + _publish_src = src; +} + +RtmpPusher::~RtmpPusher() { + teardown(); + DebugL; +} + +void RtmpPusher::teardown() { + if (alive()) { + _app.clear(); + _stream_id.clear(); + _tc_url.clear(); + _map_on_result.clear(); + _deque_on_status.clear(); + _publish_timer.reset(); + reset(); + shutdown(SockException(Err_shutdown, "teardown")); + } +} + +void RtmpPusher::onPublishResult_l(const SockException &ex, bool handshake_done) { + DebugL << ex.what(); + if (ex.getErrCode() == Err_shutdown) { + // 主动shutdown的,不触发回调 [AUTO-TRANSLATED:bd97b1c1] + // Actively shutdown, no callback triggered + return; + } + if (!handshake_done) { + // 播放结果回调 [AUTO-TRANSLATED:a5714269] + // Playback result callback + _publish_timer.reset(); + onPublishResult(ex); + } else { + // 播放成功后异常断开回调 [AUTO-TRANSLATED:b5c5fa80] + // Callback for abnormal disconnection after successful playback + onShutdown(ex); + } + + if (ex) { + shutdown(SockException(Err_shutdown,"teardown")); + } +} + +void RtmpPusher::publish(const string &url) { + teardown(); + auto schema = findSubString(url.data(), nullptr, "://"); + auto host_url = findSubString(url.data(), "://", "/"); + _app = findSubString(url.data(), (host_url + "/").data(), "/"); + _stream_id = findSubString(url.data(), (host_url + "/" + _app + "/").data(), NULL); + auto app_second = findSubString(_stream_id.data(), nullptr, "/"); + if (!app_second.empty() && app_second.find('?') == std::string::npos) { + // _stream_id存在多级;不包含'?', 说明分割符'/'不是url参数的一部分 [AUTO-TRANSLATED:ef820905] + // _stream_id has multiple levels; does not contain '?', indicating that the delimiter '/' is not part of the URL parameters + _app += "/" + app_second; + _stream_id.erase(0, app_second.size() + 1); + } + _tc_url = schema + "://" + host_url + "/" + _app; + if (_app.empty() || _stream_id.empty()) { + onPublishResult_l(SockException(Err_other, "rtmp url非法"), false); + return; + } + DebugL << host_url << " " << _app << " " << _stream_id; + + uint16_t port = start_with(url, "rtmps") ? 443 : 1935; + splitUrl(host_url, host_url, port); + + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + float publishTimeOutSec = (*this)[Client::kTimeoutMS].as() / 1000.0f; + _publish_timer.reset(new Timer(publishTimeOutSec, [weakSelf]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->onPublishResult_l(SockException(Err_timeout, "publish rtmp timeout"), false); + return false; + }, getPoller())); + + if (!(*this)[Client::kNetAdapter].empty()) { + setNetAdapter((*this)[Client::kNetAdapter]); + } + + startConnect(host_url, port); +} + +void RtmpPusher::onError(const SockException &ex){ + // 定时器_pPublishTimer为空后表明握手结束了 [AUTO-TRANSLATED:630ec31e] + // The timer _pPublishTimer is empty, indicating that the handshake is over + onPublishResult_l(ex, !_publish_timer); +} + +void RtmpPusher::onConnect(const SockException &err){ + if (err) { + onPublishResult_l(err, false); + return; + } + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + startClientSession([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + strong_self->sendChunkSize(60000); + strong_self->send_connect(); + }); +} + +void RtmpPusher::onRecv(const Buffer::Ptr &buf){ + try { + onParseRtmp(buf->data(), buf->size()); + } catch (exception &e) { + SockException ex(Err_other, e.what()); + // 定时器_pPublishTimer为空后表明握手结束了 [AUTO-TRANSLATED:630ec31e] + // The timer _pPublishTimer is empty, indicating that the handshake is over + onPublishResult_l(ex, !_publish_timer); + } +} + +void RtmpPusher::send_connect() { + AMFValue obj(AMF_OBJECT); + obj.set("app", _app); + obj.set("type", "nonprivate"); + obj.set("tcUrl", _tc_url); + obj.set("swfUrl", _tc_url); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + + sendInvoke("connect", obj); + addOnResultCB([this](AMFDecoder &dec) { + //TraceL << "connect result"; + dec.load(); + auto val = dec.load(); + auto level = val["level"].as_string(); + auto code = val["code"].as_string(); + if (level != "status") { + throw std::runtime_error(StrPrinter << "connect 失败:" << level << " " << code << endl); + } + send_createStream(); + }); +} + +void RtmpPusher::send_createStream() { + AMFValue obj(AMF_NULL); + sendInvoke("createStream", obj); + addOnResultCB([this](AMFDecoder &dec) { + //TraceL << "createStream result"; + dec.load(); + _stream_index = dec.load(); + send_publish(); + }); +} + +#define RTMP_STREAM_LIVE "live" +void RtmpPusher::send_publish() { + AMFEncoder enc; + enc << "publish" << ++_send_req_id << nullptr << _stream_id << RTMP_STREAM_LIVE; + sendRequest(MSG_CMD, enc.data()); + + addOnStatusCB([this](AMFValue &val) { + auto level = val["level"].as_string(); + auto code = val["code"].as_string(); + if (level != "status") { + throw std::runtime_error(StrPrinter << "publish 失败:" << level << " " << code << endl); + } + //start send media + send_metaData(); + }); +} + +void RtmpPusher::send_metaData(){ + auto src = _publish_src.lock(); + if (!src) { + throw std::runtime_error("the media source was released"); + } + + // metadata + src->getMetaData([&](const AMFValue &metadata) { + AMFEncoder enc; + enc << "@setDataFrame" << "onMetaData" << metadata; + sendRequest(MSG_DATA, enc.data()); + }); + + // config frame + src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { + sendRtmp(pkt->type_id, _stream_index, pkt, pkt->time_stamp, pkt->chunk_id); + }); + + src->pause(false); + _rtmp_reader = src->getRing()->attach(getPoller()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _rtmp_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + size_t i = 0; + auto size = pkt->size(); + strong_self->setSendFlushFlag(false); + pkt->for_each([&](const RtmpPacket::Ptr &rtmp) { + if (++i == size) { + strong_self->setSendFlushFlag(true); + } + if (rtmp->type_id == MSG_DATA) { + // update metadata + AMFEncoder enc; + enc << "@setDataFrame"; + auto pkt = enc.data(); + pkt.append(rtmp->data(), rtmp->size()); + strong_self->sendRequest(MSG_DATA, pkt); + } else { + strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id); + } + }); + }); + _rtmp_reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->onPublishResult_l(SockException(Err_other, "媒体源被释放"), !strong_self->_publish_timer); + } + }); + onPublishResult_l(SockException(Err_success, "success"), false); + // 提升发送性能 [AUTO-TRANSLATED:90630751] + // Improve sending performance + setSocketFlags(); +} + +void RtmpPusher::setSocketFlags(){ + GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS); + if (mergeWriteMS > 0) { + // 提高发送性能 [AUTO-TRANSLATED:de96ec30] + // Improve sending performance + setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); + SockUtil::setNoDelay(getSock()->rawFD(), false); + } +} + +void RtmpPusher::onCmd_result(AMFDecoder &dec){ + auto req_id = dec.load(); + auto it = _map_on_result.find(req_id); + if (it != _map_on_result.end()) { + it->second(dec); + _map_on_result.erase(it); + } else { + WarnL << "unhandled _result"; + } +} + +void RtmpPusher::onCmd_onStatus(AMFDecoder &dec) { + AMFValue val; + while (true) { + val = dec.load(); + if (val.type() == AMF_OBJECT) { + break; + } + } + if (val.type() != AMF_OBJECT) { + throw std::runtime_error("onStatus:the result object was not found"); + } + + if (_deque_on_status.size()) { + _deque_on_status.front()(val); + _deque_on_status.pop_front(); + } else { + auto level = val["level"]; + auto code = val["code"].as_string(); + if (level.type() == AMF_STRING) { + if (level.as_string() != "status") { + throw std::runtime_error(StrPrinter << "onStatus 失败:" << level.as_string() << " " << code << endl); + } + } + } +} + +void RtmpPusher::onRtmpChunk(RtmpPacket::Ptr packet) { + auto &chunk_data = *packet; + switch (chunk_data.type_id) { + case MSG_CMD: + case MSG_CMD3: { + typedef void (RtmpPusher::*rtmpCMDHandle)(AMFDecoder &dec); + static unordered_map g_mapCmd; + static onceToken token([]() { + g_mapCmd.emplace("_error", &RtmpPusher::onCmd_result); + g_mapCmd.emplace("_result", &RtmpPusher::onCmd_result); + g_mapCmd.emplace("onStatus", &RtmpPusher::onCmd_onStatus); + }); + + AMFDecoder dec(chunk_data.buffer, 0, chunk_data.type_id == MSG_CMD3 ? 3 : 0); + std::string type = dec.load(); + auto it = g_mapCmd.find(type); + if (it != g_mapCmd.end()) { + auto fun = it->second; + (this->*fun)(dec); + } else { + WarnL << "can not support cmd:" << type; + } + break; + } + + default: + //WarnL << "unhandled message:" << (int) chunk_data.type_id << hexdump(chunk_data.buffer.data(), chunk_data.buffer.size()); + break; + } +} + + +} /* namespace mediakit */ + diff --git a/MediaServer/Rtmp/RtmpPusher.h b/MediaServer/Rtmp/RtmpPusher.h new file mode 100644 index 0000000..41caac4 --- /dev/null +++ b/MediaServer/Rtmp/RtmpPusher.h @@ -0,0 +1,82 @@ +/* + * 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_RTMP_RTMPPUSHER_H_ +#define SRC_RTMP_RTMPPUSHER_H_ + +#include "RtmpProtocol.h" +#include "RtmpMediaSource.h" +#include "Network/TcpClient.h" +#include "Pusher/PusherBase.h" + +namespace mediakit { + +class RtmpPusher : public RtmpProtocol, public toolkit::TcpClient, public PusherBase { +public: + using Ptr = std::shared_ptr; + RtmpPusher(const toolkit::EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src); + ~RtmpPusher() override; + + void publish(const std::string &url) override ; + void teardown() override; + +protected: + //for Tcpclient override + void onRecv(const toolkit::Buffer::Ptr &buf) override; + void onConnect(const toolkit::SockException &err) override; + void onError(const toolkit::SockException &ex) override; + + //for RtmpProtocol override + void onRtmpChunk(RtmpPacket::Ptr chunk_data) override; + void onSendRawData(toolkit::Buffer::Ptr buffer) override{ + send(std::move(buffer)); + } + +private: + void onPublishResult_l(const toolkit::SockException &ex, bool handshake_done); + + template + inline void addOnResultCB(const FUN &fun) { + _map_on_result.emplace(_send_req_id, fun); + } + template + inline void addOnStatusCB(const FUN &fun) { + _deque_on_status.emplace_back(fun); + } + + void onCmd_result(AMFDecoder &dec); + void onCmd_onStatus(AMFDecoder &dec); + void onCmd_onMetaData(AMFDecoder &dec); + + inline void send_connect(); + inline void send_createStream(); + inline void send_publish(); + inline void send_metaData(); + void setSocketFlags(); + +private: + std::string _app; + std::string _stream_id; + std::string _tc_url; + std::deque > _deque_on_status; + std::unordered_map > _map_on_result; + + // 推流超时定时器 [AUTO-TRANSLATED:7d2dcb86] + // Stream timeout timer + std::shared_ptr _publish_timer; + std::weak_ptr _publish_src; + RtmpMediaSource::RingType::RingReader::Ptr _rtmp_reader; +}; + +using RtmpPusherImp = PusherImp; + +} /* namespace mediakit */ + +#endif /* SRC_RTMP_RTMPPUSHER_H_ */ diff --git a/MediaServer/Rtmp/RtmpSession.cpp b/MediaServer/Rtmp/RtmpSession.cpp new file mode 100644 index 0000000..6fc3c21 --- /dev/null +++ b/MediaServer/Rtmp/RtmpSession.cpp @@ -0,0 +1,639 @@ +/* + * 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 "RtmpSession.h" +#include "Common/config.h" +#include "Util/onceToken.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +RtmpSession::RtmpSession(const Socket::Ptr &sock) : Session(sock) { + GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond); + sock->setSendTimeOutSecond(keep_alive_sec); +} + +void RtmpSession::onError(const SockException& err) { + bool is_player = !_push_src_ownership; + uint64_t duration = _ticker.createdTime() / 1000; + WarnP(this) << (is_player ? "RTMP播放器(" : "RTMP推流器(") + << _media_info.shortUrl() + << ")断开:" << err.what() + << ",耗时(s):" << duration; + + //流量统计事件广播 + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + + if (_total_bytes >= iFlowThreshold * 1024) { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, *this); + } + + //如果是主动关闭的,那么不延迟注销 + if (_push_src && _continue_push_ms && err.getErrCode() != Err_shutdown) { + //取消所有权 + _push_src_ownership = nullptr; + //延时10秒注销流 + auto push_src = std::move(_push_src); + getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; }); + } +} + +void RtmpSession::onManager() { + GET_CONFIG(uint32_t, handshake_sec, Rtmp::kHandshakeSecond); + GET_CONFIG(uint32_t, keep_alive_sec, Rtmp::kKeepAliveSecond); + + if (_ticker.createdTime() > handshake_sec * 1000) { + if (!_ring_reader && !_push_src) { + shutdown(SockException(Err_timeout, "illegal connection")); + } + } + if (_push_src) { + // push + if (_ticker.elapsedTime() > keep_alive_sec * 1000) { + shutdown(SockException(Err_timeout, "recv data from rtmp pusher timeout")); + } + } +} + +void RtmpSession::onRecv(const Buffer::Ptr &buf) { + _ticker.resetTime(); + _total_bytes += buf->size(); + onParseRtmp(buf->data(), buf->size()); +} + +void RtmpSession::onCmd_connect(AMFDecoder &dec) { + auto params = dec.load(); + ///////////set chunk size//////////////// + sendChunkSize(60000); + ////////////window Acknowledgement size///// + sendAcknowledgementSize(5000000); + ///////////set peerBandwidth//////////////// + sendPeerBandwidth(5000000); + + auto tc_url = params["tcUrl"].as_string(); + if (tc_url.empty()) { + // defaultVhost:默认vhost + tc_url = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _media_info.app; + } else { + auto pos = tc_url.rfind('?'); + if (pos != string::npos) { + // tc_url 中可能包含?以及参数,参见issue: #692 + tc_url = tc_url.substr(0, pos); + } + } + // 初步解析,只用于获取vhost信息 + _media_info.parse(tc_url); + _media_info.schema = RTMP_SCHEMA; + // 赋值rtmp app + _media_info.app = params["app"].as_string(); + + bool ok = true; //(app == APP_NAME); + AMFValue version(AMF_OBJECT); + version.set("fmsVer", "FMS/3,0,1,123"); + version.set("capabilities", 31.0); + AMFValue status(AMF_OBJECT); + status.set("level", ok ? "status" : "error"); + status.set("code", ok ? "NetConnection.Connect.Success" : "NetConnection.Connect.InvalidApp"); + status.set("description", ok ? "Connection succeeded." : "InvalidApp."); + status.set("objectEncoding", params["objectEncoding"]); + sendReply(ok ? "_result" : "_error", version, status); + if (!ok) { + throw std::runtime_error("Unsupported application: " + _media_info.app); + } + + AMFEncoder invoke; + invoke << "onBWDone" << 0.0 << nullptr; + sendResponse(MSG_CMD, invoke.data()); +} + +void RtmpSession::onCmd_createStream(AMFDecoder &dec) { + sendReply("_result", nullptr, double(STREAM_MEDIA)); +} + +void RtmpSession::onCmd_publish(AMFDecoder &dec) { + std::shared_ptr ticker(new Ticker); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + std::shared_ptr token(new onceToken(nullptr, [ticker, weak_self]() { + auto strong_self = weak_self.lock(); + if (strong_self) { + DebugP(strong_self.get()) << "publish 回复时间:" << ticker->elapsedTime() << "ms"; + } + })); + dec.load();/* NULL */ + // 赋值为rtmp stream id 信息 + _media_info.stream = getStreamId(dec.load()); + // 再解析url,切割url为app/stream_id (不一定符合rtmp url切割规范) + _media_info.parse(_media_info.getUrl()); + + auto now_stream_index = _now_stream_index; + auto on_res = [this, token, now_stream_index](const string &err, const ProtocolOption &option) { + _now_stream_index = now_stream_index; + if (!err.empty()) { + sendStatus({ "level", "error", + "code", "NetStream.Publish.BadAuth", + "description", err, + "clientid", "0" }); + shutdown(SockException(Err_shutdown, StrPrinter << "Unauthorized:" << err)); + return; + } + + assert(!_push_src); + auto src = MediaSource::find(RTMP_SCHEMA, _media_info.vhost, _media_info.app, _media_info.stream); + auto push_failed = (bool)src; + + while (src) { + //尝试断连后继续推流 + auto rtmp_src = dynamic_pointer_cast(src); + if (!rtmp_src) { + //源不是rtmp推流产生的 + break; + } + auto ownership = rtmp_src->getOwnership(); + if (!ownership) { + //获取推流源所有权失败 + break; + } + _push_src = std::move(rtmp_src); + _push_src_ownership = std::move(ownership); + push_failed = false; + break; + } + + if (push_failed) { + sendStatus({"level", "error", + "code", "NetStream.Publish.BadName", + "description", "Already publishing.", + "clientid", "0" }); + shutdown(SockException(Err_shutdown, StrPrinter << "Already publishing:" << err)); + return; + } + + if (!_push_src) { + _push_src = std::make_shared(_media_info); + //获取所有权 + _push_src_ownership = _push_src->getOwnership(); + _push_src->setProtocolOption(option); + } + + _push_src->setListener(static_pointer_cast(shared_from_this())); + _continue_push_ms = option.continue_push_ms; + sendStatus({"level", "status", + "code", "NetStream.Publish.Start", + "description", "Started publishing stream.", + "clientid", "0" }); + + setSocketFlags(); + }; + + if(_media_info.app.empty() || _media_info.stream.empty()){ + //不允许莫名其妙的推流url + on_res("rtmp推流url非法", ProtocolOption()); + return; + } + + Broadcast::PublishAuthInvoker invoker = [weak_self, on_res, token](const string &err, const ProtocolOption &option) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->async([weak_self, on_res, err, token, option]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + on_res(err, option); + }); + }; + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, *this); + if(!flag){ + //该事件无人监听,默认鉴权成功 + on_res("", ProtocolOption()); + } +} + +void RtmpSession::onCmd_deleteStream(AMFDecoder &dec) { + _push_src = nullptr; + //此时回复可能触发broken pipe事件,从而直接触发onError回调;所以需要先把_push_src置空,防止触发断流续推功能 + sendStatus({ "level", "status", + "code", "NetStream.Unpublish.Success", + "description", "Stop publishing." }); + throw std::runtime_error(StrPrinter << "Stop publishing" << endl); +} + +void RtmpSession::sendStatus(const std::initializer_list &key_value) { + AMFValue status(AMF_OBJECT); + int i = 0; + string key; + for (auto &val : key_value) { + if (++i % 2 == 0) { + status.set(key, val); + } else { + key = val; + } + } + sendReply("onStatus", nullptr, status); +} + +void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr &src) { + bool auth_success = err.empty(); + bool ok = (src.operator bool() && auth_success); + if (ok) { + //stream begin + sendUserControl(CONTROL_STREAM_BEGIN, STREAM_MEDIA); + } + // onStatus(NetStream.Play.Reset) + sendStatus({ "level", (ok ? "status" : "error"), + "code", (ok ? "NetStream.Play.Reset" : (auth_success ? "NetStream.Play.StreamNotFound" : "NetStream.Play.BadAuth")), + "description", (ok ? "Resetting and playing." : (auth_success ? "No such stream." : err.data())), + "details", _media_info.stream, + "clientid", "0" }); + + if (!ok) { + string err_msg = StrPrinter << (auth_success ? "no such stream:" : err.data()) << " " << _media_info.shortUrl(); + shutdown(SockException(Err_shutdown, err_msg)); + return; + } + + // onStatus(NetStream.Play.Start) + + sendStatus({ "level", "status", + "code", "NetStream.Play.Start", + "description", "Started playing." , + "details", _media_info.stream, + "clientid", "0"}); + + // |RtmpSampleAccess(true, true) + AMFEncoder invoke; + invoke << "|RtmpSampleAccess" << true << true; + sendResponse(MSG_DATA, invoke.data()); + + //onStatus(NetStream.Data.Start) + invoke.clear(); + AMFValue obj(AMF_OBJECT); + obj.set("code", "NetStream.Data.Start"); + invoke << "onStatus" << obj; + sendResponse(MSG_DATA, invoke.data()); + + //onStatus(NetStream.Play.PublishNotify) + sendStatus({ "level", "status", + "code", "NetStream.Play.PublishNotify", + "description", "Now published." , + "details", _media_info.stream, + "clientid", "0"}); + // metadata + src->getMetaData([&](const AMFValue &metadata) { + invoke.clear(); + invoke << "onMetaData" << metadata; + sendResponse(MSG_DATA, invoke.data()); + }); + + // config frame + src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { + onSendMedia(pkt); + }); + + src->pause(false); + _ring_reader = src->getRing()->attach(getPoller()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); + _ring_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + size_t i = 0; + auto size = pkt->size(); + strong_self->setSendFlushFlag(false); + pkt->for_each([&](const RtmpPacket::Ptr &rtmp){ + if(++i == size){ + strong_self->setSendFlushFlag(true); + } + strong_self->onSendMedia(rtmp); + }); + }); + _ring_reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->sendUserControl(CONTROL_STREAM_EOF/*or CONTROL_STREAM_DRY ?*/, STREAM_MEDIA); + strong_self->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); + }); + src->pause(false); + _play_src = src; + //提高服务器发送性能 + setSocketFlags(); +} + +void RtmpSession::doPlayResponse(const string &err,const std::function &cb){ + if(!err.empty()){ + //鉴权失败,直接返回播放失败 + sendPlayResponse(err, nullptr); + cb(false); + return; + } + + //鉴权成功,查找媒体源并回复 + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + MediaSource::findAsync(_media_info, weak_self.lock(), [weak_self,cb](const MediaSource::Ptr &src){ + auto rtmp_src = dynamic_pointer_cast(src); + auto strong_self = weak_self.lock(); + if(strong_self){ + strong_self->sendPlayResponse("", rtmp_src); + } + cb(rtmp_src.operator bool()); + }); +} + +void RtmpSession::doPlay(AMFDecoder &dec){ + std::shared_ptr ticker(new Ticker); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + std::shared_ptr token(new onceToken(nullptr, [ticker,weak_self](){ + auto strong_self = weak_self.lock(); + if (strong_self) { + DebugP(strong_self.get()) << "play 回复时间:" << ticker->elapsedTime() << "ms"; + } + })); + auto now_stream_index = _now_stream_index; + Broadcast::AuthInvoker invoker = [weak_self, token, now_stream_index](const string &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->async([weak_self, err, token, now_stream_index]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->_now_stream_index = now_stream_index; + strong_self->doPlayResponse(err, [token](bool) {}); + }); + }; + + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); + if (!flag) { + // 该事件无人监听,默认不鉴权 + doPlayResponse("", [token](bool) {}); + } +} + +void RtmpSession::onCmd_play2(AMFDecoder &dec) { + doPlay(dec); +} + +string RtmpSession::getStreamId(const string &str){ + string stream_id; + string params; + auto pos = str.find('?'); + if (pos != string::npos) { + //有url参数 + stream_id = str.substr(0, pos); + //获取url参数 + params = str.substr(pos + 1); + } else { + //没有url参数 + stream_id = str; + } + + pos = stream_id.find(":"); + if (pos != string::npos) { + //vlc和ffplay在播放 rtmp://127.0.0.1/record/0.mp4时, + //传过来的url会是rtmp://127.0.0.1/record/mp4:0, + //我们在这里还原成0.mp4 + //实际使用时发现vlc,mpv等会传过来rtmp://127.0.0.1/record/mp4:0.mp4,这里做个判断 + auto ext = stream_id.substr(0, pos); + stream_id = stream_id.substr(pos + 1); + if (stream_id.find(ext) == string::npos) { + stream_id = stream_id + "." + ext; + } + } + + if (params.empty()) { + //没有url参数 + return stream_id; + } + + //有url参数 + return stream_id + '?' + params; +} + +void RtmpSession::onCmd_play(AMFDecoder &dec) { + dec.load(); /* NULL */ + // 赋值为rtmp stream id 信息 + _media_info.stream = getStreamId(dec.load()); + // 再解析url,切割url为app/stream_id (不一定符合rtmp url切割规范) + _media_info.parse(_media_info.getUrl()); + doPlay(dec); +} + +void RtmpSession::onCmd_pause(AMFDecoder &dec) { + dec.load();/* NULL */ + bool paused = dec.load(); + TraceP(this) << paused; + + sendStatus({ "level", "status", + "code", (paused ? "NetStream.Pause.Notify" : "NetStream.Unpause.Notify"), + "description", (paused ? "Paused stream." : "Unpaused stream.")}); + + //streamBegin + sendUserControl(paused ? CONTROL_STREAM_EOF : CONTROL_STREAM_BEGIN, STREAM_MEDIA); + auto strongSrc = _play_src.lock(); + if (strongSrc) { + strongSrc->pause(paused); + } +} + +void RtmpSession::onCmd_playCtrl(AMFDecoder &dec) { + dec.load(); + auto ctrlObj = dec.load(); + int ctrlType = ctrlObj["ctrlType"].as_integer(); + float speed = ctrlObj["speed"].as_number(); + + sendStatus({ "level", "status", + "code", "NetStream.Speed.Notify", + "description", "Speeding"}); + + //streamBegin + sendUserControl(CONTROL_STREAM_EOF, STREAM_MEDIA); + + auto strong_src = _play_src.lock(); + if (strong_src) { + strong_src->speed(speed); + } +} + +void RtmpSession::setMetaData(AMFDecoder &dec) { + std::string type = dec.load(); + if (type != "onMetaData") { + throw std::runtime_error("can only set metadata"); + } + _push_metadata = dec.load(); + _set_meta_data = false; +} + +void RtmpSession::onProcessCmd(AMFDecoder &dec) { + typedef void (RtmpSession::*cmd_function)(AMFDecoder &dec); + static unordered_map s_cmd_functions; + static onceToken token([]() { + s_cmd_functions.emplace("connect", &RtmpSession::onCmd_connect); + s_cmd_functions.emplace("createStream", &RtmpSession::onCmd_createStream); + s_cmd_functions.emplace("publish", &RtmpSession::onCmd_publish); + s_cmd_functions.emplace("deleteStream", &RtmpSession::onCmd_deleteStream); + s_cmd_functions.emplace("play", &RtmpSession::onCmd_play); + s_cmd_functions.emplace("play2", &RtmpSession::onCmd_play2); + s_cmd_functions.emplace("seek", &RtmpSession::onCmd_seek); + s_cmd_functions.emplace("pause", &RtmpSession::onCmd_pause); + s_cmd_functions.emplace("onPlayCtrl", &RtmpSession::onCmd_playCtrl); + }); + + std::string method = dec.load(); + auto it = s_cmd_functions.find(method); + if (it == s_cmd_functions.end()) { +// TraceP(this) << "can not support cmd:" << method; + return; + } + _recv_req_id = dec.load(); + auto fun = it->second; + (this->*fun)(dec); +} + +void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { + auto &chunk_data = *packet; + switch (chunk_data.type_id) { + case MSG_CMD: + case MSG_CMD3: { + AMFDecoder dec(chunk_data.buffer, chunk_data.type_id == MSG_CMD3 ? 3 : 0); + onProcessCmd(dec); + break; + } + + case MSG_DATA: + case MSG_DATA3: { + AMFDecoder dec(chunk_data.buffer, chunk_data.type_id == MSG_DATA3 ? 3 : 0); + std::string type = dec.load(); + if (type == "@setDataFrame") { + setMetaData(dec); + } else if (type == "onMetaData") { + //兼容某些不规范的推流器 + _push_metadata = dec.load(); + _set_meta_data = false; + } else { + TraceP(this) << "unknown notify:" << type; + } + break; + } + + case MSG_AUDIO: + case MSG_VIDEO: { + if (!_push_src) { + if (_ring_reader) { + throw std::runtime_error("Rtmp player send media packets"); + } + if (packet->isConfigFrame()) { + auto id = packet->type_id; + _push_config_packets.emplace(id, std::move(packet)); + } + WarnL << "Rtmp pusher send media packet before handshake completed!"; + return; + } + + if (!_set_meta_data) { + _set_meta_data = true; + _push_src->setMetaData(_push_metadata ? _push_metadata : TitleMeta().getMetadata()); + } + if (!_push_config_packets.empty()) { + for (auto &pr : _push_config_packets) { + _push_src->onWrite(std::move(pr.second)); + } + _push_config_packets.clear(); + } + _push_src->onWrite(std::move(packet)); + break; + } + + default: + WarnP(this) << "unhandled message:" << (int) chunk_data.type_id << hexdump(chunk_data.buffer.data(), chunk_data.buffer.size()); + break; + } +} + +void RtmpSession::onCmd_seek(AMFDecoder &dec) { + dec.load();/* NULL */ + sendStatus({ "level", "status", + "code", "NetStream.Seek.Notify", + "description", "Seeking."}); + + auto milliSeconds = (uint32_t)(dec.load().as_number()); + InfoP(this) << "rtmp seekTo(ms):" << milliSeconds; + auto strong_src = _play_src.lock(); + if (strong_src) { + strong_src->seekTo(milliSeconds); + } +} + +void RtmpSession::onSendMedia(const RtmpPacket::Ptr &pkt) { + sendRtmp(pkt->type_id, pkt->stream_index, pkt, pkt->time_stamp, pkt->chunk_id); +} + +bool RtmpSession::close(MediaSource &sender) { + //此回调在其他线程触发 + string err = StrPrinter << "close media: " << sender.getUrl(); + safeShutdown(SockException(Err_shutdown, err)); + return true; +} + +int RtmpSession::totalReaderCount(MediaSource &sender) { + return _push_src ? _push_src->totalReaderCount() : sender.readerCount(); +} + +MediaOriginType RtmpSession::getOriginType(MediaSource &sender) const{ + return MediaOriginType::rtmp_push; +} + +string RtmpSession::getOriginUrl(MediaSource &sender) const { + return _media_info.full_url; +} + +std::shared_ptr RtmpSession::getOriginSock(MediaSource &sender) const { + return const_cast(this)->shared_from_this(); +} + +toolkit::EventPoller::Ptr RtmpSession::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + +void RtmpSession::setSocketFlags(){ + GET_CONFIG(int, merge_write_ms, General::kMergeWriteMS); + if (merge_write_ms > 0) { + //推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 + SockUtil::setNoDelay(getSock()->rawFD(), false); + //播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 + setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); + } +} + +void RtmpSession::dumpMetadata(const AMFValue &metadata) { + if (metadata.type() != AMF_OBJECT && metadata.type() != AMF_ECMA_ARRAY) { + WarnL << "invalid metadata type:" << metadata.type(); + return; + } + _StrPrinter printer; + metadata.object_for_each([&](const string &key, const AMFValue &val) { + printer << "\r\n" << key << "\t:" << val.to_string(); + }); + InfoL << _media_info.shortUrl() << (string) printer; +} +} /* namespace mediakit */ diff --git a/MediaServer/Rtmp/RtmpSession.h b/MediaServer/Rtmp/RtmpSession.h new file mode 100644 index 0000000..251475b --- /dev/null +++ b/MediaServer/Rtmp/RtmpSession.h @@ -0,0 +1,124 @@ +/* + * 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_RTMP_RTMPSESSION_H_ +#define SRC_RTMP_RTMPSESSION_H_ + +#include +#include "amf.h" +#include "Rtmp.h" +#include "utils.h" +#include "RtmpProtocol.h" +#include "RtmpMediaSourceImp.h" +#include "Util/TimeTicker.h" +#include "Network/Session.h" + +namespace mediakit { + +class RtmpSession : public toolkit::Session, public RtmpProtocol, public MediaSourceEvent { +public: + using Ptr = std::shared_ptr; + + RtmpSession(const toolkit::Socket::Ptr &sock); + + void onRecv(const toolkit::Buffer::Ptr &buf) override; + void onError(const toolkit::SockException &err) override; + void onManager() override; + +private: + void onProcessCmd(AMFDecoder &dec); + void onCmd_connect(AMFDecoder &dec); + void onCmd_createStream(AMFDecoder &dec); + + void onCmd_publish(AMFDecoder &dec); + void onCmd_deleteStream(AMFDecoder &dec); + + void onCmd_play(AMFDecoder &dec); + void onCmd_play2(AMFDecoder &dec); + void doPlay(AMFDecoder &dec); + void doPlayResponse(const std::string &err,const std::function &cb); + void sendPlayResponse(const std::string &err,const RtmpMediaSource::Ptr &src); + + void onCmd_seek(AMFDecoder &dec); + void onCmd_pause(AMFDecoder &dec); + void onCmd_playCtrl(AMFDecoder &dec); + void setMetaData(AMFDecoder &dec); + + void onSendMedia(const RtmpPacket::Ptr &pkt); + void onSendRawData(toolkit::Buffer::Ptr buffer) override{ + _total_bytes += buffer->size(); + send(std::move(buffer)); + } + void onRtmpChunk(RtmpPacket::Ptr chunk_data) override; + + template + inline void sendReply(const char *str, const first &reply, const second &status) { + AMFEncoder invoke; + invoke << str << _recv_req_id << reply << status; + sendResponse(MSG_CMD, invoke.data()); + } + + ///////MediaSourceEvent override/////// + // 关闭 [AUTO-TRANSLATED:92392f02] + // Close + bool close(MediaSource &sender) override; + // 播放总人数 [AUTO-TRANSLATED:c42a3161] + // Total number of plays + int totalReaderCount(MediaSource &sender) override; + // 获取媒体源类型 [AUTO-TRANSLATED:34290a69] + // Get media source type + MediaOriginType getOriginType(MediaSource &sender) const override; + // 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795] + // Get media source url or file path + std::string getOriginUrl(MediaSource &sender) const override; + // 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910] + // Get media source client related information + std::shared_ptr getOriginSock(MediaSource &sender) const override; + // 由于支持断连续推,存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40] + // Due to support for discontinuous pushing, there may be changes in OwnerPoller + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + + void setSocketFlags(); + std::string getStreamId(const std::string &str); + void dumpMetadata(const AMFValue &metadata); + void sendStatus(const std::initializer_list &key_value); + +private: + bool _set_meta_data = false; + double _recv_req_id = 0; + // 断连续推延时 [AUTO-TRANSLATED:13ad578a] + // Discontinuous pushing delay + uint32_t _continue_push_ms = 0; + // 消耗的总流量 [AUTO-TRANSLATED:45ad2785] + // Total traffic consumed + uint64_t _total_bytes = 0; + // 数据接收超时计时器 [AUTO-TRANSLATED:3fba518a] + // Data reception timeout timer + toolkit::Ticker _ticker; + MediaInfo _media_info; + std::weak_ptr _play_src; + AMFValue _push_metadata; + std::map _push_config_packets; + RtmpMediaSourceImp::Ptr _push_src; + std::shared_ptr _push_src_ownership; + RtmpMediaSource::RingType::RingReader::Ptr _ring_reader; +}; + +/** + * 支持ssl加密的rtmp服务器 + * Supports ssl encrypted rtmp server + + + * [AUTO-TRANSLATED:21d167ba] + */ +using RtmpSessionWithSSL = toolkit::SessionWithSSL; + +} /* namespace mediakit */ +#endif /* SRC_RTMP_RTMPSESSION_H_ */ diff --git a/MediaServer/Rtmp/amf.cpp b/MediaServer/Rtmp/amf.cpp new file mode 100644 index 0000000..b93fa2c --- /dev/null +++ b/MediaServer/Rtmp/amf.cpp @@ -0,0 +1,692 @@ +/* + * 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 +#include +#include "amf.h" +#include "utils.h" +#include "Util/util.h" +#include "Util/logger.h" +#include "Network/sockutil.h" +#include "Network/Buffer.h" + +using namespace std; +using namespace toolkit; + +/////////////////////AMFValue///////////////////////////// +inline void AMFValue::destroy() { + switch (_type) { + case AMF_STRING: + if (_value.string) { + delete _value.string; + _value.string = nullptr; + } + break; + case AMF_OBJECT: + case AMF_ECMA_ARRAY: + if (_value.object) { + delete _value.object; + _value.object = nullptr; + } + break; + case AMF_STRICT_ARRAY: + if (_value.array) { + delete _value.array; + _value.array = nullptr; + } + break; + default: + break; + } +} + +inline void AMFValue::init() { + switch (_type) { + case AMF_OBJECT: + case AMF_ECMA_ARRAY: + _value.object = new mapType; + break; + case AMF_STRING: + _value.string = new std::string; + break; + case AMF_STRICT_ARRAY: + _value.array = new arrayType; + break; + + default: + break; + } +} + +AMFValue::AMFValue(AMFType type) : + _type(type) { + init(); +} + +AMFValue::~AMFValue() { + destroy(); +} + +AMFValue::AMFValue(const char *s) : + _type(AMF_STRING) { + init(); + *_value.string = s; +} + +AMFValue::AMFValue(const std::string &s) : + _type(AMF_STRING) { + init(); + *_value.string = s; +} + +AMFValue::AMFValue(double n) : + _type(AMF_NUMBER) { + init(); + _value.number = n; +} + +AMFValue::AMFValue(int i) : + _type(AMF_INTEGER) { + init(); + _value.integer = i; +} + +AMFValue::AMFValue(bool b) : + _type(AMF_BOOLEAN) { + init(); + _value.boolean = b; +} + +AMFValue::AMFValue(const AMFValue &from) : + _type(AMF_NULL) { + *this = from; +} + +AMFValue& AMFValue::operator = (const AMFValue &from) { + destroy(); + _type = from._type; + init(); + switch (_type) { + case AMF_STRING: + *_value.string = (*from._value.string); + break; + case AMF_OBJECT: + case AMF_ECMA_ARRAY: + *_value.object = (*from._value.object); + break; + case AMF_STRICT_ARRAY: + *_value.array = (*from._value.array); + break; + case AMF_NUMBER: + _value.number = from._value.number; + break; + case AMF_INTEGER: + _value.integer = from._value.integer; + break; + case AMF_BOOLEAN: + _value.boolean = from._value.boolean; + break; + default: + break; + } + return *this; +} + +void AMFValue::clear() { + switch (_type) { + case AMF_STRING: + _value.string->clear(); + break; + case AMF_OBJECT: + case AMF_ECMA_ARRAY: + _value.object->clear(); + break; + default: + break; + } +} + +AMFType AMFValue::type() const { + return _type; +} + +const std::string &AMFValue::as_string() const { + if(_type != AMF_STRING){ + throw std::runtime_error("AMF not a string"); + } + return *_value.string; +} + +double AMFValue::as_number() const { + switch (_type) { + case AMF_NUMBER: + return _value.number; + case AMF_INTEGER: + return _value.integer; + case AMF_BOOLEAN: + return _value.boolean; + default: + throw std::runtime_error("AMF not a number"); + } +} + +int AMFValue::as_integer() const { + switch (_type) { + case AMF_NUMBER: + return (int)_value.number; + case AMF_INTEGER: + return _value.integer; + case AMF_BOOLEAN: + return _value.boolean; + default: + throw std::runtime_error("AMF not a integer"); + } +} + +bool AMFValue::as_boolean() const { + switch (_type) { + case AMF_NUMBER: + return _value.number; + case AMF_INTEGER: + return _value.integer; + case AMF_BOOLEAN: + return _value.boolean; + default: + throw std::runtime_error("AMF not a boolean"); + } +} + +string AMFValue::to_string() const{ + switch (_type) { + case AMF_NUMBER: + return StrPrinter << _value.number; + case AMF_INTEGER: + return StrPrinter << _value.integer; + case AMF_BOOLEAN: + return _value.boolean ? "true" : "false"; + case AMF_STRING: + return *(_value.string); + case AMF_OBJECT: + return "object"; + case AMF_NULL: + return "null"; + case AMF_UNDEFINED: + return "undefined"; + case AMF_ECMA_ARRAY: + return "ecma_array"; + case AMF_STRICT_ARRAY: + return "strict_array"; + default: + throw std::runtime_error("can not convert to string "); + } +} + +const AMFValue& AMFValue::operator[](const char *str) const { + if (_type != AMF_OBJECT && _type != AMF_ECMA_ARRAY) { + throw std::runtime_error("AMF not a object"); + } + auto i = _value.object->find(str); + if (i == _value.object->end()) { + static AMFValue val(AMF_NULL); + return val; + } + return i->second; +} + +void AMFValue::object_for_each(const function &fun) const { + if (_type != AMF_OBJECT && _type != AMF_ECMA_ARRAY) { + throw std::runtime_error("AMF not a object"); + } + for (auto & pr : *(_value.object)) { + fun(pr.first, pr.second); + } +} + +AMFValue::operator bool() const{ + return _type != AMF_NULL; +} +void AMFValue::set(const std::string &s, const AMFValue &val) { + if (_type != AMF_OBJECT && _type != AMF_ECMA_ARRAY) { + throw std::runtime_error("AMF not a object"); + } + _value.object->emplace(s, val); +} +void AMFValue::add(const AMFValue &val) { + if (_type != AMF_STRICT_ARRAY) { + throw std::runtime_error("AMF not a array"); + } + assert(_type == AMF_STRICT_ARRAY); + _value.array->push_back(val); +} + +const AMFValue::mapType &AMFValue::getMap() const { + if (_type != AMF_OBJECT && _type != AMF_ECMA_ARRAY) { + throw std::runtime_error("AMF not a object"); + } + return *_value.object; +} +const AMFValue::arrayType &AMFValue::getArr() const { + if (_type != AMF_STRICT_ARRAY) { + throw std::runtime_error("AMF not a array"); + } + return *_value.array; +} +/////////////////////////////////////////////////////////////////////////// + +enum { + AMF0_NUMBER, + AMF0_BOOLEAN, + AMF0_STRING, + AMF0_OBJECT, + AMF0_MOVIECLIP, + AMF0_NULL, + AMF0_UNDEFINED, + AMF0_REFERENCE, + AMF0_ECMA_ARRAY, + AMF0_OBJECT_END, + AMF0_STRICT_ARRAY, + AMF0_DATE, + AMF0_LONG_STRING, + AMF0_UNSUPPORTED, + AMF0_RECORD_SET, + AMF0_XML_OBJECT, + AMF0_TYPED_OBJECT, + AMF0_SWITCH_AMF3, +}; + +enum { + AMF3_UNDEFINED, + AMF3_NULL, + AMF3_FALSE, + AMF3_TRUE, + AMF3_INTEGER, + AMF3_NUMBER, + AMF3_STRING, + AMF3_LEGACY_XML, + AMF3_DATE, + AMF3_ARRAY, + AMF3_OBJECT, + AMF3_XML, + AMF3_BYTE_ARRAY, +}; + +////////////////////////////////Encoder////////////////////////////////////////// +AMFEncoder & AMFEncoder::operator <<(const char *s) { + if (s) { + buf += char(AMF0_STRING); + auto len = strlen(s); + assert(len <= 0xFFFF); + uint16_t str_len = htons((uint16_t)len); + buf.append((char *) &str_len, 2); + buf += s; + } else { + buf += char(AMF0_NULL); + } + return *this; +} + +AMFEncoder & AMFEncoder::operator <<(const std::string &s) { + if (!s.empty()) { + buf += char(AMF0_STRING); + assert(s.size() <= 0xFFFF); + uint16_t str_len = htons((uint16_t)s.size()); + buf.append((char *) &str_len, 2); + buf += s; + } else { + buf += char(AMF0_NULL); + } + return *this; +} + +AMFEncoder & AMFEncoder::operator <<(std::nullptr_t) { + buf += char(AMF0_NULL); + return *this; +} + +AMFEncoder & AMFEncoder::write_undefined() { + buf += char(AMF0_UNDEFINED); + return *this; + +} + +AMFEncoder & AMFEncoder::operator <<(const int n){ + return (*this) << (double)n; +} + +AMFEncoder & AMFEncoder::operator <<(const double n) { + buf += char(AMF0_NUMBER); + uint64_t encoded = 0; + memcpy(&encoded, &n, 8); + uint32_t val = htonl(encoded >> 32); + buf.append((char *) &val, 4); + val = htonl(encoded & 0xFFFFFFFF); + buf.append((char *) &val, 4); + return *this; +} + +AMFEncoder & AMFEncoder::operator <<(const bool b) { + buf += char(AMF0_BOOLEAN); + buf += char(b); + return *this; +} + +AMFEncoder & AMFEncoder::operator <<(const AMFValue& value) { + switch ((int) value.type()) { + case AMF_STRING: + *this << value.as_string(); + break; + case AMF_NUMBER: + *this << value.as_number(); + break; + case AMF_INTEGER: + *this << value.as_integer(); + break; + case AMF_BOOLEAN: + *this << value.as_boolean(); + break; + case AMF_OBJECT: { + buf += char(AMF0_OBJECT); + for (auto &pr : value.getMap()) { + write_key(pr.first); + *this << pr.second; + } + write_key(""); + buf += char(AMF0_OBJECT_END); + } + break; + case AMF_ECMA_ARRAY: { + buf += char(AMF0_ECMA_ARRAY); + uint32_t sz = htonl((uint32_t)value.getMap().size()); + buf.append((char *) &sz, 4); + for (auto &pr : value.getMap()) { + write_key(pr.first); + *this << pr.second; + } + write_key(""); + buf += char(AMF0_OBJECT_END); + } + break; + case AMF_NULL: + *this << nullptr; + break; + case AMF_UNDEFINED: + this->write_undefined(); + break; + case AMF_STRICT_ARRAY: { + buf += char(AMF0_STRICT_ARRAY); + uint32_t sz = htonl((uint32_t)value.getArr().size()); + buf.append((char *) &sz, 4); + for (auto &val : value.getArr()) { + *this << val; + } + //write_key(""); + //buf += char(AMF0_OBJECT_END); + } + break; + } + return *this; + +} + +void AMFEncoder::write_key(const std::string& s) { + assert(s.size() <= 0xFFFF); + uint16_t str_len = htons((uint16_t)s.size()); + buf.append((char *) &str_len, 2); + buf += s; +} + +void AMFEncoder::clear() { + buf.clear(); +} + +const std::string& AMFEncoder::data() const { + return buf; +} + +//////////////////Decoder////////////////// + +uint8_t AMFDecoder::front() { + if (pos >= buf.size()) { + throw std::runtime_error("Not enough data"); + } + return uint8_t(buf[pos]); +} + +uint8_t AMFDecoder::pop_front() { + if (version == 0 && front() == AMF0_SWITCH_AMF3) { + InfoL << "entering AMF3 mode"; + pos++; + version = 3; + } + + if (pos >= buf.size()) { + throw std::runtime_error("Not enough data"); + } + return uint8_t(buf[pos++]); +} + +template<> +double AMFDecoder::load() { + if (pop_front() != AMF0_NUMBER) { + throw std::runtime_error("Expected a number"); + } + if (pos + 8 > buf.size()) { + throw std::runtime_error("Not enough data"); + } + uint64_t val = ((uint64_t) load_be32(&buf[pos]) << 32) | load_be32(&buf[pos + 4]); + double n = 0; + static_assert(sizeof(n) == sizeof(val), "sizeof(double) not eq sizeof(uint64_t)"); + memcpy(&n, &val, sizeof(n)); + pos += 8; + return n; + +} + +template<> +bool AMFDecoder::load() { + if (pop_front() != AMF0_BOOLEAN) { + throw std::runtime_error("Expected a boolean"); + } + return pop_front() != 0; +} +template<> +unsigned int AMFDecoder::load() { + unsigned int value = 0; + for (int i = 0; i < 4; ++i) { + uint8_t b = pop_front(); + if (i == 3) { + /* use all bits from 4th byte */ + value = (value << 8) | b; + break; + } + value = (value << 7) | (b & 0x7f); + if ((b & 0x80) == 0) + break; + } + return value; +} + +template<> +int AMFDecoder::load() { + if (version == 3) { + return (int)load(); + } else { + return (int)load(); + } +} + +template<> +std::string AMFDecoder::load() { + size_t str_len = 0; + uint8_t type = pop_front(); + if (version == 3) { + if (type != AMF3_STRING) { + throw std::runtime_error("Expected a string"); + } + str_len = load() / 2; + + } else { + if (type != AMF0_STRING) { + throw std::runtime_error("Expected a string"); + } + if (pos + 2 > buf.size()) { + throw std::runtime_error("Not enough data"); + } + str_len = load_be16(&buf[pos]); + pos += 2; + } + if (pos + str_len > buf.size()) { + throw std::runtime_error("Not enough data"); + } + std::string s = buf.substr(pos, str_len); + pos += str_len; + return s; +} + +template<> +AMFValue AMFDecoder::load() { + uint8_t type = front(); + if (version == 3) { + switch (type) { + case AMF3_STRING: + return load(); + case AMF3_NUMBER: + return load(); + case AMF3_INTEGER: + return load(); + case AMF3_FALSE: + pos++; + return false; + case AMF3_TRUE: + pos++; + return true; + case AMF3_OBJECT: + return load_object(); + case AMF3_ARRAY: + return load_ecma(); + case AMF3_NULL: + pos++; + return AMF_NULL; + case AMF3_UNDEFINED: + pos++; + return AMF_UNDEFINED; + default: + throw std::runtime_error( + StrPrinter << "Unsupported AMF3 type:" << (int) type << endl); + } + } else { + switch (type) { + case AMF0_STRING: + return load(); + case AMF0_NUMBER: + return load(); + case AMF0_BOOLEAN: + return load(); + case AMF0_OBJECT: + return load_object(); + case AMF0_ECMA_ARRAY: + return load_ecma(); + case AMF0_NULL: + pos++; + return AMF_NULL; + case AMF0_UNDEFINED: + pos++; + return AMF_UNDEFINED; + case AMF0_STRICT_ARRAY: + return load_arr(); + default: + throw std::runtime_error( + StrPrinter << "Unsupported AMF type:" << (int) type << endl); + } + } + +} + +std::string AMFDecoder::load_key() { + if (pos + 2 > buf.size()) { + throw std::runtime_error("Not enough data"); + } + size_t str_len = load_be16(&buf[pos]); + pos += 2; + if (pos + str_len > buf.size()) { + throw std::runtime_error("Not enough data"); + } + std::string s = buf.substr(pos, str_len); + pos += str_len; + return s; + +} + +AMFValue AMFDecoder::load_object() { + AMFValue object(AMF_OBJECT); + if (pop_front() != AMF0_OBJECT) { + throw std::runtime_error("Expected an object"); + } + while (1) { + std::string key = load_key(); + if (key.empty()) + break; + AMFValue value = load(); + object.set(key, value); + } + if (pop_front() != AMF0_OBJECT_END) { + throw std::runtime_error("expected object end"); + } + return object; +} + +AMFValue AMFDecoder::load_ecma() { + /* ECMA array is the same as object, with 4 extra zero bytes */ + AMFValue object(AMF_ECMA_ARRAY); + if (pop_front() != AMF0_ECMA_ARRAY) { + throw std::runtime_error("Expected an ECMA array"); + } + if (pos + 4 > buf.size()) { + throw std::runtime_error("Not enough data"); + } + pos += 4; + while (1) { + std::string key = load_key(); + if (key.empty()) + break; + AMFValue value = load(); + object.set(key, value); + } + if (pop_front() != AMF0_OBJECT_END) { + throw std::runtime_error("expected object end"); + } + return object; +} +AMFValue AMFDecoder::load_arr() { + /* ECMA array is the same as object, with 4 extra zero bytes */ + AMFValue object(AMF_STRICT_ARRAY); + if (pop_front() != AMF0_STRICT_ARRAY) { + throw std::runtime_error("Expected an STRICT array"); + } + if (pos + 4 > buf.size()) { + throw std::runtime_error("Not enough data"); + } + int arrSize = load_be32(&buf[pos]); + pos += 4; + while (arrSize--) { + AMFValue value = load(); + object.add(value); + } + /*pos += 2; + if (pop_front() != AMF0_OBJECT_END) { + throw std::runtime_error("expected object end"); + }*/ + return object; +} + +AMFDecoder::AMFDecoder(const BufferLikeString &buf_in, size_t pos_in, int version_in) : + buf(buf_in), pos(pos_in), version(version_in) { +} + diff --git a/MediaServer/Rtmp/amf.h b/MediaServer/Rtmp/amf.h new file mode 100644 index 0000000..c3f7ed2 --- /dev/null +++ b/MediaServer/Rtmp/amf.h @@ -0,0 +1,126 @@ +/* + * 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 __amf_h +#define __amf_h + +#include +#include +#include +#include +#include +#include +namespace toolkit { + class BufferLikeString; +} +enum AMFType { + AMF_NUMBER, + AMF_INTEGER, + AMF_BOOLEAN, + AMF_STRING, + AMF_OBJECT, + AMF_NULL, + AMF_UNDEFINED, + AMF_ECMA_ARRAY, + AMF_STRICT_ARRAY, +}; + +class AMFValue; + +class AMFValue { +public: + friend class AMFEncoder; + + using mapType = std::map; + using arrayType = std::vector; + + ~AMFValue(); + AMFValue(AMFType type = AMF_NULL); + AMFValue(const char *s); + AMFValue(const std::string &s); + AMFValue(double n); + AMFValue(int i); + AMFValue(bool b); + AMFValue(const AMFValue &from); + AMFValue &operator = (const AMFValue &from); + + void clear(); + AMFType type() const ; + const std::string &as_string() const; + double as_number() const; + int as_integer() const; + bool as_boolean() const; + std::string to_string() const; + const AMFValue &operator[](const char *str) const; + void object_for_each(const std::function &fun) const ; + operator bool() const; + void set(const std::string &s, const AMFValue &val); + void add(const AMFValue &val); + +private: + const mapType &getMap() const; + const arrayType &getArr() const; + void destroy(); + void init(); + +private: + AMFType _type; + union { + std::string *string; + double number; + int integer; + bool boolean; + mapType *object; + arrayType *array; + } _value; +}; + +class AMFDecoder { +public: + AMFDecoder(const toolkit::BufferLikeString &buf, size_t pos, int version = 0); + template + TP load(); + +private: + std::string load_key(); + AMFValue load_object(); + AMFValue load_ecma(); + AMFValue load_arr(); + uint8_t front(); + uint8_t pop_front(); + +private: + const toolkit::BufferLikeString &buf; + size_t pos; + int version; +}; + +class AMFEncoder { +public: + AMFEncoder & operator <<(const char *s); + AMFEncoder & operator <<(const std::string &s); + AMFEncoder & operator <<(std::nullptr_t); + AMFEncoder & operator <<(const int n); + AMFEncoder & operator <<(const double n); + AMFEncoder & operator <<(const bool b); + AMFEncoder & operator <<(const AMFValue &value); + const std::string& data() const ; + void clear() ; + +private: + void write_key(const std::string &s); + AMFEncoder &write_undefined(); + +private: + std::string buf; +}; + + +#endif diff --git a/MediaServer/Rtmp/utils.cpp b/MediaServer/Rtmp/utils.cpp new file mode 100644 index 0000000..cb36447 --- /dev/null +++ b/MediaServer/Rtmp/utils.cpp @@ -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 "utils.h" +#include +#include +#include +#include +#include "Util/util.h" +#include "Network/sockutil.h" + +using namespace toolkit; + +/* + * Used to do unaligned loads on archs that don't support them. GCC can mostly + * optimize these away. + */ +uint32_t load_be32(const void *p) +{ + uint32_t val; + memcpy(&val, p, sizeof val); + return ntohl(val); +} + +uint16_t load_be16(const void *p) +{ + uint16_t val; + memcpy(&val, p, sizeof val); + return ntohs(val); +} + +uint32_t load_le32(const void *p) +{ + const uint8_t *data = (const uint8_t *) p; + return data[0] | ((uint32_t) data[1] << 8) | + ((uint32_t) data[2] << 16) | ((uint32_t) data[3] << 24); +} + +uint32_t load_be24(const void *p) +{ + const uint8_t *data = (const uint8_t *) p; + return data[2] | ((uint32_t) data[1] << 8) | ((uint32_t) data[0] << 16); +} + +void set_be24(void *p, uint32_t val) +{ + uint8_t *data = (uint8_t *) p; + data[0] = val >> 16; + data[1] = val >> 8; + data[2] = val; +} + +void set_le32(void *p, uint32_t val) +{ + uint8_t *data = (uint8_t *) p; + data[0] = val; + data[1] = val >> 8; + data[2] = val >> 16; + data[3] = val >> 24; +} + +void set_be32(void *p, uint32_t val) +{ + uint8_t *data = (uint8_t *) p; + data[3] = val; + data[2] = val >> 8; + data[1] = val >> 16; + data[0] = val >> 24; +} + diff --git a/MediaServer/Rtmp/utils.h b/MediaServer/Rtmp/utils.h new file mode 100644 index 0000000..3e13a7f --- /dev/null +++ b/MediaServer/Rtmp/utils.h @@ -0,0 +1,27 @@ +/* + * 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 __utils_h +#define __utils_h + +#include +#include + + +uint32_t load_be32(const void *p); +uint16_t load_be16(const void *p); +uint32_t load_be24(const void *p); +uint32_t load_le32(const void *p); +void set_be24(void *p, uint32_t val); +void set_le32(void *p, uint32_t val); +void set_be32(void *p, uint32_t val); + + +#endif diff --git a/MediaServer/Rtp/Decoder.cpp b/MediaServer/Rtp/Decoder.cpp new file mode 100644 index 0000000..5a14870 --- /dev/null +++ b/MediaServer/Rtp/Decoder.cpp @@ -0,0 +1,160 @@ +/* + * 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 "Decoder.h" +#include "PSDecoder.h" +#include "TSDecoder.h" +#include "Extension/Factory.h" + +#if defined(ENABLE_RTPPROXY) || defined(ENABLE_HLS) +#include "mpeg-ts.h" +#endif + +using namespace toolkit; + +namespace mediakit { + +void Decoder::setOnDecode(Decoder::onDecode cb) { + _on_decode = std::move(cb); +} + +void Decoder::setOnStream(Decoder::onStream cb) { + _on_stream = std::move(cb); +} + +static Decoder::Ptr createDecoder_l(DecoderImp::Type type) { + switch (type){ + case DecoderImp::decoder_ps: +#ifdef ENABLE_RTPPROXY + return std::make_shared(); +#else + WarnL << "创建ps解复用器失败,请打开ENABLE_RTPPROXY然后重新编译"; + return nullptr; +#endif//ENABLE_RTPPROXY + + case DecoderImp::decoder_ts: +#ifdef ENABLE_HLS + return std::make_shared(); +#else + WarnL << "创建mpegts解复用器失败,请打开ENABLE_HLS然后重新编译"; + return nullptr; +#endif//ENABLE_HLS + + default: return nullptr; + } +} + +///////////////////////////////////////////////////////////// + +DecoderImp::Ptr DecoderImp::createDecoder(Type type, MediaSinkInterface *sink){ + auto decoder = createDecoder_l(type); + if(!decoder){ + return nullptr; + } + return DecoderImp::Ptr(new DecoderImp(decoder, sink)); +} + +void DecoderImp::flush() { + for (auto &pr : _tracks) { + pr.second.second.flush(); + } +} + +ssize_t DecoderImp::input(const uint8_t *data, size_t bytes){ + return _decoder->input(data, bytes); +} + +DecoderImp::DecoderImp(const Decoder::Ptr &decoder, MediaSinkInterface *sink){ + _decoder = decoder; + _sink = sink; + _decoder->setOnDecode([this](int stream, int codecid, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes) { + onDecode(stream, codecid, flags, pts, dts, data, bytes); + }); + _decoder->setOnStream([this](int stream, int codecid, const void *extra, size_t bytes, int finish) { + onStream(stream, codecid, extra, bytes, finish); + }); +} + +#if defined(ENABLE_RTPPROXY) || defined(ENABLE_HLS) + +void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t bytes, int finish) { + if (_finished) { + return; + } + // G711传统只支持 8000/1/16的规格,FFmpeg貌似做了扩展,但是这里不管它了 [AUTO-TRANSLATED:851813f7] + // G711 traditionally only supports the 8000/1/16 specification. FFmpeg seems to have extended it, but we'll ignore that here. + auto track = Factory::getTrackByCodecId(getCodecByMpegId(codecid), 8000, 1, 16); + if (track) { + onTrack(stream, std::move(track)); + } + // 防止未获取视频track提前complete导致忽略后续视频的问题,用于兼容一些不太规范的ps流 [AUTO-TRANSLATED:d6b349b5] + // Prevent the problem of ignoring subsequent video due to premature completion of the video track before it is obtained. This is used to be compatible with some non-standard PS streams. + if (finish && _have_video) { + _finished = true; + _sink->addTrackCompleted(); + InfoL << "Add track finished"; + } +} + +void DecoderImp::onDecode(int stream, int codecid, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes) { + pts /= 90; + dts /= 90; + + auto codec = getCodecByMpegId(codecid); + if (codec == CodecInvalid) { + return; + } + auto &ref = _tracks[stream]; + if (!ref.first) { + onTrack(stream, Factory::getTrackByCodecId(codec, 8000, 1, 16)); + } + if (!ref.first) { + WarnL << "Unsupported codec :" << getCodecName(codec); + return; + } + auto frame = Factory::getFrameFromPtr(codec, (char *)data, bytes, dts, pts); + if (getTrackType(codec) != TrackVideo) { + onFrame(stream, frame); + return; + } + ref.second.inputFrame(frame, [this, stream, codec](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool) { + onFrame(stream, Factory::getFrameFromBuffer(codec, buffer, dts, pts)); + }); +} +#else +void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t dts,const void *data,size_t bytes) {} +void DecoderImp::onStream(int stream,int codecid,const void *extra,size_t bytes,int finish) {} +#endif + +void DecoderImp::onTrack(int index, const Track::Ptr &track) { + if (!track) { + return; + } + track->setIndex(index); + auto &ref = _tracks[index]; + if (ref.first) { + WarnL << "Already existed a same track: " << index << ", codec: " << track->getCodecName(); + return; + } + ref.first = track; + _sink->addTrack(track); + InfoL << "Got track: " << track->getCodecName(); + _have_video = track->getTrackType() == TrackVideo ? true : _have_video; +} + +void DecoderImp::onFrame(int index, const Frame::Ptr &frame) { + if (frame) { + frame->setIndex(index); + _sink->inputFrame(frame); + } +} + +}//namespace mediakit + diff --git a/MediaServer/Rtp/Decoder.h b/MediaServer/Rtp/Decoder.h new file mode 100644 index 0000000..4902157 --- /dev/null +++ b/MediaServer/Rtp/Decoder.h @@ -0,0 +1,73 @@ +/* + * 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_DECODER_H +#define ZLMEDIAKIT_DECODER_H + +#include +#include +#include +#include "Common/MediaSink.h" + +namespace mediakit { + +class Decoder { +public: + using Ptr = std::shared_ptr; + using onDecode = std::function; + using onStream = std::function; + + virtual ssize_t input(const uint8_t *data, size_t bytes) = 0; + void setOnDecode(onDecode cb); + void setOnStream(onStream cb); + +protected: + Decoder() = default; + virtual ~Decoder() = default; + +protected: + onDecode _on_decode; + onStream _on_stream; +}; + +class DecoderImp { +public: + typedef enum { decoder_ts = 0, decoder_ps } Type; + + using Ptr = std::shared_ptr; + + static Ptr createDecoder(Type type, MediaSinkInterface *sink); + ssize_t input(const uint8_t *data, size_t bytes); + void flush(); + +protected: + void onTrack(int index, const Track::Ptr &track); + void onFrame(int index, const Frame::Ptr &frame); + +private: + DecoderImp(const Decoder::Ptr &decoder, MediaSinkInterface *sink); + void onDecode(int stream, int codecid, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes); + void onStream(int stream, int codecid, const void *extra, size_t bytes, int finish); + +private: + bool _finished = false; + bool _have_video = false; + Decoder::Ptr _decoder; + MediaSinkInterface *_sink; + + class FrameMergerImp : public FrameMerger { + public: + FrameMergerImp() : FrameMerger(FrameMerger::none) {} + }; + std::unordered_map > _tracks; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_DECODER_H diff --git a/MediaServer/Rtp/GB28181Process.cpp b/MediaServer/Rtp/GB28181Process.cpp new file mode 100644 index 0000000..26210d0 --- /dev/null +++ b/MediaServer/Rtp/GB28181Process.cpp @@ -0,0 +1,207 @@ +/* + * 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_RTPPROXY) +#include "GB28181Process.h" +#include "Extension/CommonRtp.h" +#include "Extension/Factory.h" +#include "Http/HttpTSPlayer.h" +#include "Util/File.h" +#include "Common/config.h" +#include "Rtsp/RtpReceiver.h" +#include "Rtsp/Rtsp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +// 判断是否为ts负载 [AUTO-TRANSLATED:77d1aa3c] +// Determine if it is a ts payload +static inline bool checkTS(const uint8_t *packet, size_t bytes) { + return bytes % TS_PACKET_SIZE == 0 && packet[0] == TS_SYNC_BYTE; +} + +class RtpReceiverImp : public RtpTrackImp { +public: + using Ptr = std::shared_ptr; + + RtpReceiverImp(int sample_rate, RtpTrackImp::OnSorted cb, RtpTrackImp::BeforeSorted cb_before = nullptr) { + _sample_rate = sample_rate; + setOnSorted(std::move(cb)); + setBeforeSorted(std::move(cb_before)); + // GB28181推流不支持ntp时间戳 [AUTO-TRANSLATED:f661f052] + // GB28181 streaming does not support ntp timestamps + setNtpStamp(0, 0); + } + + bool inputRtp(TrackType type, uint8_t *ptr, size_t len) { + return RtpTrack::inputRtp(type, _sample_rate, ptr, len).operator bool(); + } + +private: + int _sample_rate; +}; + +/////////////////////////////////////////////////////////////////////////////////////////// + +GB28181Process::GB28181Process(const MediaInfo &media_info, MediaSinkInterface *sink) { + assert(sink); + _media_info = media_info; + _interface = sink; +} + +void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp) { + _rtp_decoder[rtp->getHeader()->pt]->inputRtp(rtp, false); +} + +void GB28181Process::flush() { + if (_decoder) { + _decoder->flush(); + } +} + +bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) { + GET_CONFIG(uint32_t, h264_pt, RtpProxy::kH264PT); + GET_CONFIG(uint32_t, h265_pt, RtpProxy::kH265PT); + GET_CONFIG(uint32_t, ps_pt, RtpProxy::kPSPT); + GET_CONFIG(uint32_t, opus_pt, RtpProxy::kOpusPT); + + RtpHeader *header = (RtpHeader *)data; + auto pt = header->pt; + auto &ref = _rtp_receiver[pt]; + if (!ref) { + if (_rtp_receiver.size() > 2) { + // 防止pt类型太多导致内存溢出 [AUTO-TRANSLATED:7695e49b] + // Prevent too many pt types from causing memory overflow + WarnL << "Rtp payload type more than 2 types: " << _rtp_receiver.size(); + } + switch (pt) { + case Rtsp::PT_PCMA: + case Rtsp::PT_PCMU: { + // CodecG711U or CodecG711A + ref = std::make_shared(8000, [this](RtpPacket::Ptr rtp) { onRtpSorted(std::move(rtp)); }); + auto track = Factory::getTrackByCodecId(pt == Rtsp::PT_PCMU ? CodecG711U : CodecG711A, 8000, 1, 16); + CHECK(track); + track->setIndex(pt); + _interface->addTrack(track); + _rtp_decoder[pt] = Factory::getRtpDecoderByCodecId(track->getCodecId()); + break; + } + case Rtsp::PT_JPEG: { + // mjpeg + ref = std::make_shared(90000, [this](RtpPacket::Ptr rtp) { onRtpSorted(std::move(rtp)); }); + auto track = Factory::getTrackByCodecId(CodecJPEG); + CHECK(track); + track->setIndex(pt); + _interface->addTrack(track); + _rtp_decoder[pt] = Factory::getRtpDecoderByCodecId(track->getCodecId()); + break; + } + default: { + if (pt == opus_pt) { + // opus负载 [AUTO-TRANSLATED:defa6a8d] + // opus payload + ref = std::make_shared(48000, [this](RtpPacket::Ptr rtp) { onRtpSorted(std::move(rtp)); }); + auto track = Factory::getTrackByCodecId(CodecOpus); + CHECK(track); + track->setIndex(pt); + _interface->addTrack(track); + _rtp_decoder[pt] = Factory::getRtpDecoderByCodecId(track->getCodecId()); + } else if (pt == h265_pt) { + // H265负载 [AUTO-TRANSLATED:61fbcf7f] + // H265 payload + ref = std::make_shared(90000, [this](RtpPacket::Ptr rtp) { onRtpSorted(std::move(rtp)); }); + auto track = Factory::getTrackByCodecId(CodecH265); + CHECK(track); + track->setIndex(pt); + _interface->addTrack(track); + _rtp_decoder[pt] = Factory::getRtpDecoderByCodecId(track->getCodecId()); + } else if (pt == h264_pt) { + // H264负载 [AUTO-TRANSLATED:6f3fbb0d] + // H264 payload + ref = std::make_shared(90000, [this](RtpPacket::Ptr rtp) { onRtpSorted(std::move(rtp)); }); + auto track = Factory::getTrackByCodecId(CodecH264); + CHECK(track); + track->setIndex(pt); + _interface->addTrack(track); + _rtp_decoder[pt] = Factory::getRtpDecoderByCodecId(track->getCodecId()); + } else { + if (pt != Rtsp::PT_MP2T && pt != ps_pt) { + WarnL << "Unknown rtp payload type(" << (int)pt << "), decode it as mpeg-ps or mpeg-ts"; + } + ref = std::make_shared(90000, [this](RtpPacket::Ptr rtp) { onRtpSorted(std::move(rtp)); }); + // ts或ps负载 [AUTO-TRANSLATED:3ca31480] + // ts or ps payload + _rtp_decoder[pt] = std::make_shared(CodecInvalid, 32 * 1024); + // 设置dump目录 [AUTO-TRANSLATED:23c88ace] + // Set dump directory + GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir); + if (!dump_dir.empty()) { + auto save_path = File::absolutePath(_media_info.stream + ".mpeg", dump_dir); + _save_file_ps.reset(File::create_file(save_path.data(), "wb"), [](FILE *fp) { + if (fp) { + fclose(fp); + } + }); + } + } + break; + } + } + // 设置frame回调 [AUTO-TRANSLATED:dec7590f] + // Set frame callback + _rtp_decoder[pt]->addDelegate([this, pt](const Frame::Ptr &frame) { + frame->setIndex(pt); + onRtpDecode(frame); + return true; + }); + } + + return ref->inputRtp(TrackVideo, (unsigned char *)data, data_len); +} + +void GB28181Process::onRtpDecode(const Frame::Ptr &frame) { + if (frame->getCodecId() != CodecInvalid) { + // 这里不是ps或ts [AUTO-TRANSLATED:6f79ac69] + // This is not ps or ts + _interface->inputFrame(frame); + return; + } + + // 这是TS或PS [AUTO-TRANSLATED:55782860] + // This is TS or PS + if (_save_file_ps) { + fwrite(frame->data(), frame->size(), 1, _save_file_ps.get()); + } + + if (!_decoder) { + // 创建解码器 [AUTO-TRANSLATED:0cc03d90] + // Create decoder + if (checkTS((uint8_t *)frame->data(), frame->size())) { + // 猜测是ts负载 [AUTO-TRANSLATED:c2be3a47] + // Guess it is a ts payload + InfoL << _media_info.stream << " judged to be TS"; + _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _interface); + } else { + // 猜测是ps负载 [AUTO-TRANSLATED:b7c0ff45] + // Guess it is a ps payload + InfoL << _media_info.stream << " judged to be PS"; + _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ps, _interface); + } + } + + if (_decoder) { + _decoder->input(reinterpret_cast(frame->data()), frame->size()); + } +} + +} // namespace mediakit +#endif // defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/GB28181Process.h b/MediaServer/Rtp/GB28181Process.h new file mode 100644 index 0000000..d2db13a --- /dev/null +++ b/MediaServer/Rtp/GB28181Process.h @@ -0,0 +1,71 @@ +/* + * 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_GB28181ROCESS_H +#define ZLMEDIAKIT_GB28181ROCESS_H + +#if defined(ENABLE_RTPPROXY) + +#include "Decoder.h" +#include "ProcessInterface.h" +#include "Http/HttpRequestSplitter.h" +#include "Rtsp/RtpCodec.h" +#include "Common/MediaSource.h" + +namespace mediakit{ + +class RtpReceiverImp; +class GB28181Process : public ProcessInterface { +public: + using Ptr = std::shared_ptr; + + GB28181Process(const MediaInfo &media_info, MediaSinkInterface *sink); + + /** + * 输入rtp + * @param data rtp数据指针 + * @param data_len rtp数据长度 + * @return 是否解析成功 + * Input rtp + * @param data rtp data pointer + * @param data_len rtp data length + * @return Whether the parsing is successful + + * [AUTO-TRANSLATED:d7b14ffe] + */ + bool inputRtp(bool, const char *data, size_t data_len) override; + + /** + * 刷新输出所有缓存 + * Refresh and output all caches + + + * [AUTO-TRANSLATED:4509b01f] + */ + void flush() override; + +protected: + void onRtpSorted(RtpPacket::Ptr rtp); + +private: + void onRtpDecode(const Frame::Ptr &frame); + +private: + MediaInfo _media_info; + DecoderImp::Ptr _decoder; + MediaSinkInterface *_interface; + std::shared_ptr _save_file_ps; + std::unordered_map _rtp_decoder; + std::unordered_map > _rtp_receiver; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_GB28181ROCESS_H diff --git a/MediaServer/Rtp/PSDecoder.cpp b/MediaServer/Rtp/PSDecoder.cpp new file mode 100644 index 0000000..21014b4 --- /dev/null +++ b/MediaServer/Rtp/PSDecoder.cpp @@ -0,0 +1,79 @@ +/* + * 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_RTPPROXY) + +#include "PSDecoder.h" +#include "mpeg-ps.h" + +using namespace toolkit; + +namespace mediakit{ + +PSDecoder::PSDecoder() { + _ps_demuxer = ps_demuxer_create([](void* param, + int stream, + int codecid, + int flags, + int64_t pts, + int64_t dts, + const void* data, + size_t bytes){ + PSDecoder *thiz = (PSDecoder *)param; + if(thiz->_on_decode){ + thiz->_on_decode(stream, codecid, flags, pts, dts, data, bytes); + } + return 0; + },this); + + ps_demuxer_notify_t notify = { + [](void *param, int stream, int codecid, const void *extra, int bytes, int finish) { + PSDecoder *thiz = (PSDecoder *) param; + if (thiz->_on_stream) { + thiz->_on_stream(stream, codecid, extra, bytes, finish); + } + } + }; + ps_demuxer_set_notify((struct ps_demuxer_t *) _ps_demuxer, ¬ify, this); +} + +PSDecoder::~PSDecoder() { + ps_demuxer_destroy((struct ps_demuxer_t*)_ps_demuxer); +} + +ssize_t PSDecoder::input(const uint8_t *data, size_t bytes) { + HttpRequestSplitter::input(reinterpret_cast(data), bytes); + return bytes; +} + +const char *PSDecoder::onSearchPacketTail(const char *data, size_t len) { + try { + auto ret = ps_demuxer_input(static_cast(_ps_demuxer), reinterpret_cast(data), len); + if (ret >= 0) { + // 解析成功全部或部分 [AUTO-TRANSLATED:a8085d34] + // Parse successful, all or part + return data + ret; + } + + // 解析失败,丢弃所有数据 [AUTO-TRANSLATED:e6f644d9] + // Parse failed, discard all data + return data + len; + } catch (toolkit::AssertFailedException &ex) { + InfoL << "解析 ps 异常: bytes=" << len + << ", exception=" << ex.what() + << ", hex=" << hexdump(data, MIN(len, 32)); + // 触发断言,解析失败,丢弃所有数据 [AUTO-TRANSLATED:b60c6db0] + // Trigger assertion, parse failed, discard all data + return data + len; + } +} + +}//namespace mediakit +#endif//#if defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/PSDecoder.h b/MediaServer/Rtp/PSDecoder.h new file mode 100644 index 0000000..268836d --- /dev/null +++ b/MediaServer/Rtp/PSDecoder.h @@ -0,0 +1,42 @@ +/* + * 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_PSDECODER_H +#define ZLMEDIAKIT_PSDECODER_H + +#if defined(ENABLE_RTPPROXY) +#include +#include "Decoder.h" +#include "Http/HttpRequestSplitter.h" + +namespace mediakit{ + +// ps解析器 [AUTO-TRANSLATED:f156a1f1] +// ps parser +class PSDecoder : public Decoder, private HttpRequestSplitter { +public: + PSDecoder(); + ~PSDecoder(); + + ssize_t input(const uint8_t* data, size_t bytes) override; + + // HttpRequestSplitter interface +private: + using HttpRequestSplitter::input; + const char *onSearchPacketTail(const char *data, size_t len) override; + ssize_t onRecvHeader(const char *, size_t) override { return 0; }; + +private: + void *_ps_demuxer = nullptr; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_PSDECODER_H diff --git a/MediaServer/Rtp/PSEncoder.cpp b/MediaServer/Rtp/PSEncoder.cpp new file mode 100644 index 0000000..24cb8ce --- /dev/null +++ b/MediaServer/Rtp/PSEncoder.cpp @@ -0,0 +1,54 @@ +/* + * 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_RTPPROXY) + +#include "PSEncoder.h" +#include "Common/config.h" +#include "Extension/CommonRtp.h" +#include "Rtsp/RtspMuxer.h" + +using namespace toolkit; + +namespace mediakit{ + +PSEncoderImp::PSEncoderImp(uint32_t ssrc, uint8_t payload_type, bool ps_or_ts) : MpegMuxer(ps_or_ts) { + GET_CONFIG(uint32_t, s_video_mtu, Rtp::kVideoMtuSize); + _rtp_encoder = std::make_shared(); + auto video_mtu = s_video_mtu; + if (!ps_or_ts) { + // 确保ts rtp负载部分长度是188的倍数 [AUTO-TRANSLATED:ad7aa6c0] + // Ensure the ts rtp payload length is a multiple of 188 + video_mtu = RtpPacket::kRtpHeaderSize + (s_video_mtu - (s_video_mtu % 188)); + if (video_mtu > s_video_mtu) { + video_mtu -= 188; + } + } + _rtp_encoder->setRtpInfo(ssrc, video_mtu, 90000, payload_type); + auto ring = std::make_shared(); + ring->setDelegate(std::make_shared([this](RtpPacket::Ptr rtp, bool is_key) { onRTP(std::move(rtp), is_key); })); + _rtp_encoder->setRtpRing(std::move(ring)); + InfoL << this << " " << ssrc; +} + +PSEncoderImp::~PSEncoderImp() { + InfoL << this; +} + +void PSEncoderImp::onWrite(std::shared_ptr buffer, uint64_t stamp, bool key_pos) { + if (!buffer) { + return; + } + _rtp_encoder->inputFrame(std::make_shared(CodecH264/*只用于识别为视频*/, buffer->data(), buffer->size(), stamp, stamp, 0, key_pos)); +} + +}//namespace mediakit + +#endif//defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/PSEncoder.h b/MediaServer/Rtp/PSEncoder.h new file mode 100644 index 0000000..08897d8 --- /dev/null +++ b/MediaServer/Rtp/PSEncoder.h @@ -0,0 +1,55 @@ +/* + * 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_PSENCODER_H +#define ZLMEDIAKIT_PSENCODER_H + +#if defined(ENABLE_RTPPROXY) + +#include "Record/MPEG.h" +#include "Common/MediaSink.h" + +namespace mediakit { + +class CommonRtpEncoder; + +class PSEncoderImp : public MpegMuxer { +public: + /** + * 创建psh或ts rtp编码器 + * @param ssrc rtp的ssrc + * @param payload_type rtp的pt + * @param ps_or_ts true: ps, false: ts + * Create a psh or ts rtp encoder + * @param ssrc rtp's ssrc + * @param payload_type rtp's pt + * @param ps_or_ts true: ps, false: ts + + * [AUTO-TRANSLATED:b79d8b65] + */ + PSEncoderImp(uint32_t ssrc, uint8_t payload_type = 96, bool ps_or_ts = true); + ~PSEncoderImp() override; + +protected: + // rtp打包后回调 [AUTO-TRANSLATED:8f88aef9] + // Callback after rtp packaging + virtual void onRTP(toolkit::Buffer::Ptr rtp, bool is_key = false) = 0; + +protected: + void onWrite(std::shared_ptr buffer, uint64_t stamp, bool key_pos) override; + +private: + std::shared_ptr _rtp_encoder; +}; + +}//namespace mediakit + +#endif //ENABLE_RTPPROXY +#endif //ZLMEDIAKIT_PSENCODER_H diff --git a/MediaServer/Rtp/ProcessInterface.h b/MediaServer/Rtp/ProcessInterface.h new file mode 100644 index 0000000..deda6cd --- /dev/null +++ b/MediaServer/Rtp/ProcessInterface.h @@ -0,0 +1,52 @@ +/* + * 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_PROCESSINTERFACE_H +#define ZLMEDIAKIT_PROCESSINTERFACE_H + +#include +#include + +namespace mediakit { + +class ProcessInterface { +public: + using Ptr = std::shared_ptr; + virtual ~ProcessInterface() = default; + + /** + * 输入rtp + * @param is_udp 是否为udp模式 + * @param data rtp数据指针 + * @param data_len rtp数据长度 + * @return 是否解析成功 + * Input rtp + * @param is_udp Whether it is udp mode + * @param data rtp data pointer + * @param data_len rtp data length + * @return Whether the parsing is successful + + * [AUTO-TRANSLATED:7d5b06f0] + */ + virtual bool inputRtp(bool is_udp, const char *data, size_t data_len) = 0; + + /** + * 刷新输出所有缓存 + * Refresh and output all caches + + + * [AUTO-TRANSLATED:4509b01f] + */ + virtual void flush() {} +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_PROCESSINTERFACE_H diff --git a/MediaServer/Rtp/RawEncoder.cpp b/MediaServer/Rtp/RawEncoder.cpp new file mode 100644 index 0000000..c14254b --- /dev/null +++ b/MediaServer/Rtp/RawEncoder.cpp @@ -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. + */ + +#if defined(ENABLE_RTPPROXY) + +#include "RawEncoder.h" +#include "Extension/Factory.h" +#include "Rtsp/RtspMuxer.h" +#include "Common//config.h" + +using namespace toolkit; + +namespace mediakit { + +RawEncoderImp::RawEncoderImp(uint32_t ssrc, uint8_t payload_type, bool send_audio) + : _send_audio(send_audio) + , _payload_type(payload_type) + , _ssrc(ssrc) {} + +RawEncoderImp::~RawEncoderImp() { + InfoL << this << " " << printSSRC(_ssrc); +} + +bool RawEncoderImp::addTrack(const Track::Ptr &track) { + if (_send_audio && track->getTrackType() == TrackType::TrackAudio && !_rtp_encoder) { // audio + _rtp_encoder = createRtpEncoder(track); + auto ring = std::make_shared(); + ring->setDelegate(std::make_shared([this](RtpPacket::Ptr rtp, bool is_key) { onRTP(std::move(rtp), true); })); + _rtp_encoder->setRtpRing(std::move(ring)); + if (track->getCodecId() == CodecG711A || track->getCodecId() == CodecG711U) { + GET_CONFIG(uint32_t, dur_ms, RtpProxy::kRtpG711DurMs); + Any param; + param.set(dur_ms); + _rtp_encoder->setOpt(RtpCodec::RTP_ENCODER_PKT_DUR_MS, param); + } + return true; + } + + if (!_send_audio && track->getTrackType() == TrackType::TrackVideo && !_rtp_encoder) { + _rtp_encoder = createRtpEncoder(track); + auto ring = std::make_shared(); + ring->setDelegate(std::make_shared([this](RtpPacket::Ptr rtp, bool is_key) { onRTP(std::move(rtp), is_key); })); + _rtp_encoder->setRtpRing(std::move(ring)); + return true; + } + return true; +} + +void RawEncoderImp::resetTracks() { + return; +} + +bool RawEncoderImp::inputFrame(const Frame::Ptr &frame) { + if (frame->getTrackType() == TrackType::TrackAudio && _send_audio && _rtp_encoder) { + _rtp_encoder->inputFrame(frame); + } + + if (frame->getTrackType() == TrackType::TrackVideo && !_send_audio && _rtp_encoder) { + _rtp_encoder->inputFrame(frame); + } + return true; +} + +RtpCodec::Ptr RawEncoderImp::createRtpEncoder(const Track::Ptr &track) { + GET_CONFIG(uint32_t, audio_mtu, Rtp::kAudioMtuSize); + GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize); + auto sample_rate = 90000u; + auto mtu = video_mtu; + if (track->getTrackType() == TrackType::TrackAudio) { + mtu = audio_mtu; + sample_rate = std::static_pointer_cast(track)->getAudioSampleRate(); + } + auto ret = Factory::getRtpEncoderByCodecId(track->getCodecId(), _payload_type); + ret->setRtpInfo(_ssrc, mtu, sample_rate, _payload_type); + return ret; +} + +} // namespace mediakit + +#endif // defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/RawEncoder.h b/MediaServer/Rtp/RawEncoder.h new file mode 100644 index 0000000..c4cdb9e --- /dev/null +++ b/MediaServer/Rtp/RawEncoder.h @@ -0,0 +1,68 @@ +/* + * 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_RAWENCODER_H +#define ZLMEDIAKIT_RAWENCODER_H + +#if defined(ENABLE_RTPPROXY) + +#include "Common/MediaSink.h" +#include "Rtsp/RtpCodec.h" + +namespace mediakit { + +class RawEncoderImp : public MediaSinkInterface { +public: + RawEncoderImp(uint32_t ssrc, uint8_t payload_type = 96, bool send_audio = true); + ~RawEncoderImp() override; + + /** + * 添加音视频轨道 + * Add audio and video tracks + + * [AUTO-TRANSLATED:7b0c1d64] + */ + bool addTrack(const Track::Ptr &track) override; + + /** + * 重置音视频轨道 + * Reset audio and video tracks + + * [AUTO-TRANSLATED:6eb1b742] + */ + void resetTracks() override; + + /** + * 输入帧数据 + * Input frame data + + * [AUTO-TRANSLATED:d13bc7f2] + */ + bool inputFrame(const Frame::Ptr &frame) override; + +protected: + // rtp打包后回调 [AUTO-TRANSLATED:61f5159b] + // Callback after RTP packaging + virtual void onRTP(toolkit::Buffer::Ptr rtp, bool is_key = false) = 0; + +private: + std::shared_ptr createRtpEncoder(const Track::Ptr &track); + +private: + bool _send_audio; + uint8_t _payload_type; + uint32_t _ssrc; + RtpCodec::Ptr _rtp_encoder; +}; + +} // namespace mediakit + +#endif // ENABLE_RTPPROXY +#endif // ZLMEDIAKIT_RAWENCODER_H diff --git a/MediaServer/Rtp/RtpCache.cpp b/MediaServer/Rtp/RtpCache.cpp new file mode 100644 index 0000000..75061e0 --- /dev/null +++ b/MediaServer/Rtp/RtpCache.cpp @@ -0,0 +1,55 @@ +/* + * 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 "RtpCache.h" + +#if defined(ENABLE_RTPPROXY) + +using namespace toolkit; + +namespace mediakit{ + +RtpCache::RtpCache(onFlushed cb) { + _cb = std::move(cb); +} + +void RtpCache::onFlush(std::shared_ptr> rtp_list, bool) { + _cb(std::move(rtp_list)); +} + +void RtpCache::input(uint64_t stamp, Buffer::Ptr buffer, bool is_key) { + inputPacket(stamp, true, std::move(buffer), is_key); +} + +void RtpCachePS::flush() { + PSEncoderImp::flush(); + RtpCache::flush(); +} + +void RtpCachePS::onRTP(Buffer::Ptr buffer, bool is_key) { + auto rtp = std::static_pointer_cast(buffer); + auto stamp = rtp->getStampMS(); + input(stamp, std::move(buffer), is_key); +} + +void RtpCacheRaw::flush() { + RawEncoderImp::flush(); + RtpCache::flush(); +} + +void RtpCacheRaw::onRTP(Buffer::Ptr buffer, bool is_key) { + auto rtp = std::static_pointer_cast(buffer); + auto stamp = rtp->getStampMS(); + input(stamp, std::move(buffer), is_key); +} + +}//namespace mediakit + +#endif//#if defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/MediaServer/Rtp/RtpCache.h b/MediaServer/Rtp/RtpCache.h new file mode 100644 index 0000000..8432eef --- /dev/null +++ b/MediaServer/Rtp/RtpCache.h @@ -0,0 +1,69 @@ +/* + * 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_RTPCACHE_H +#define ZLMEDIAKIT_RTPCACHE_H + +#if defined(ENABLE_RTPPROXY) + +#include "PSEncoder.h" +#include "RawEncoder.h" +#include "Common/PacketCache.h" + +namespace mediakit{ + +class RtpCache : protected PacketCache { +public: + using onFlushed = std::function >)>; + RtpCache(onFlushed cb); + +protected: + /** + * 输入rtp(目的是为了合并写) + * @param buffer rtp数据 + * Input rtp (for merging) + * @param buffer rtp data + + + * [AUTO-TRANSLATED:de9469b5] + */ + void input(uint64_t stamp, toolkit::Buffer::Ptr buffer,bool is_key = false); + +protected: + void onFlush(std::shared_ptr > rtp_list, bool) override; + +private: + onFlushed _cb; +}; + +class RtpCachePS : public RtpCache, public PSEncoderImp { +public: + RtpCachePS(onFlushed cb, uint32_t ssrc, uint8_t payload_type = 96, bool ps_or_ts = true) : + RtpCache(std::move(cb)), PSEncoderImp(ssrc, ps_or_ts ? payload_type : static_cast(Rtsp::PT_MP2T), ps_or_ts) {}; + + void flush() override; + +protected: + void onRTP(toolkit::Buffer::Ptr rtp, bool is_key = false) override; +}; + +class RtpCacheRaw : public RtpCache, public RawEncoderImp { +public: + RtpCacheRaw(onFlushed cb, uint32_t ssrc, uint8_t payload_type = 96, bool send_audio = true) : RtpCache(std::move(cb)), RawEncoderImp(ssrc, payload_type, send_audio) {}; + void flush() override; + +protected: + void onRTP(toolkit::Buffer::Ptr rtp, bool is_key = false) override; +}; + +} //namespace mediakit + +#endif//ENABLE_RTPPROXY +#endif //ZLMEDIAKIT_RTPCACHE_H diff --git a/MediaServer/Rtp/RtpProcess.cpp b/MediaServer/Rtp/RtpProcess.cpp new file mode 100644 index 0000000..62a82e9 --- /dev/null +++ b/MediaServer/Rtp/RtpProcess.cpp @@ -0,0 +1,350 @@ +/* + * 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_RTPPROXY) +#include "GB28181Process.h" +#include "RtpProcess.h" +#include "Util/File.h" +#include "Common/config.h" + +using namespace std; +using namespace toolkit; + +// 在创建_muxer对象前(也就是推流鉴权成功前),需要先缓存frame,这样可以防止丢包,提高体验 [AUTO-TRANSLATED:fb12a6c2] +// Before creating the _muxer object (before the streaming authentication is successful), you need to cache the frame first, which can prevent packet loss and improve the experience. +// 但是同时需要控制缓冲长度,防止内存溢出。最多缓存10秒数据,应该足矣等待鉴权hook返回 [AUTO-TRANSLATED:23ff0a4a] +// But at the same time, you need to control the buffer length to prevent memory overflow. Caching 10 seconds of data should be enough to wait for the authentication hook to return. +static constexpr size_t kMaxCachedFrameMS = 10 * 1000; + +namespace mediakit { + +RtpProcess::Ptr RtpProcess::createProcess(const MediaTuple &tuple) { + RtpProcess::Ptr ret(new RtpProcess(tuple)); + ret->createTimer(); + return ret; +} + +RtpProcess::RtpProcess(const MediaTuple &tuple) { + _media_info.schema = "rtp"; + static_cast(_media_info) = tuple; + + GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir); + { + FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info.stream + ".rtp", dump_dir), "wb") : nullptr; + if (fp) { + _save_file_rtp.reset(fp, [](FILE *fp) { + fclose(fp); + }); + } + } + + { + FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info.stream + ".video", dump_dir), "wb") : nullptr; + if (fp) { + _save_file_video.reset(fp, [](FILE *fp) { + fclose(fp); + }); + } + } +} + +void RtpProcess::flush() { + if (_process) { + _process->flush(); + } +} + +RtpProcess::~RtpProcess() { + uint64_t duration = (_last_frame_time.createdTime() - _last_frame_time.elapsedTime()) / 1000; + WarnP(this) << "RTP推流器(" + << _media_info.shortUrl() + << ")断开,耗时(s):" << duration; + + // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] + // Traffic statistics event broadcast + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + if (_total_bytes >= iFlowThreshold * 1024) { + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } + } +} + +void RtpProcess::onManager() { + if (!alive()) { + onDetach(SockException(Err_timeout, "RtpProcess timeout")); + } +} + +void RtpProcess::createTimer() { + // 创建超时管理定时器 [AUTO-TRANSLATED:865cf865] + // Create a timeout management timer + weak_ptr weakSelf = shared_from_this(); + _timer = std::make_shared(3.0f, [weakSelf] { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->onManager(); + return true; + }, EventPollerPool::Instance().getPoller()); +} + +bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data, size_t len, const struct sockaddr *addr, uint64_t *dts_out) { + if (!isRtp(data, len)) { + WarnP(this) << "Not rtp packet"; + return false; + } + if (!_auth_err.empty()) { + throw toolkit::SockException(toolkit::Err_other, _auth_err); + } + if (_sock != sock) { + // 第一次运行本函数 [AUTO-TRANSLATED:a1d7ac17] + // First time running this function + bool first = !_sock; + _sock = sock; + _addr.reset(new sockaddr_storage(*((sockaddr_storage *)addr))); + if (first) { + emitOnPublish(); + _cache_ticker.resetTime(); + } + } + + _total_bytes += len; + if (_save_file_rtp) { + uint16_t size = (uint16_t)len; + size = htons(size); + fwrite((uint8_t *) &size, 2, 1, _save_file_rtp.get()); + fwrite((uint8_t *) data, len, 1, _save_file_rtp.get()); + } + if (!_process) { + _process = std::make_shared(_media_info, this); + } + + auto header = (RtpHeader *) data; + onRtp(ntohs(header->seq), ntohl(header->stamp), 0/*不发送sr,所以可以设置为0*/ , 90000/*ps/ts流时间戳按照90K采样率*/, len); + + GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir); + if (_muxer && !_muxer->isEnabled() && !dts_out && dump_dir.empty()) { + // 无人访问、且不取时间戳、不导出调试文件时,我们可以直接丢弃数据 [AUTO-TRANSLATED:2fc75705] + // When there is no access, and no timestamp is taken, and no debug file is exported, we can directly discard the data. + _last_frame_time.resetTime(); + return false; + } + + bool ret = _process ? _process->inputRtp(is_udp, data, len) : false; + if (dts_out) { + *dts_out = _dts; + } + return ret; +} + +bool RtpProcess::inputFrame(const Frame::Ptr &frame) { + _dts = frame->dts(); + if (_save_file_video && frame->getTrackType() == TrackVideo) { + fwrite((uint8_t *) frame->data(), frame->size(), 1, _save_file_video.get()); + } + if (_muxer) { + _last_frame_time.resetTime(); + return _muxer->inputFrame(frame); + } + if (_cache_ticker.elapsedTime() > kMaxCachedFrameMS) { + WarnL << "Cached frame of stream(" << _media_info.stream << ") is too much, your on_publish hook responded too late!"; + return false; + } + auto frame_cached = Frame::getCacheAbleFrame(frame); + lock_guard lck(_func_mtx); + _cached_func.emplace_back([this, frame_cached]() { + _last_frame_time.resetTime(); + _muxer->inputFrame(frame_cached); + }); + return true; +} + +bool RtpProcess::addTrack(const Track::Ptr &track) { + if (_muxer) { + return _muxer->addTrack(track); + } + + lock_guard lck(_func_mtx); + _cached_func.emplace_back([this, track]() { + _muxer->addTrack(track); + }); + return true; +} + +void RtpProcess::addTrackCompleted() { + if (_muxer) { + _muxer->addTrackCompleted(); + } else { + lock_guard lck(_func_mtx); + _cached_func.emplace_back([this]() { + _muxer->addTrackCompleted(); + }); + } +} + +void RtpProcess::doCachedFunc() { + lock_guard lck(_func_mtx); + for (auto &func : _cached_func) { + func(); + } + _cached_func.clear(); +} + +bool RtpProcess::alive() { + if (_stop_rtp_check.load()) { + if(_last_check_alive.elapsedTime() > 5 * 60 * 1000){ + // 最多暂停5分钟的rtp超时检测,因为NAT映射有效期一般不会太长 [AUTO-TRANSLATED:2df59aad] + // Pause the RTP timeout detection for a maximum of 5 minutes, because the NAT mapping validity period is generally not very long. + _stop_rtp_check = false; + } else { + return true; + } + } + + _last_check_alive.resetTime(); + GET_CONFIG(uint64_t, timeoutSec, RtpProxy::kTimeoutSec) + if (_last_frame_time.elapsedTime() / 1000 < timeoutSec) { + return true; + } + return false; +} + +void RtpProcess::setStopCheckRtp(bool is_check){ + _stop_rtp_check = is_check; + if (!is_check) { + _last_frame_time.resetTime(); + } +} + +void RtpProcess::setOnlyTrack(OnlyTrack only_track) { + _only_track = only_track; +} + +void RtpProcess::onDetach(const SockException &ex) { + if (_on_detach) { + WarnL << ex << ", stream_id: " << getIdentifier(); + _on_detach(ex); + } +} + +void RtpProcess::setOnDetach(onDetachCB cb) { + _on_detach = std::move(cb); +} + +string RtpProcess::get_peer_ip() { + try { + return _addr ? SockUtil::inet_ntoa((sockaddr *)_addr.get()) : "::"; + } catch (std::exception &ex) { + return "::"; + } +} + +uint16_t RtpProcess::get_peer_port() { + try { + return _addr ? SockUtil::inet_port((sockaddr *)_addr.get()) : 0; + } catch (std::exception &ex) { + return 0; + } +} + +string RtpProcess::get_local_ip() { + return _sock ? _sock->get_local_ip() : "::"; +} + +uint16_t RtpProcess::get_local_port() { + return _sock ? _sock->get_local_port() : 0; +} + +string RtpProcess::getIdentifier() const { + return _media_info.stream; +} + +void RtpProcess::emitOnPublish() { + weak_ptr weak_self = shared_from_this(); + Broadcast::PublishAuthInvoker invoker = [weak_self](const string &err, const ProtocolOption &option) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + auto poller = strong_self->getOwnerPoller(MediaSource::NullMediaSource()); + poller->async([weak_self, err, option]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (err.empty()) { + strong_self->_muxer = std::make_shared(strong_self->_media_info, 0.0f, option); + switch (strong_self->_only_track) { + case kOnlyAudio: strong_self->_muxer->setOnlyAudio(); break; + case kOnlyVideo: strong_self->_muxer->enableAudio(false); break; + default: break; + } + strong_self->_muxer->setMediaListener(strong_self); + strong_self->doCachedFunc(); + InfoP(strong_self) << "允许RTP推流"; + } else { + strong_self->_auth_err = err; + WarnP(strong_self) << "禁止RTP推流:" << err; + } + }); + }; + + // 触发推流鉴权事件 [AUTO-TRANSLATED:cd889b29] + // Trigger the streaming authentication event + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, *this); + if (!flag) { + // 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae] + // No one is listening to this event, and authentication is not performed by default. + invoker("", ProtocolOption()); + } +} + +MediaOriginType RtpProcess::getOriginType(MediaSource &sender) const{ + return MediaOriginType::rtp_push; +} + +string RtpProcess::getOriginUrl(MediaSource &sender) const { + return _media_info.getUrl(); +} + +std::shared_ptr RtpProcess::getOriginSock(MediaSource &sender) const { + return const_cast(this)->shared_from_this(); +} + +RtpProcess::Ptr RtpProcess::getRtpProcess(mediakit::MediaSource &sender) const { + return const_cast(this)->shared_from_this(); +} + +bool RtpProcess::close(mediakit::MediaSource &sender) { + onDetach(SockException(Err_shutdown, "close media")); + return true; +} + +toolkit::EventPoller::Ptr RtpProcess::getOwnerPoller(MediaSource &sender) { + if (_sock) { + return _sock->getPoller(); + } + throw std::runtime_error("RtpProcess::getOwnerPoller failed:" + _media_info.stream); +} + +float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { + auto expected = getExpectedPacketsInterval(); + if (!expected) { + return -1; + } + return getLostInterval() * 100 / expected; +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/MediaServer/Rtp/RtpProcess.h b/MediaServer/Rtp/RtpProcess.h new file mode 100644 index 0000000..ef82f82 --- /dev/null +++ b/MediaServer/Rtp/RtpProcess.h @@ -0,0 +1,153 @@ +/* + * 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_RTPPROCESS_H +#define ZLMEDIAKIT_RTPPROCESS_H + +#if defined(ENABLE_RTPPROXY) +#include "ProcessInterface.h" +#include "Rtcp/RtcpContext.h" +#include "Common/MultiMediaSourceMuxer.h" + +namespace mediakit { + +static constexpr char kRtpAppName[] = "rtp"; + +class RtpProcess final : public RtcpContextForRecv, public toolkit::SockInfo, public MediaSinkInterface, public MediaSourceEvent, public std::enable_shared_from_this{ +public: + using Ptr = std::shared_ptr; + using onDetachCB = std::function; + + static Ptr createProcess(const MediaTuple &tuple); + ~RtpProcess(); + enum OnlyTrack { kAll = 0, kOnlyAudio = 1, kOnlyVideo = 2 }; + + /** + * 输入rtp + * @param is_udp 是否为udp模式 + * @param sock 本地监听的socket + * @param data rtp数据指针 + * @param len rtp数据长度 + * @param addr 数据源地址 + * @param dts_out 解析出最新的dts + * @return 是否解析成功 + * Input rtp + * @param is_udp Whether it is udp mode + * @param sock Local listening socket + * @param data Rtp data pointer + * @param len Rtp data length + * @param addr Data source address + * @param dts_out Parse out the latest dts + * @return Whether the parsing is successful + + * [AUTO-TRANSLATED:a10c5edf] + */ + bool inputRtp(bool is_udp, const toolkit::Socket::Ptr &sock, const char *data, size_t len, const struct sockaddr *addr , uint64_t *dts_out = nullptr); + + + /** + * 超时时被RtpSelector移除时触发 + * Triggered when removed by RtpSelector when timeout + + * [AUTO-TRANSLATED:dc4c6609] + */ + void onDetach(const toolkit::SockException &ex); + + /** + * 设置onDetach事件回调 + * Set onDetach event callback + + * [AUTO-TRANSLATED:b30f67c3] + */ + void setOnDetach(onDetachCB cb); + + /** + * 设置onDetach事件回调,false检查RTP超时,true停止 + * Set onDetach event callback, false checks RTP timeout, true stops + + * [AUTO-TRANSLATED:2780397f] + */ + void setStopCheckRtp(bool is_check=false); + + /** + * 设置为单track,单音频/单视频时可以加快媒体注册速度 + * 请在inputRtp前调用此方法,否则可能会是空操作 + * Set to single track, single audio/single video can speed up media registration + * Please call this method before inputRtp, otherwise it may be a null operation + + * [AUTO-TRANSLATED:55095289] + */ + void setOnlyTrack(OnlyTrack only_track); + + /** + * flush输出缓存 + * Flush output cache + + + * [AUTO-TRANSLATED:40618a29] + */ + void flush() override; + + /// SockInfo override + std::string get_local_ip() override; + uint16_t get_local_port() override; + std::string get_peer_ip() override; + uint16_t get_peer_port() override; + std::string getIdentifier() const override; + +protected: + bool inputFrame(const Frame::Ptr &frame) override; + bool addTrack(const Track::Ptr & track) override; + void addTrackCompleted() override; + void resetTracks() override {}; + + //// MediaSourceEvent override //// + MediaOriginType getOriginType(MediaSource &sender) const override; + std::string getOriginUrl(MediaSource &sender) const override; + std::shared_ptr getOriginSock(MediaSource &sender) const override; + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + float getLossRate(MediaSource &sender, TrackType type) override; + Ptr getRtpProcess(mediakit::MediaSource &sender) const override; + bool close(mediakit::MediaSource &sender) override; + +private: + RtpProcess(const MediaTuple &tuple); + + void emitOnPublish(); + void doCachedFunc(); + bool alive(); + void onManager(); + void createTimer(); + +private: + OnlyTrack _only_track = kAll; + std::string _auth_err; + uint64_t _dts = 0; + uint64_t _total_bytes = 0; + std::unique_ptr _addr; + toolkit::Socket::Ptr _sock; + MediaInfo _media_info; + toolkit::Ticker _last_frame_time; + onDetachCB _on_detach; + std::shared_ptr _save_file_rtp; + std::shared_ptr _save_file_video; + ProcessInterface::Ptr _process; + MultiMediaSourceMuxer::Ptr _muxer; + std::atomic_bool _stop_rtp_check{false}; + toolkit::Timer::Ptr _timer; + toolkit::Ticker _last_check_alive; + std::recursive_mutex _func_mtx; + toolkit::Ticker _cache_ticker; + std::deque > _cached_func; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPPROCESS_H diff --git a/MediaServer/Rtp/RtpSender.cpp b/MediaServer/Rtp/RtpSender.cpp new file mode 100644 index 0000000..21b82ab --- /dev/null +++ b/MediaServer/Rtp/RtpSender.cpp @@ -0,0 +1,416 @@ +/* + * 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_RTPPROXY) +#include "RtpSender.h" +#include "RtpSession.h" +#include "Rtsp/RtspSession.h" +#include "Thread/WorkThreadPool.h" +#include "Util/uv_errno.h" +#include "RtpCache.h" +#include "Rtcp/RtcpContext.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +RtpSender::RtpSender(EventPoller::Ptr poller) { + _poller = poller ? std::move(poller) : EventPollerPool::Instance().getPoller(); + _socket_rtp = Socket::createSocket(_poller, false); +} + +RtpSender::~RtpSender() { + try { + flush(); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } +} + +void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const function &cb){ + _args = args; + if (!_interface) { + // 重连时不重新创建对象 [AUTO-TRANSLATED:b788cd5d] + // Do not recreate the object when reconnecting + auto lam = [this](std::shared_ptr> list) { onFlushRtpList(std::move(list)); }; + switch (args.data_type) { + case MediaSourceEvent::SendRtpArgs::kRtpPS: _interface = std::make_shared(lam, atoi(args.ssrc.data()), args.pt, true); break; + case MediaSourceEvent::SendRtpArgs::kRtpTS: _interface = std::make_shared(lam, atoi(args.ssrc.data()), args.pt, false); break; + case MediaSourceEvent::SendRtpArgs::kRtpES: _interface = std::make_shared(lam, atoi(args.ssrc.data()), args.pt, args.only_audio); break; + default: CHECK(0, "invalid rtp type: " + to_string(args.data_type)); break; + } + } + + auto delay_ms = _args.close_delay_ms ? _args.close_delay_ms : 5000; + weak_ptr weak_self = shared_from_this(); + if (args.con_type == MediaSourceEvent::SendRtpArgs::kTcpPassive) { + auto tcp_listener = Socket::createSocket(_poller, false); + if (args.src_port) { + // 指定端口 [AUTO-TRANSLATED:ed4ca3dd] + // Specify the port + if (!tcp_listener->listen(args.src_port)) { + throw std::invalid_argument(StrPrinter << "open tcp passive server failed on port: " << args.src_port << ", err: " << get_uv_errmsg(true)); + } + } else { + auto pr = std::make_pair(tcp_listener, Socket::createSocket(_poller, false)); + // 从端口池获取随机端口 [AUTO-TRANSLATED:139ceb4f] + // Get a random port from the port pool + makeSockPair(pr, "::", true, false); + } + // 定时器持有tcp_listener,保证超时时间内保持监听 [AUTO-TRANSLATED:39df3f48] + // The timer holds the tcp_listener to ensure listening within the timeout period + auto delay_task = _poller->doDelayTask(delay_ms, [weak_self, tcp_listener]() mutable { + // 防止循环引用 [AUTO-TRANSLATED:e2e9f9e7] + // Prevent circular references + tcp_listener = nullptr; + if (auto strong_self = weak_self.lock()) { + strong_self->onClose(SockException(Err_timeout, "wait tcp connection timeout")); + } + return 0; + }); + tcp_listener->setOnAccept([weak_self, delay_task](Socket::Ptr &sock, std::shared_ptr &complete) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + delay_task->cancel(); + strong_self->_socket_rtp = sock; + strong_self->onConnect(); + InfoL << "accept tcp connection from: " << sock->get_peer_ip() << ":" << sock->get_peer_port(); + }); + InfoL << "start tcp passive server on: " << tcp_listener->get_local_port(); + cb(tcp_listener->get_local_port(), SockException()); + + } else if (args.con_type == MediaSourceEvent::SendRtpArgs::kUdpPassive) { + if (args.src_port) { + // 指定端口 [AUTO-TRANSLATED:ed4ca3dd] + // Specify the port + if (!_socket_rtp->bindUdpSock(args.src_port, "::", true)) { + throw std::invalid_argument(StrPrinter << "open udp passive server failed on port: " << args.src_port << ", err: " << get_uv_errmsg(true)); + } + } else { + auto pr = std::make_pair(_socket_rtp, Socket::createSocket(_poller, false)); + // 从端口池获取随机端口 [AUTO-TRANSLATED:139ceb4f] + // Get a random port from the port pool + makeSockPair(pr, "::", true, true); + } + auto delay_task = _poller->doDelayTask(delay_ms, [weak_self]() mutable { + if (auto strong_self = weak_self.lock()) { + // 关闭端口 [AUTO-TRANSLATED:3b3dff64] + // Close the port + strong_self->_socket_rtp->closeSock(); + strong_self->onClose(SockException(Err_timeout, "wait udp connection timeout")); + } + return 0; + }); + _socket_rtp->setOnRead([weak_self, delay_task](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + delay_task->cancel(); + strong_self->_socket_rtp->bindPeerAddr(addr, addr_len, true); + // 异步执行onConnect,防止在OnRead回调中调用setOnRead [AUTO-TRANSLATED:83881d7f] + // Execute onConnect asynchronously to prevent calling setOnRead in the OnRead callback + strong_self->_poller->async([strong_self]() { strong_self->onConnect(); }, false); + InfoL << "accept udp connection from: " << strong_self->_socket_rtp->get_peer_ip() << ":" << strong_self->_socket_rtp->get_peer_port(); + }); + InfoL << "start udp passive server on: " << _socket_rtp->get_local_port(); + cb(_socket_rtp->get_local_port(), SockException()); + + } else if (args.con_type == MediaSourceEvent::SendRtpArgs::kUdpActive) { + auto poller = _poller; + WorkThreadPool::Instance().getPoller()->async([cb, args, weak_self, poller]() { + struct sockaddr_storage addr; + // 切换线程目的是为了dns解析放在后台线程执行 [AUTO-TRANSLATED:1a09ba8a] + // The purpose of switching threads is to perform DNS resolution in the background thread + if (!SockUtil::getDomainIP(args.dst_url.data(), args.dst_port, addr, AF_INET, SOCK_DGRAM, IPPROTO_UDP)) { + poller->async([args, cb]() { + // 切回自己的线程 [AUTO-TRANSLATED:b95746d6] + // Switch back to your own thread + cb(0, SockException(Err_dns, StrPrinter << "dns resolution failed: " << args.dst_url)); + }); + return; + } + + // dns解析成功 [AUTO-TRANSLATED:e1b35821] + // DNS resolution successful + poller->async([args, addr, weak_self, cb]() { + // 切回自己的线程 [AUTO-TRANSLATED:b95746d6] + // Switch back to your own thread + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + string ifr_ip = addr.ss_family == AF_INET ? "0.0.0.0" : "::"; + try { + if (args.src_port) { + // 指定端口 [AUTO-TRANSLATED:ed4ca3dd] + // Specify the port + if (!strong_self->_socket_rtp->bindUdpSock(args.src_port, ifr_ip, true)) { + throw std::invalid_argument(StrPrinter << "open udp active client failed on port: " << args.src_port << ", err: " << get_uv_errmsg(true)); + } + } else { + auto pr = std::make_pair(strong_self->_socket_rtp, Socket::createSocket(strong_self->_poller, false)); + // 从端口池获取随机端口 [AUTO-TRANSLATED:139ceb4f] + // Get a random port from the port pool + makeSockPair(pr, ifr_ip, true, true); + } + } catch (std::exception &ex) { + cb(0, SockException(Err_other, ex.what())); + return; + } + strong_self->_socket_rtp->bindPeerAddr((struct sockaddr *)&addr, 0, true); + strong_self->onConnect(); + cb(strong_self->_socket_rtp->get_local_port(), SockException()); + }); + }); + InfoL << "start udp active send rtp to: " << args.dst_url << ":" << args.dst_port; + + } else if (args.con_type == MediaSourceEvent::SendRtpArgs::kTcpActive) { + _socket_rtp->connect(args.dst_url, args.dst_port,[cb, weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (strong_self) { + if (!err) { + // tcp连接成功 [AUTO-TRANSLATED:1a33669a] + // TCP connection successful + strong_self->onConnect(); + } + cb(strong_self->_socket_rtp->get_local_port(), err); + } else { + cb(0, err); + } + }, delay_ms / 1000.0, "::", args.src_port); + InfoL << "start tcp active send rtp to: " << args.dst_url << ":" << args.dst_port; + } else { + CHECK(0, "invalid con type"); + } +} + +void RtpSender::createRtcpSocket() { + if (_socket_rtcp) { + return; + } + _socket_rtcp = Socket::createSocket(_socket_rtp->getPoller(), false); + // rtcp端口使用户rtp端口+1 [AUTO-TRANSLATED:8a0a6b2c] + // The RTCP port is the RTP port + 1 + if(!_socket_rtcp->bindUdpSock(_socket_rtp->get_local_port() + 1, _socket_rtp->get_local_ip(), true)){ + WarnL << "bind rtcp udp socket failed: " << get_uv_errmsg(true); + _socket_rtcp = nullptr; + return; + } + + // 绑定目标rtcp端口(目标rtp端口 + 1) [AUTO-TRANSLATED:c402de5b] + // Bind the target RTCP port (target RTP port + 1) + auto addr = SockUtil::make_sockaddr(_socket_rtp->get_peer_ip().data(), _socket_rtp->get_peer_port() + 1); + _socket_rtcp->bindPeerAddr((struct sockaddr *)&addr, 0, true); + + _rtcp_context = std::make_shared(); + weak_ptr weak_self = shared_from_this(); + bool bind_addr = false; + _socket_rtcp->setOnRead([weak_self, bind_addr](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) mutable { + // 接收receive report rtcp [AUTO-TRANSLATED:38d3c1ba] + // Receive receive report RTCP + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (!bind_addr) { + // 收到对方rtcp打洞包后,再回复rtcp [AUTO-TRANSLATED:393868ca] + // Reply RTCP after receiving the other party's RTCP hole punching packet + bind_addr = true; + strong_self->_socket_rtcp->bindPeerAddr(addr, addr_len, true); + } + auto rtcp_arr = RtcpHeader::loadFromBytes(buf->data(), buf->size()); + for (auto &rtcp : rtcp_arr) { + strong_self->onRecvRtcp(rtcp); + } + }); + InfoL << "open rtcp port success, start check rr rtcp timeout"; +} + +void RtpSender::onRecvRtcp(RtcpHeader *rtcp) { + _rtcp_context->onRtcp(rtcp); + _rtcp_recv_ticker.resetTime(); +} + +// 连接建立成功事件 [AUTO-TRANSLATED:ac279c86] +// Connection established successfully event +void RtpSender::onConnect() { + _is_connect = true; + // 加大发送缓存,防止udp丢包之类的问题 [AUTO-TRANSLATED:6e1cb40a] + // Increase the send buffer to prevent problems such as UDP packet loss + SockUtil::setSendBuf(_socket_rtp->rawFD(), 4 * 1024 * 1024); + if (_args.con_type == MediaSourceEvent::SendRtpArgs::kTcpActive || _args.con_type == MediaSourceEvent::SendRtpArgs::kTcpPassive) { + // 关闭tcp no_delay并开启MSG_MORE, 提高发送性能 [AUTO-TRANSLATED:c0f4e378] + // Close TCP no_delay and enable MSG_MORE to improve sending performance + SockUtil::setNoDelay(_socket_rtp->rawFD(), false); + _socket_rtp->setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); + } else if (_args.udp_rtcp_timeout) { + createRtcpSocket(); + } + // 连接建立成功事件 [AUTO-TRANSLATED:ac279c86] + // Connection established successfully event + weak_ptr weak_self = shared_from_this(); + if (!_args.recv_stream_id.empty()) { + mINI ini; + ini[RtpSession::kStreamID] = _args.recv_stream_id; + // 强制同步接收流和发送流的app和vhost [AUTO-TRANSLATED:134c9663] + // Force synchronization of the app and vhost of the receive stream and send stream + ini[RtpSession::kApp] = _args.recv_stream_app; + ini[RtpSession::kVhost] = _args.recv_stream_vhost; + _rtp_session = std::make_shared(_socket_rtp); + _rtp_session->setParams(ini); + + _socket_rtp->setOnRead([weak_self](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + try { + strong_self->_rtp_session->onRecv(buf); + } catch (std::exception &ex) { + SockException err(toolkit::Err_shutdown, ex.what()); + strong_self->_rtp_session->shutdown(err); + } + }); + } else { + _socket_rtp->setOnRead(nullptr); + } + _socket_rtp->setOnErr([weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->onErr(err); + } + }); + InfoL << "startSend rtp success: " << _socket_rtp->get_peer_ip() << ":" << _socket_rtp->get_peer_port() << ", data_type: " << _args.data_type << ", con_type: " << _args.con_type; +} + +bool RtpSender::addTrack(const Track::Ptr &track) { + if (_args.only_audio && track->getTrackType() == TrackVideo) { + // 如果只发送音频则忽略视频 [AUTO-TRANSLATED:6843e322] + // Ignore video if only audio is sent + return false; + } + return _interface->addTrack(track); +} + +void RtpSender::addTrackCompleted() { + _interface->addTrackCompleted(); +} + +void RtpSender::resetTracks() { + _interface->resetTracks(); +} + +void RtpSender::flush() { + if (_interface) { + _interface->flush(); + } +} + +bool RtpSender::inputFrame(const Frame::Ptr &frame) { + if (_args.only_audio && frame->getTrackType() == TrackVideo) { + // 如果只发送音频则忽略视频 [AUTO-TRANSLATED:6843e322] + // Ignore video if only audio is sent + return false; + } + // 连接成功后才做实质操作(节省cpu资源) [AUTO-TRANSLATED:666253b3] + // Perform the actual operation after the connection is successful (save CPU resources) + return _is_connect ? _interface->inputFrame(frame) : false; +} + +void RtpSender::onSendRtpUdp(const toolkit::Buffer::Ptr &buf, bool check) { + if (!_socket_rtcp) { + return; + } + auto rtp = static_pointer_cast(buf); + _rtcp_context->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, 90000 /*not used*/, rtp->size()); + + if (!check) { + // 减少判断次数 [AUTO-TRANSLATED:0cfaddd8] + // Reduce the number of judgments + return; + } + // 每5秒发送一次rtcp [AUTO-TRANSLATED:3c9bcb7b] + // Send RTCP every 5 seconds + if (_rtcp_send_ticker.elapsedTime() > _args.rtcp_send_interval_ms) { + _rtcp_send_ticker.resetTime(); + // rtcp ssrc为rtp ssrc + 1 [AUTO-TRANSLATED:318fada3] + // rtcp ssrc is rtp ssrc + 1 + auto sr = _rtcp_context->createRtcpSR(atoi(_args.ssrc.data()) + 1); + // send sender report rtcp + _socket_rtcp->send(sr); + } + + if (_rtcp_recv_ticker.elapsedTime() > _args.rtcp_timeout_ms) { + // 接收rr rtcp超时 [AUTO-TRANSLATED:a6ccd262] + // Receive rr rtcp timeout + WarnL << "recv rr rtcp timeout"; + _rtcp_recv_ticker.resetTime(); + onClose(SockException(Err_timeout, "recv rr rtcp timeout")); + } +} + +void RtpSender::onClose(const SockException &ex) { + auto cb = _on_close; + if (cb) { + // 在下次循环时触发onClose,原因是防止遍历map时删除元素 [AUTO-TRANSLATED:2841df7f] + // Trigger onClose in the next loop to prevent deleting elements while iterating through the map + _poller->async([cb, ex]() { cb(ex); }, false); + } +} + +// 此函数在其他线程执行 [AUTO-TRANSLATED:3569a681] +// This function is executed in another thread +void RtpSender::onFlushRtpList(shared_ptr> rtp_list) { + if (!_is_connect) { + // 连接成功后才能发送数据 [AUTO-TRANSLATED:14d00ad5] + // Data can only be sent after the connection is successful + return; + } + + size_t i = 0; + auto size = rtp_list->size(); + rtp_list->for_each([&](Buffer::Ptr &packet) { + switch (_args.con_type) { + case MediaSourceEvent::SendRtpArgs::kUdpActive: + case MediaSourceEvent::SendRtpArgs::kUdpPassive: { + onSendRtpUdp(packet, i == 0); + // udp模式,rtp over tcp前4个字节可以忽略 [AUTO-TRANSLATED:5d648f4b] + // UDP mode, the first 4 bytes of rtp over tcp can be ignored + _socket_rtp->send(std::make_shared(std::move(packet), RtpPacket::kRtpTcpHeaderSize), nullptr, 0, ++i == size); + break; + } + case MediaSourceEvent::SendRtpArgs::kTcpActive: + case MediaSourceEvent::SendRtpArgs::kTcpPassive: { + // tcp模式, rtp over tcp前2个字节可以忽略,只保留后续rtp长度的2个字节 [AUTO-TRANSLATED:a3bc338a] + // TCP mode, the first 2 bytes of rtp over tcp can be ignored, only the subsequent 2 bytes of rtp length are retained + _socket_rtp->send(std::make_shared(std::move(packet), 2), nullptr, 0, ++i == size); + break; + } + default: CHECK(0); + } + }); +} + +void RtpSender::onErr(const SockException &ex) { + _is_connect = false; + WarnL << "send rtp connection lost: " << ex; + onClose(ex); +} + +void RtpSender::setOnClose(std::function on_close) { + _on_close = std::move(on_close); +} + +} // namespace mediakit +#endif // defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/RtpSender.h b/MediaServer/Rtp/RtpSender.h new file mode 100644 index 0000000..dbdf4a1 --- /dev/null +++ b/MediaServer/Rtp/RtpSender.h @@ -0,0 +1,128 @@ +/* + * 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_RTPSENDER_H +#define ZLMEDIAKIT_RTPSENDER_H +#if defined(ENABLE_RTPPROXY) +#include "PSEncoder.h" +#include "Extension/CommonRtp.h" +#include "Rtcp/RtcpContext.h" +#include "Common/MediaSource.h" +#include "Common/MediaSink.h" + +namespace mediakit{ + +class RtpSession; + +// rtp发送客户端,支持发送GB28181协议 [AUTO-TRANSLATED:668038b6] +// RTP sending client, supporting sending GB28181 protocol +class RtpSender final : public MediaSinkInterface, public std::enable_shared_from_this{ +public: + using Ptr = std::shared_ptr; + + RtpSender(toolkit::EventPoller::Ptr poller = nullptr); + ~RtpSender() override; + + /** + * 开始发送ps-rtp包 + * @param args 发送参数 + * @param cb 连接目标端口是否成功的回调 + * Start sending ps-rtp packets + * @param args Sending parameters + * @param cb Callback for whether the connection to the target port is successful + + * [AUTO-TRANSLATED:c31bd9b3] + */ + void startSend(const MediaSourceEvent::SendRtpArgs &args, const std::function &cb); + + /** + * 输入帧数据 + * Input frame data + + * [AUTO-TRANSLATED:d13bc7f2] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出frame缓存 + * Refresh the output frame cache + + * [AUTO-TRANSLATED:547f851c] + */ + void flush() override; + + /** + * 添加track,内部会调用Track的clone方法 + * 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系 + * @param track + * Add track, internally calls the clone method of Track + * Only clones sps pps information, not Delegate relationships + * @param track + + * [AUTO-TRANSLATED:ba6faf58] + */ + virtual bool addTrack(const Track::Ptr & track) override; + + /** + * 添加所有Track完毕 + * All Tracks added + + * [AUTO-TRANSLATED:751c45ca] + */ + virtual void addTrackCompleted() override; + + /** + * 重置track + * Reset track + + * [AUTO-TRANSLATED:95dc0b4f] + */ + virtual void resetTracks() override; + + /** + * 设置发送rtp停止回调 + * Set RTP sending stop callback + + * [AUTO-TRANSLATED:7e0a6714] + */ + void setOnClose(std::function on_close); + +private: + // 合并写输出 [AUTO-TRANSLATED:23544836] + // Merge write output + void onFlushRtpList(std::shared_ptr > rtp_list); + // udp/tcp连接成功回调 [AUTO-TRANSLATED:ca35017d] + // UDP/TCP connection success callback + void onConnect(); + // 异常断开socket事件 [AUTO-TRANSLATED:a59cd9de] + // Abnormal socket disconnect event + void onErr(const toolkit::SockException &ex); + void createRtcpSocket(); + void onRecvRtcp(RtcpHeader *rtcp); + void onSendRtpUdp(const toolkit::Buffer::Ptr &buf, bool check); + void onClose(const toolkit::SockException &ex); + +private: + bool _is_connect = false; + MediaSourceEvent::SendRtpArgs _args; + toolkit::Socket::Ptr _socket_rtp; + toolkit::Socket::Ptr _socket_rtcp; + toolkit::EventPoller::Ptr _poller; + MediaSinkInterface::Ptr _interface; + std::shared_ptr _rtcp_context; + toolkit::Ticker _rtcp_send_ticker; + toolkit::Ticker _rtcp_recv_ticker; + std::shared_ptr _rtp_session; + std::function _on_close; +}; + +}//namespace mediakit +#endif// defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSENDER_H diff --git a/MediaServer/Rtp/RtpServer.cpp b/MediaServer/Rtp/RtpServer.cpp new file mode 100644 index 0000000..ebd3db9 --- /dev/null +++ b/MediaServer/Rtp/RtpServer.cpp @@ -0,0 +1,299 @@ +/* + * 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_RTPPROXY) +#include "Util/uv_errno.h" +#include "RtpServer.h" +#include "RtpProcess.h" +#include "Rtcp/RtcpContext.h" +#include "Common/config.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +RtpServer::~RtpServer() { + if (_on_cleanup) { + _on_cleanup(); + } +} + +class RtcpHelper: public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + RtcpHelper(Socket::Ptr rtcp_sock, MediaTuple tuple) { + _rtcp_sock = std::move(rtcp_sock); + _tuple = std::move(tuple); + } + + void setRtpServerInfo(uint16_t local_port, RtpServer::TcpMode mode, bool re_use_port, uint32_t ssrc, int only_track) { + _ssrc = ssrc; + _process = RtpProcess::createProcess(_tuple); + _process->setOnlyTrack((RtpProcess::OnlyTrack)only_track); + + _timeout_cb = [=]() mutable { + NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::kBroadcastRtpServerTimeout, local_port, _tuple, (int)mode, re_use_port, ssrc); + }; + + weak_ptr weak_self = shared_from_this(); + _process->setOnDetach([weak_self](const SockException &ex) { + if (auto strong_self = weak_self.lock()) { + if (strong_self->_on_detach) { + strong_self->_on_detach(ex); + } + if (ex.getErrCode() == Err_timeout) { + strong_self->_timeout_cb(); + } + } + }); + } + + void setOnDetach(RtpProcess::onDetachCB cb) { _on_detach = std::move(cb); } + + RtpProcess::Ptr getProcess() const { return _process; } + + void onRecvRtp(const Socket::Ptr &sock, const Buffer::Ptr &buf, struct sockaddr *addr) { + _process->inputRtp(true, sock, buf->data(), buf->size(), addr); + // 统计rtp接受情况,用于发送rr包 [AUTO-TRANSLATED:bd2fbe7e] + // Count RTP reception status, used to send RR packets + auto header = (RtpHeader *)buf->data(); + sendRtcp(ntohl(header->ssrc), addr); + } + + void startRtcp() { + weak_ptr weak_self = shared_from_this(); + _rtcp_sock->setOnRead([weak_self](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + // 用于接受rtcp打洞包 [AUTO-TRANSLATED:d75d9d87] + // Used to receive RTCP hole punching packets + auto strong_self = weak_self.lock(); + if (!strong_self || !strong_self->_process) { + return; + } + if (!strong_self->_rtcp_addr) { + // 只设置一次rtcp对端端口 [AUTO-TRANSLATED:fdc4eb4e] + // Set the RTCP peer port only once + strong_self->_rtcp_addr = std::make_shared(); + memcpy(strong_self->_rtcp_addr.get(), addr, addr_len); + } + auto rtcps = RtcpHeader::loadFromBytes(buf->data(), buf->size()); + for (auto &rtcp : rtcps) { + strong_self->_process->onRtcp(rtcp); + } + // 收到sr rtcp后驱动返回rr rtcp [AUTO-TRANSLATED:d7373077] + // After receiving SR RTCP, the driver returns RR RTCP + strong_self->sendRtcp(strong_self->_ssrc, (struct sockaddr *)(strong_self->_rtcp_addr.get())); + }); + } + +private: + void sendRtcp(uint32_t rtp_ssrc, struct sockaddr *addr) { + // 每5秒发送一次rtcp [AUTO-TRANSLATED:3c9bcb7b] + // Send RTCP every 5 seconds + if (_ticker.elapsedTime() < 5000) { + return; + } + _ticker.resetTime(); + + auto rtcp_addr = (struct sockaddr *)_rtcp_addr.get(); + if (!rtcp_addr) { + // 默认的,rtcp端口为rtp端口+1 [AUTO-TRANSLATED:273d164d] + // By default, the RTCP port is the RTP port + 1 + switch (addr->sa_family) { + case AF_INET: ((sockaddr_in *)addr)->sin_port = htons(ntohs(((sockaddr_in *)addr)->sin_port) + 1); break; + case AF_INET6: ((sockaddr_in6 *)addr)->sin6_port = htons(ntohs(((sockaddr_in6 *)addr)->sin6_port) + 1); break; + } + // 未收到rtcp打洞包时,采用默认的rtcp端口 [AUTO-TRANSLATED:d99b12b8] + // When no RTCP hole punching packet is received, the default RTCP port is used + rtcp_addr = addr; + } + _rtcp_sock->send(_process->createRtcpRR(rtp_ssrc + 1, rtp_ssrc), rtcp_addr); + } + +private: + uint32_t _ssrc = 0; + std::function _timeout_cb; + Ticker _ticker; + Socket::Ptr _rtcp_sock; + RtpProcess::Ptr _process; + MediaTuple _tuple; + RtpProcess::onDetachCB _on_detach; + std::shared_ptr _rtcp_addr; +}; + +void RtpServer::start(uint16_t local_port, const char *local_ip, const MediaTuple &tuple, TcpMode tcp_mode, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) { + // 创建udp服务器 [AUTO-TRANSLATED:99619428] + // Create UDP server + auto poller = EventPollerPool::Instance().getPoller(); + Socket::Ptr rtp_socket = Socket::createSocket(poller, true); + Socket::Ptr rtcp_socket = Socket::createSocket(poller, true); + if (local_port == 0) { + // 随机端口,rtp端口采用偶数 [AUTO-TRANSLATED:3664eaf5] + // Random port, RTP port uses even numbers + auto pair = std::make_pair(rtp_socket, rtcp_socket); + makeSockPair(pair, local_ip, re_use_port); + local_port = rtp_socket->get_local_port(); + } else if (!rtp_socket->bindUdpSock(local_port, local_ip, re_use_port)) { + // 用户指定端口 [AUTO-TRANSLATED:1328393b] + // User-specified port + throw std::runtime_error(StrPrinter << "创建rtp端口 " << local_ip << ":" << local_port << " 失败:" << get_uv_errmsg(true)); + } else if (!rtcp_socket->bindUdpSock(local_port + 1, local_ip, re_use_port)) { + // rtcp端口 [AUTO-TRANSLATED:00cf932e] + // RTCP port + throw std::runtime_error(StrPrinter << "创建rtcp端口 " << local_ip << ":" << local_port + 1 << " 失败:" << get_uv_errmsg(true)); + } + + // 设置udp socket读缓存 [AUTO-TRANSLATED:3bf101d7] + // Set UDP socket read cache + GET_CONFIG(int, udpRecvSocketBuffer, RtpProxy::kUdpRecvSocketBuffer); + SockUtil::setRecvBuf(rtp_socket->rawFD(), udpRecvSocketBuffer); + + // 创建udp服务器 [AUTO-TRANSLATED:99619428] + // Create UDP server + UdpServer::Ptr udp_server; + RtcpHelper::Ptr helper; + // 增加了多路复用判断,如果多路复用为true,就走else逻辑,同时保留了原来stream_id为空走else逻辑 [AUTO-TRANSLATED:114690b1] + // Added multiplexing judgment. If multiplexing is true, then go to the else logic, while retaining the original stream_id is empty to go to the else logic + if (!tuple.stream.empty() && !multiplex) { + // 指定了流id,那么一个端口一个流(不管是否包含多个ssrc的多个流,绑定rtp源后,会筛选掉ip端口不匹配的流) [AUTO-TRANSLATED:3aeb611e] + // If a stream ID is specified, then one port is one stream (regardless of whether it contains multiple streams with multiple SSRCs, after binding the RTP source, streams that do not match the IP port will be filtered out) + helper = std::make_shared(std::move(rtcp_socket), tuple); + helper->startRtcp(); + helper->setRtpServerInfo(local_port, tcp_mode, re_use_port, ssrc, only_track); + bool bind_peer_addr = false; + auto ssrc_ptr = std::make_shared(ssrc); + _ssrc = ssrc_ptr; + rtp_socket->setOnRead([rtp_socket, helper, ssrc_ptr, bind_peer_addr](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) mutable { + RtpHeader *header = (RtpHeader *)buf->data(); + auto rtp_ssrc = ntohl(header->ssrc); + auto ssrc = *ssrc_ptr; + if (ssrc && rtp_ssrc != ssrc) { + WarnL << "ssrc mismatched, rtp dropped: " << rtp_ssrc << " != " << ssrc; + } else { + if (!bind_peer_addr) { + // 绑定对方ip+端口,防止多个设备或一个设备多次推流从而日志报ssrc不匹配问题 [AUTO-TRANSLATED:f27dd373] + // Bind the peer IP + port to prevent multiple devices or one device from pushing multiple streams, resulting in log reports of mismatched SSRCs + bind_peer_addr = true; + rtp_socket->bindPeerAddr(addr, addr_len); + } + helper->onRecvRtp(rtp_socket, buf, addr); + } + }); + } else { + // 单端口多线程接收多个流,根据ssrc区分流 [AUTO-TRANSLATED:e11c3ca8] + // Single-port multi-threaded reception of multiple streams, distinguishing streams based on SSRC + udp_server = std::make_shared(); + (*udp_server)[RtpSession::kOnlyTrack] = only_track; + (*udp_server)[RtpSession::kUdpRecvBuffer] = udpRecvSocketBuffer; + (*udp_server)[RtpSession::kVhost] = tuple.vhost; + (*udp_server)[RtpSession::kApp] = tuple.app; + udp_server->start(local_port, local_ip); + rtp_socket = nullptr; + } + + TcpServer::Ptr tcp_server; + if (tcp_mode == PASSIVE || tcp_mode == ACTIVE) { + auto processor = helper ? helper->getProcess() : nullptr; + // 如果共享同一个processor对象,那么tcp server深圳为单线程模式确保线程安全 [AUTO-TRANSLATED:68bdd877] + // If the same processor object is shared, then the TCP server Shenzhen is in single-threaded mode to ensure thread safety + tcp_server = std::make_shared(processor ? poller : nullptr); + (*tcp_server)[RtpSession::kVhost] = tuple.vhost; + (*tcp_server)[RtpSession::kApp] = tuple.app; + (*tcp_server)[RtpSession::kStreamID] = tuple.stream; + (*tcp_server)[RtpSession::kSSRC] = ssrc; + (*tcp_server)[RtpSession::kOnlyTrack] = only_track; + if (tcp_mode == PASSIVE) { + weak_ptr weak_self = shared_from_this(); + tcp_server->start(local_port, local_ip, 1024, [weak_self, processor](std::shared_ptr &session) { + session->setRtpProcess(processor); + }); + } else if (tuple.stream.empty()) { + // tcp主动模式时只能一个端口一个流,必须指定流id; 创建TcpServer对象也仅用于传参 [AUTO-TRANSLATED:61d2a642] + // In TCP active mode, only one port can have one stream, and the stream ID must be specified; the TcpServer object is created only for parameter passing + throw std::runtime_error(StrPrinter << "tcp主动模式时必需指定流id"); + } + } + + _on_cleanup = [rtp_socket]() { + if (rtp_socket) { + // 去除循环引用 [AUTO-TRANSLATED:9afaed31] + // Remove circular references + rtp_socket->setOnRead(nullptr); + } + }; + + _tcp_server = tcp_server; + _udp_server = udp_server; + _rtp_socket = rtp_socket; + _rtcp_helper = helper; + _tcp_mode = tcp_mode; +} + +void RtpServer::setOnDetach(RtpProcess::onDetachCB cb) { + if (_rtcp_helper) { + _rtcp_helper->setOnDetach(std::move(cb)); + } +} + +uint16_t RtpServer::getPort() { + return _udp_server ? _udp_server->getPort() : _rtp_socket->get_local_port(); +} + +void RtpServer::connectToServer(const std::string &url, uint16_t port, const function &cb) { + if (_tcp_mode != ACTIVE || !_rtp_socket) { + cb(SockException(Err_other, "仅支持tcp主动模式")); + return; + } + weak_ptr weak_self = shared_from_this(); + _rtp_socket->connect(url, port, [url, port, cb, weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + cb(SockException(Err_other, "服务对象已释放")); + return; + } + if (err) { + WarnL << "连接到服务器 " << url << ":" << port << " 失败 " << err; + } else { + InfoL << "连接到服务器 " << url << ":" << port << " 成功"; + strong_self->onConnect(); + } + cb(err); + }, + 5.0F, "::", _rtp_socket->get_local_port()); +} + +void RtpServer::onConnect() { + auto rtp_session = std::make_shared(_rtp_socket); + rtp_session->setRtpProcess(_rtcp_helper->getProcess()); + rtp_session->attachServer(*_tcp_server); + _rtp_socket->setOnRead([rtp_session](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + rtp_session->onRecv(buf); + }); + weak_ptr weak_self = shared_from_this(); + _rtp_socket->setOnErr([weak_self](const SockException &err) { + if (auto strong_self = weak_self.lock()) { + strong_self->_rtp_socket->setOnRead(nullptr); + } + }); +} + +void RtpServer::updateSSRC(uint32_t ssrc) { + if (_ssrc) { + *_ssrc = ssrc; + } + + if (_tcp_server) { + (*_tcp_server)[RtpSession::kSSRC] = ssrc; + } +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/RtpServer.h b/MediaServer/Rtp/RtpServer.h new file mode 100644 index 0000000..daf5b6e --- /dev/null +++ b/MediaServer/Rtp/RtpServer.h @@ -0,0 +1,121 @@ +/* + * 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_RTPSERVER_H +#define ZLMEDIAKIT_RTPSERVER_H + +#if defined(ENABLE_RTPPROXY) +#include +#include "Network/Socket.h" +#include "Network/TcpServer.h" +#include "Network/UdpServer.h" +#include "RtpSession.h" + +namespace mediakit { + +class RtcpHelper; + +/** + * RTP服务器,支持UDP/TCP + * RTP server, supports UDP/TCP + + * [AUTO-TRANSLATED:03e7daba] + */ +class RtpServer : public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + using onRecv = std::function; + enum TcpMode { NONE = 0, PASSIVE, ACTIVE }; + + ~RtpServer(); + + /** + * 开启服务器,可能抛异常 + * @param local_port 本地端口,0时为随机端口 + * @param local_ip 绑定的本地网卡ip + * @param stream_id 流id,置空则使用ssrc + * @param tcp_mode tcp服务模式 + * @param re_use_port 是否设置socket为re_use属性 + * @param ssrc 指定的ssrc + * @param multiplex 多路复用 + * Start the server, may throw an exception + * @param local_port Local port, 0 for random port + * @param local_ip Local network interface ip to bind + * @param stream_id Stream id, use ssrc if empty + * @param tcp_mode TCP service mode + * @param re_use_port Whether to set the socket to re_use property + * @param ssrc Specified ssrc + * @param multiplex Multiplexing + + * [AUTO-TRANSLATED:cb9e0717] + */ + void start(uint16_t local_port, const char *local_ip = "::", const MediaTuple &tuple = MediaTuple{DEFAULT_VHOST, kRtpAppName, "", ""}, TcpMode tcp_mode = PASSIVE, + bool re_use_port = true, uint32_t ssrc = 0, int only_track = 0, bool multiplex = false); + + /** + * 连接到tcp服务(tcp主动模式) + * @param url 服务器地址 + * @param port 服务器端口 + * @param cb 连接服务器是否成功的回调 + * Connect to the tcp service (tcp active mode) + * @param url Server address + * @param port Server port + * @param cb Callback whether the connection to the server is successful + + * [AUTO-TRANSLATED:3de57bc0] + */ + void connectToServer(const std::string &url, uint16_t port, const std::function &cb); + + /** + * 获取绑定的本地端口 + * Get the bound local port + + * [AUTO-TRANSLATED:beed15d6] + */ + uint16_t getPort(); + + /** + * 设置RtpProcess onDetach事件回调 + * Set RtpProcess onDetach event callback + + * [AUTO-TRANSLATED:a316d07e] + */ + void setOnDetach(RtpProcess::onDetachCB cb); + + /** + * 更新ssrc + * Update ssrc + + * [AUTO-TRANSLATED:dae5e276] + */ + void updateSSRC(uint32_t ssrc); + +private: + // tcp主动模式连接服务器成功回调 [AUTO-TRANSLATED:0775844e] + // tcp active mode connection server success callback + void onConnect(); + +protected: + toolkit::Socket::Ptr _rtp_socket; + toolkit::UdpServer::Ptr _udp_server; + toolkit::TcpServer::Ptr _tcp_server; + std::shared_ptr _ssrc; + std::shared_ptr _rtcp_helper; + std::function _on_cleanup; + + int _only_track = 0; + // 用于tcp主动模式 [AUTO-TRANSLATED:faedf05c] + // Used for tcp active mode + TcpMode _tcp_mode = NONE; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSERVER_H diff --git a/MediaServer/Rtp/RtpSession.cpp b/MediaServer/Rtp/RtpSession.cpp new file mode 100644 index 0000000..a68146d --- /dev/null +++ b/MediaServer/Rtp/RtpSession.cpp @@ -0,0 +1,286 @@ +/* + * 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_RTPPROXY) +#include "RtpSession.h" +#include "RtpProcess.h" +#include "Network/TcpServer.h" +#include "Rtsp/Rtsp.h" +#include "Rtsp/RtpReceiver.h" +#include "Common/config.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +const string RtpSession::kVhost = "vhost"; +const string RtpSession::kApp = "app"; +const string RtpSession::kStreamID = "stream_id"; +const string RtpSession::kSSRC = "ssrc"; +const string RtpSession::kOnlyTrack = "only_track"; +const string RtpSession::kUdpRecvBuffer = "udp_recv_socket_buffer"; + +void RtpSession::attachServer(const Server &server) { + setParams(const_cast(server)); +} + +void RtpSession::setParams(mINI &ini) { + _tuple.vhost = ini[kVhost]; + _tuple.app = ini[kApp]; + _tuple.stream = ini[kStreamID]; + _ssrc = ini[kSSRC]; + _only_track = ini[kOnlyTrack]; + int udp_socket_buffer = ini[kUdpRecvBuffer]; + if (_is_udp) { + // 设置udp socket读缓存 [AUTO-TRANSLATED:80cfb6e3] + // Set udp socket read buffer + SockUtil::setRecvBuf(getSock()->rawFD(), + (udp_socket_buffer > 0) ? udp_socket_buffer : (4 * 1024 * 1024)); + } +} + +RtpSession::RtpSession(const Socket::Ptr &sock) + : Session(sock) { + socklen_t addr_len = sizeof(_addr); + getpeername(sock->rawFD(), (struct sockaddr *)&_addr, &addr_len); + _is_udp = sock->sockType() == SockNum::Sock_UDP; +} + +RtpSession::~RtpSession() = default; + +void RtpSession::onRecv(const Buffer::Ptr &data) { + if (_is_udp) { + onRtpPacket(data->data(), data->size()); + return; + } + RtpSplitter::input(data->data(), data->size()); +} + +void RtpSession::onError(const SockException &err) { + if (_emit_detach) { + _process->onDetach(err); + } + WarnP(this) << _tuple.shortUrl() << " " << err; +} + +void RtpSession::onManager() { + if (!_process && _ticker.createdTime() > 10 * 1000) { + shutdown(SockException(Err_timeout, "illegal connection")); + } +} + +void RtpSession::setRtpProcess(RtpProcess::Ptr process) { + _emit_detach = (bool)process; + _process = std::move(process); +} + +void RtpSession::onRtpPacket(const char *data, size_t len) { + if (!isRtp(data, len)) { + // 忽略非rtp数据 [AUTO-TRANSLATED:771b77d8] + // Ignore non-rtp data + WarnP(this) << "Not rtp packet"; + return; + } + if (!_is_udp) { + if (_search_rtp) { + // 搜索上下文期间,数据丢弃 [AUTO-TRANSLATED:e0a3b407] + // Data discarded during context search + if (_search_rtp_finished) { + // 下个包开始就是正确的rtp包了 [AUTO-TRANSLATED:a73a3a61] + // The next packet is the correct rtp packet + _search_rtp_finished = false; + _search_rtp = false; + } + return; + } + GET_CONFIG(uint32_t, rtpMaxSize, Rtp::kRtpMaxSize); + if (len > 1024 * rtpMaxSize) { + _search_rtp = true; + WarnL << "rtp包长度异常(" << len << "),发送端可能缓存溢出并覆盖,开始搜索ssrc以便恢复上下文"; + return; + } + } + + // 未设置ssrc时,尝试获取ssrc [AUTO-TRANSLATED:30f31a81] + // Try to get ssrc when ssrc is not set + if (!_ssrc && !getSSRC(data, len, _ssrc)) { + return; + } + + // 未指定流id就使用ssrc为流id [AUTO-TRANSLATED:9eb98394] + // Use ssrc as stream id if stream id is not specified + if (_tuple.stream.empty()) { + _tuple.stream = printSSRC(_ssrc); + } + + if (!_process) { + _process = RtpProcess::createProcess(_tuple); + _process->setOnlyTrack((RtpProcess::OnlyTrack)_only_track); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _process->setOnDetach([weak_self](const SockException &ex) { + if (auto strong_self = weak_self.lock()) { + strong_self->_process = nullptr; + strong_self->shutdown(ex); + } + }); + } + try { + uint32_t rtp_ssrc = 0; + getSSRC(data, len, rtp_ssrc); + if (rtp_ssrc != _ssrc) { + WarnP(this) << "ssrc mismatched, rtp dropped: " << rtp_ssrc << " != " << _ssrc; + return; + } + _process->inputRtp(false, getSock(), data, len, (struct sockaddr *)&_addr); + } catch (RtpTrack::BadRtpException &ex) { + if (!_is_udp) { + WarnL << ex.what() << ",开始搜索ssrc以便恢复上下文"; + _search_rtp = true; + } else { + throw; + } + } + _ticker.resetTime(); +} + +static const char *findSSRC(const char *data, ssize_t len, uint32_t ssrc) { + // rtp前面必须预留两个字节的长度字段 [AUTO-TRANSLATED:2af4e647] + // Two bytes of length field must be reserved before rtp + for (ssize_t i = 2; i <= len - 4; ++i) { + auto ptr = (const uint8_t *)data + i; + if (ptr[0] == (ssrc >> 24) && ptr[1] == ((ssrc >> 16) & 0xFF) && ptr[2] == ((ssrc >> 8) & 0xFF) + && ptr[3] == (ssrc & 0xFF)) { + return (const char *)ptr; + } + } + return nullptr; +} + +static const char *findPsHeaderFlag(const char *data, ssize_t len) { + for (ssize_t i = 2; i <= len - 4; ++i) { + auto ptr = (const uint8_t *)data + i; + // PsHeader 0x000001ba、PsSystemHeader0x000001bb(关键帧标识) [AUTO-TRANSLATED:f8146534] + // PsHeader 0x000001ba, PsSystemHeader 0x000001bb (keyframe identifier) + if (ptr[0] == (0x00) && ptr[1] == (0x00) && ptr[2] == (0x01) && ptr[3] == (0xbb)) { + return (const char *)ptr; + } + } + + return nullptr; +} + +// rtp长度到ssrc间的长度固定为10 [AUTO-TRANSLATED:7428bd59] +// The length between rtp length and ssrc is fixed to 10 +static size_t constexpr kSSRCOffset = 2 + 4 + 4; +// rtp长度到ps header间的长度固定为14 (暂时不采用找ps header,采用找system header代替) [AUTO-TRANSLATED:cf6b289c] +// The length between rtp length and ps header is fixed to 14 (temporarily not using ps header, using system header instead) +// rtp长度到ps system header间的长度固定为20 (关键帧标识) [AUTO-TRANSLATED:abe8bb8e] +// The length between rtp length and ps system header is fixed to 20 (keyframe identifier) +static size_t constexpr kPSHeaderOffset = 2 + 4 + 4 + 4 + 20; + +const char *RtpSession::onSearchPacketTail(const char *data, size_t len) { + if (!_search_rtp) { + // tcp上下文正常,不用搜索ssrc [AUTO-TRANSLATED:cab86669] + // Tcp context is normal, no need to search ssrc + return RtpSplitter::onSearchPacketTail(data, len); + } + if (!_process) { + InfoL << "ssrc未获取到,无法通过ssrc恢复tcp上下文;尝试搜索PsSystemHeader恢复tcp上下文。"; + auto rtp_ptr1 = searchByPsHeaderFlag(data, len); + return rtp_ptr1; + } + auto rtp_ptr0 = searchBySSRC(data, len); + if (rtp_ptr0) { + return rtp_ptr0; + } + // ssrc搜索失败继续尝试搜索ps header flag [AUTO-TRANSLATED:e8f65bd2] + // Continue to search for ps header flag if ssrc search fails + auto rtp_ptr2 = searchByPsHeaderFlag(data, len); + return rtp_ptr2; +} + +const char *RtpSession::searchBySSRC(const char *data, size_t len) { + InfoL << "尝试rtp搜索ssrc..._ssrc=" << _ssrc; + // 搜索第一个rtp的ssrc [AUTO-TRANSLATED:6b010df0] + // Search for the first rtp's ssrc + auto ssrc_ptr0 = findSSRC(data, len, _ssrc); + if (!ssrc_ptr0) { + // 未搜索到任意rtp,返回数据不够 [AUTO-TRANSLATED:50db17ed] + // Return insufficient data if no rtp is found + InfoL << "rtp搜索ssrc失败(第一个数据不够),丢弃rtp数据为:" << len; + return nullptr; + } + // 这两个字节是第一个rtp的长度字段 [AUTO-TRANSLATED:75816ba4] + // These two bytes are the length field of the first rtp + auto rtp_len_ptr = (ssrc_ptr0 - kSSRCOffset); + auto rtp_len = ((uint8_t *)rtp_len_ptr)[0] << 8 | ((uint8_t *)rtp_len_ptr)[1]; + + // 搜索第二个rtp的ssrc [AUTO-TRANSLATED:238eaa43] + // Search for the second rtp's ssrc + auto ssrc_ptr1 = findSSRC(ssrc_ptr0 + rtp_len, data + (ssize_t)len - ssrc_ptr0 - rtp_len, _ssrc); + if (!ssrc_ptr1) { + // 未搜索到第二个rtp,返回数据不够 [AUTO-TRANSLATED:3a78a586] + // Return insufficient data if the second rtp is not found + InfoL << "rtp搜索ssrc失败(第二个数据不够),丢弃rtp数据为:" << len; + return nullptr; + } + + // 两个ssrc的间隔正好等于rtp的长度(外加rtp长度字段),那么说明找到rtp [AUTO-TRANSLATED:b1517bfd] + // The interval between the two ssrcs is exactly equal to the length of the rtp (plus the rtp length field), which means that the rtp is found + auto ssrc_offset = ssrc_ptr1 - ssrc_ptr0; + if (ssrc_offset == rtp_len + 2 || ssrc_offset == rtp_len + 4) { + InfoL << "rtp搜索ssrc成功,tcp上下文恢复成功,丢弃的rtp残余数据为:" << rtp_len_ptr - data; + _search_rtp_finished = true; + if (rtp_len_ptr == data) { + // 停止搜索rtp,否则会进入死循环 [AUTO-TRANSLATED:319eefa7] + // Stop searching for rtp, otherwise it will enter an infinite loop + _search_rtp = false; + } + // 前面的数据都需要丢弃,这个是rtp的起始 [AUTO-TRANSLATED:129082d2] + // All previous data needs to be discarded, this is the start of rtp + return rtp_len_ptr; + } + // 第一个rtp长度不匹配,说明第一个找到的ssrc不是rtp,丢弃之,我们从第二个ssrc所在rtp开始搜索 [AUTO-TRANSLATED:ec35b2ba] + // The length of the first rtp does not match, which means that the first ssrc found is not rtp, discard it, we start searching from the second ssrc rtp + return ssrc_ptr1 - kSSRCOffset; +} + +const char *RtpSession::searchByPsHeaderFlag(const char *data, size_t len) { + InfoL << "尝试rtp搜索PsSystemHeaderFlag..._ssrc=" << _ssrc; + // 搜索rtp中的第一个PsHeaderFlag [AUTO-TRANSLATED:77a18970] + // Search for the first PsHeaderFlag in rtp + auto ps_header_flag_ptr = findPsHeaderFlag(data, len); + if (!ps_header_flag_ptr) { + InfoL << "rtp搜索flag失败,丢弃rtp数据为:" << len; + return nullptr; + } + + auto rtp_ptr = ps_header_flag_ptr - kPSHeaderOffset; + _search_rtp_finished = true; + if (rtp_ptr == data) { + // 停止搜索rtp,否则会进入死循环 [AUTO-TRANSLATED:319eefa7] + // Stop searching for rtp, otherwise it will enter an infinite loop + _search_rtp = false; + } + InfoL << "rtp搜索flag成功,tcp上下文恢复成功,丢弃的rtp残余数据为:" << rtp_ptr - data; + + // TODO or Not ? 更新设置ssrc [AUTO-TRANSLATED:9c21db0a] + // TODO or Not ? Update setting ssrc + uint32_t rtp_ssrc = 0; + getSSRC(rtp_ptr + 2, len, rtp_ssrc); + _ssrc = rtp_ssrc; + InfoL << "设置_ssrc为:" << _ssrc; + // RtpServer::updateSSRC(uint32_t ssrc) + return rtp_ptr; +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) diff --git a/MediaServer/Rtp/RtpSession.h b/MediaServer/Rtp/RtpSession.h new file mode 100644 index 0000000..25d1af9 --- /dev/null +++ b/MediaServer/Rtp/RtpSession.h @@ -0,0 +1,69 @@ +/* + * 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_RTPSESSION_H +#define ZLMEDIAKIT_RTPSESSION_H + +#if defined(ENABLE_RTPPROXY) + +#include "Network/Session.h" +#include "RtpSplitter.h" +#include "RtpProcess.h" +#include "Util/TimeTicker.h" + +namespace mediakit{ + +class RtpSession : public toolkit::Session, public RtpSplitter { +public: + static const std::string kVhost; + static const std::string kApp; + static const std::string kStreamID; + static const std::string kSSRC; + static const std::string kOnlyTrack; + static const std::string kUdpRecvBuffer; + + RtpSession(const toolkit::Socket::Ptr &sock); + ~RtpSession() override; + void onRecv(const toolkit::Buffer::Ptr &) override; + void onError(const toolkit::SockException &err) override; + void onManager() override; + void setParams(toolkit::mINI &ini); + void attachServer(const toolkit::Server &server) override; + void setRtpProcess(RtpProcess::Ptr process); + +protected: + // 收到rtp回调 [AUTO-TRANSLATED:446b2cda] + // Received RTP callback + void onRtpPacket(const char *data, size_t len) override; + // RtpSplitter override + const char *onSearchPacketTail(const char *data, size_t len) override; + // 搜寻SSRC [AUTO-TRANSLATED:2cfec2e1] + // Search for SSRC + const char *searchBySSRC(const char *data, size_t len); + // 搜寻PS包里的关键帧标头 [AUTO-TRANSLATED:d8e88339] + // Search for keyframe header in PS packet + const char *searchByPsHeaderFlag(const char *data, size_t len); + +private: + bool _is_udp = false; + bool _search_rtp = false; + bool _search_rtp_finished = false; + bool _emit_detach = false; + int _only_track = 0; + uint32_t _ssrc = 0; + toolkit::Ticker _ticker; + MediaTuple _tuple; + struct sockaddr_storage _addr; + RtpProcess::Ptr _process; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSESSION_H diff --git a/MediaServer/Rtp/RtpSplitter.cpp b/MediaServer/Rtp/RtpSplitter.cpp new file mode 100644 index 0000000..d82cf6c --- /dev/null +++ b/MediaServer/Rtp/RtpSplitter.cpp @@ -0,0 +1,103 @@ +/* + * 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_RTPPROXY) +#include +#include "RtpSplitter.h" +namespace mediakit{ + +static const int kEHOME_OFFSET = 256; + +ssize_t RtpSplitter::onRecvHeader(const char *data,size_t len){ + // 忽略偏移量 [AUTO-TRANSLATED:7fbc3d5d] + // Ignore offset + data += _offset; + len -= _offset; + + if (_is_ehome && len > 12 && data[12] == '\r') { + // 这是ehome,移除第12个字节 [AUTO-TRANSLATED:643b3f39] + // This is ehome, remove the 12th byte + memmove((char *) data + 1, data, 12); + data += 1; + len -= 1; + } + onRtpPacket(data, len); + return 0; +} + +static bool isEhome(const char *data, size_t len){ + if (len < 4) { + return false; + } + if ((data[0] == 0x01) && (data[1] == 0x00) && (data[2] >= 0x01)) { + return true; + } + return false; +} + +const char *RtpSplitter::onSearchPacketTail(const char *data, size_t len) { + if (len < 4) { + // 数据不够 [AUTO-TRANSLATED:72802244] + // Not enough data + return nullptr; + } + + if (_check_ehome_count) { + if (isEhome(data, len)) { + // 是ehome协议 [AUTO-TRANSLATED:daa874ca] + // It is the ehome protocol + if (len < kEHOME_OFFSET + 4) { + // 数据不够 [AUTO-TRANSLATED:72802244] + // Not enough data + return nullptr; + } + // 忽略ehome私有头后是rtsp样式的rtp,多4个字节, [AUTO-TRANSLATED:d9bc652c] + // Ignore the ehome private header, then it is an rtsp-style rtp, with 4 extra bytes, + _offset = kEHOME_OFFSET + 4; + _is_ehome = true; + // 忽略ehome私有头 [AUTO-TRANSLATED:4fc98661] + // Ignore the ehome private header + return onSearchPacketTail_l(data + kEHOME_OFFSET + 2, len - kEHOME_OFFSET - 2); + } + _check_ehome_count--; + } + + if ( _is_rtsp_interleaved ) { + if (data[0] == '$') { + // 可能是4个字节的rtp头 [AUTO-TRANSLATED:c287c3cb] + // It may be a 4-byte rtp header + _offset = 4; + return onSearchPacketTail_l(data + 2, len - 2); + } + _is_rtsp_interleaved = false; + } + + // 两个字节的rtp头 [AUTO-TRANSLATED:85fed462] + // A 2-byte rtp header + _offset = 2; + return onSearchPacketTail_l(data, len); +} + +const char *RtpSplitter::onSearchPacketTail_l(const char *data, size_t len) { + // 这是rtp包 [AUTO-TRANSLATED:627d4881] + // This is an rtp packet + uint16_t length = (((uint8_t *) data)[0] << 8) | ((uint8_t *) data)[1]; + if (len < (size_t)(length + 2)) { + // 数据不够 [AUTO-TRANSLATED:72802244] + // Not enough data + return nullptr; + } + // 返回rtp包末尾 [AUTO-TRANSLATED:2546d5ce] + // Return the end of the rtp packet + return data + 2 + length; +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/MediaServer/Rtp/RtpSplitter.h b/MediaServer/Rtp/RtpSplitter.h new file mode 100644 index 0000000..c583c93 --- /dev/null +++ b/MediaServer/Rtp/RtpSplitter.h @@ -0,0 +1,48 @@ +/* + * 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_RTPSPLITTER_H +#define ZLMEDIAKIT_RTPSPLITTER_H + +#if defined(ENABLE_RTPPROXY) +#include "Http/HttpRequestSplitter.h" + +namespace mediakit{ + +class RtpSplitter : public HttpRequestSplitter{ +protected: + /** + * 收到rtp包回调 + * @param data RTP包数据指针 + * @param len RTP包数据长度 + * RTP packet received callback + * @param data RTP packet data pointer + * @param len RTP packet data length + + + * [AUTO-TRANSLATED:18a85278] + */ + virtual void onRtpPacket(const char *data, size_t len) = 0; + +protected: + ssize_t onRecvHeader(const char *data, size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + const char *onSearchPacketTail_l(const char *data, size_t len); + +private: + bool _is_ehome = false; + int _check_ehome_count = 3; + bool _is_rtsp_interleaved = true; + size_t _offset = 0; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSPLITTER_H diff --git a/MediaServer/Rtp/TSDecoder.cpp b/MediaServer/Rtp/TSDecoder.cpp new file mode 100644 index 0000000..8efefc3 --- /dev/null +++ b/MediaServer/Rtp/TSDecoder.cpp @@ -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. + */ + +#include "TSDecoder.h" +namespace mediakit { + +bool TSSegment::isTSPacket(const char *data, size_t len){ + return len == TS_PACKET_SIZE && ((uint8_t*)data)[0] == TS_SYNC_BYTE; +} + +void TSSegment::setOnSegment(TSSegment::onSegment cb) { + _onSegment = std::move(cb); +} + +ssize_t TSSegment::onRecvHeader(const char *data, size_t len) { + if (!isTSPacket(data, len)) { + WarnL << "不是ts包:" << (int) (data[0]) << " " << len; + return 0; + } + _onSegment(data, len); + return 0; +} + +const char *TSSegment::onSearchPacketTail(const char *data, size_t len) { + if (len < _size + 1) { + if (len == _size && ((uint8_t *) data)[0] == TS_SYNC_BYTE) { + return data + _size; + } + return nullptr; + } + // 下一个包头 [AUTO-TRANSLATED:c653c49d] + // Next packet header + if (((uint8_t *) data)[_size] == TS_SYNC_BYTE) { + return data + _size; + } + auto pos = memchr(data + _size, TS_SYNC_BYTE, len - _size); + if (pos) { + return (char *) pos; + } + if (remainDataSize() > 4 * _size) { + // 数据这么多都没ts包,全部清空 [AUTO-TRANSLATED:95bece98] + // So much data but no ts packets, clear all + return data + len; + } + // 等待更多数据 [AUTO-TRANSLATED:b47fbc81] + // Wait for more data + return nullptr; +} + +//////////////////////////////////////////////////////////////// + +#if defined(ENABLE_HLS) +#include "mpeg-ts.h" +TSDecoder::TSDecoder() : _ts_segment() { + _ts_segment.setOnSegment([this](const char *data, size_t len){ + ts_demuxer_input(_demuxer_ctx,(uint8_t*)data,len); + }); + _demuxer_ctx = ts_demuxer_create([](void* param, int program, int stream, int codecid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes){ + TSDecoder *thiz = (TSDecoder*)param; + if (thiz->_on_decode) { + if (flags & MPEG_FLAG_PACKET_CORRUPT) { + WarnL << "ts packet lost, dts:" << dts << " pts:" << pts << " bytes:" << bytes; + } else { + thiz->_on_decode(stream, codecid, flags, pts, dts, data, bytes); + } + } + return 0; + },this); + + ts_demuxer_notify_t notify = { + [](void *param, int stream, int codecid, const void *extra, int bytes, int finish) { + TSDecoder *thiz = (TSDecoder *) param; + if (thiz->_on_stream) { + thiz->_on_stream(stream, codecid, extra, bytes, finish); + } + } + }; + ts_demuxer_set_notify((struct ts_demuxer_t *) _demuxer_ctx, ¬ify, this); +} + +TSDecoder::~TSDecoder() { + ts_demuxer_destroy(_demuxer_ctx); +} + +ssize_t TSDecoder::input(const uint8_t *data, size_t bytes) { + if (TSSegment::isTSPacket((char *)data, bytes)) { + return ts_demuxer_input(_demuxer_ctx, (uint8_t *) data, bytes); + } + try { + _ts_segment.input((char *) data, bytes); + } catch (...) { + // ts解析失败,清空缓存数据 [AUTO-TRANSLATED:18b3de5b] + // ts parsing failed, clear cache data + _ts_segment.reset(); + throw; + } + return bytes; +} + +#endif//defined(ENABLE_HLS) + +}//namespace mediakit diff --git a/MediaServer/Rtp/TSDecoder.h b/MediaServer/Rtp/TSDecoder.h new file mode 100644 index 0000000..7812d51 --- /dev/null +++ b/MediaServer/Rtp/TSDecoder.h @@ -0,0 +1,57 @@ +/* + * 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_TSDECODER_H +#define ZLMEDIAKIT_TSDECODER_H + +#include "Util/logger.h" +#include "Http/HttpRequestSplitter.h" +#include "Decoder.h" + +#define TS_PACKET_SIZE 188 +#define TS_SYNC_BYTE 0x47 + +namespace mediakit { + +// TS包分割器,用于split一个一个的ts包 [AUTO-TRANSLATED:a10b66b3] +// TS package splitter, used to split one ts package at a time +class TSSegment : public HttpRequestSplitter { +public: + typedef std::function onSegment; + TSSegment(size_t size = TS_PACKET_SIZE) : _size(size){} + void setOnSegment(onSegment cb); + static bool isTSPacket(const char *data, size_t len); + +protected: + ssize_t onRecvHeader(const char *data, size_t len) override ; + const char *onSearchPacketTail(const char *data, size_t len) override ; + +private: + size_t _size; + onSegment _onSegment; +}; + +#if defined(ENABLE_HLS) +// ts解析器 [AUTO-TRANSLATED:f2b9f0cc] +// ts parser +class TSDecoder : public Decoder { +public: + TSDecoder(); + ~TSDecoder(); + ssize_t input(const uint8_t* data, size_t bytes) override ; + +private: + TSSegment _ts_segment; + struct ts_demuxer_t* _demuxer_ctx = nullptr; +}; +#endif//defined(ENABLE_HLS) + +}//namespace mediakit +#endif //ZLMEDIAKIT_TSDECODER_H diff --git a/MediaServer/Rtsp/RtpCodec.cpp b/MediaServer/Rtsp/RtpCodec.cpp new file mode 100644 index 0000000..0ed1cd2 --- /dev/null +++ b/MediaServer/Rtsp/RtpCodec.cpp @@ -0,0 +1,56 @@ +/* + * 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 "RtpCodec.h" + +namespace mediakit{ + +RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bool mark, uint64_t stamp) { + uint16_t payload_len = (uint16_t) (len + RtpPacket::kRtpHeaderSize); + auto rtp = RtpPacket::create(); + rtp->setCapacity(payload_len + RtpPacket::kRtpTcpHeaderSize); + rtp->setSize(payload_len + RtpPacket::kRtpTcpHeaderSize); + rtp->sample_rate = _sample_rate; + rtp->type = type; + rtp->track_index = _track_index; + + // rtsp over tcp 头 [AUTO-TRANSLATED:4225b9ec] + // rtsp over tcp header + auto ptr = (uint8_t *) rtp->data(); + ptr[0] = '$'; + ptr[1] = _interleaved; + ptr[2] = payload_len >> 8; + ptr[3] = payload_len & 0xFF; + + // rtp头 [AUTO-TRANSLATED:64aef747] + // rtp header + auto header = rtp->getHeader(); + header->version = RtpPacket::kRtpVersion; + header->padding = 0; + header->ext = 0; + header->csrc = 0; + header->mark = mark; + header->pt = _pt; + header->seq = htons(_seq); + ++_seq; + header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000); + header->ssrc = htonl(_ssrc); + rtp->ntp_stamp = stamp; + // 有效负载 [AUTO-TRANSLATED:8530a274] + // payload + if (data) { + memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len); + } + return rtp; +} + +}//namespace mediakit + + diff --git a/MediaServer/Rtsp/RtpCodec.h b/MediaServer/Rtsp/RtpCodec.h new file mode 100644 index 0000000..49e1ddd --- /dev/null +++ b/MediaServer/Rtsp/RtpCodec.h @@ -0,0 +1,132 @@ +/* + * 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_RTPCODEC_H +#define ZLMEDIAKIT_RTPCODEC_H + +#include +#include "Extension/Frame.h" +#include "Util/RingBuffer.h" +#include "Rtsp/Rtsp.h" + +namespace mediakit { + +class RtpRing { +public: + using Ptr = std::shared_ptr; + using RingType = toolkit::RingBuffer; + + virtual ~RtpRing() = default; + + /** + * 设置rtp环形缓存 + * @param ring + * Set the RTP ring buffer + * @param ring + + * [AUTO-TRANSLATED:283d737a] + */ + void setRtpRing(RingType::Ptr ring) { + _ring = std::move(ring); + } + + /** + * 输入rtp包 + * @param rtp rtp包 + * @param key_pos 是否为关键帧第一个rtp包 + * @return 是否为关键帧第一个rtp包 + * Input RTP packet + * @param rtp RTP packet + * @param key_pos Whether it is the first RTP packet of the key frame + * @return Whether it is the first RTP packet of the key frame + + * [AUTO-TRANSLATED:05ccdd17] + */ + virtual bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) { + if (_ring) { + _ring->write(rtp, key_pos); + } + return key_pos; + } + +protected: + RingType::Ptr _ring; +}; + +class RtpInfo { +public: + using Ptr = std::shared_ptr; + + RtpInfo(uint32_t ssrc, size_t mtu_size, uint32_t sample_rate, uint8_t pt, uint8_t interleaved, int track_index) { + if (ssrc == 0) { + ssrc = ((uint64_t) this) & 0xFFFFFFFF; + } + _pt = pt; + _ssrc = ssrc; + _mtu_size = mtu_size; + _sample_rate = sample_rate; + _interleaved = interleaved; + _track_index = track_index; + } + + // 返回rtp负载最大长度 [AUTO-TRANSLATED:0a8ee7d9] + // Return the maximum length of the RTP payload + size_t getMaxSize() const { + return _mtu_size - RtpPacket::kRtpHeaderSize; + } + + RtpPacket::Ptr makeRtp(TrackType type,const void *data, size_t len, bool mark, uint64_t stamp); + +private: + uint8_t _pt; + uint8_t _interleaved; + uint16_t _seq = 0; + uint32_t _ssrc; + uint32_t _sample_rate; + int _track_index; + size_t _mtu_size; +}; + +class RtpCodec : public RtpRing, public FrameDispatcher { +public: + using Ptr = std::shared_ptr; + + void setRtpInfo(uint32_t ssrc, size_t mtu_size, uint32_t sample_rate, uint8_t pt, uint8_t interleaved = 0, int track_index = 0) { + _rtp_info.reset(new RtpInfo(ssrc, mtu_size, sample_rate, pt, interleaved, track_index)); + } + + RtpInfo &getRtpInfo() { return *_rtp_info; } + + enum { + RTP_ENCODER_PKT_DUR_MS = 1 // 主要应用于g711 rtp 打包器每个包的时间长度,option_value 为int*, option_len 为4 + }; + /** + * @brief 设置rtp打包器与解包器的相关参数,主要应用与g711 rtp 打包器,使用方法类似setsockopt + * + * @param opt 设置的选项 + * @param param 设置的参数 + * @brief Set the parameters of the RTP packer and unpacker, mainly used for g711 RTP packer, the usage is similar to setsockopt + * + * @param opt Set options + * @param param Set parameters + + + * [AUTO-TRANSLATED:95859425] + */ + virtual void setOpt(int opt, const toolkit::Any ¶m) {}; + +private: + std::unique_ptr _rtp_info; +}; + +}//namespace mediakit + + +#endif //ZLMEDIAKIT_RTPCODEC_H diff --git a/MediaServer/Rtsp/RtpMultiCaster.cpp b/MediaServer/Rtsp/RtpMultiCaster.cpp new file mode 100644 index 0000000..1df69dc --- /dev/null +++ b/MediaServer/Rtsp/RtpMultiCaster.cpp @@ -0,0 +1,222 @@ +/* + * 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 +#include +#include "RtpMultiCaster.h" +#include "Util/util.h" +#include "Network/sockutil.h" +#include "RtspSession.h" +#include "Common/config.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +MultiCastAddressMaker &MultiCastAddressMaker::Instance() { + static MultiCastAddressMaker instance; + return instance; +} + +bool MultiCastAddressMaker::isMultiCastAddress(uint32_t addr) { + static uint32_t addrMin = mINI::Instance()[MultiCast::kAddrMin].as(); + static uint32_t addrMax = mINI::Instance()[MultiCast::kAddrMax].as(); + return addr >= addrMin && addr <= addrMax; +} + +string MultiCastAddressMaker::toString(uint32_t addr) { + addr = htonl(addr); + return SockUtil::inet_ntoa((struct in_addr &) (addr)); +} + +static uint32_t addressToInt(const string &ip){ + struct in_addr addr; + bzero(&addr, sizeof(addr)); + addr.s_addr = inet_addr(ip.data()); + return (uint32_t) ntohl((uint32_t &) addr.s_addr); +} + +std::shared_ptr MultiCastAddressMaker::obtain(uint32_t max_try) { + lock_guard lck(_mtx); + GET_CONFIG_FUNC(uint32_t, addrMin, MultiCast::kAddrMin, [](const string &str) { + return addressToInt(str); + }); + GET_CONFIG_FUNC(uint32_t, addrMax, MultiCast::kAddrMax, [](const string &str) { + return addressToInt(str); + }); + + if (_addr > addrMax || _addr == 0) { + _addr = addrMin; + } + auto iGotAddr = _addr++; + if (_used_addr.find(iGotAddr) != _used_addr.end()) { + // 已经分配过了 [AUTO-TRANSLATED:b231af33] + // Already allocated + if (max_try) { + return obtain(--max_try); + } + // 分配完了,应该不可能到这里 [AUTO-TRANSLATED:c7f06cb9] + // Allocation is complete, it should not be possible to reach here + ErrorL; + return nullptr; + } + _used_addr.emplace(iGotAddr); + std::shared_ptr ret(new uint32_t(iGotAddr), [](uint32_t *ptr) { + MultiCastAddressMaker::Instance().release(*ptr); + delete ptr; + }); + return ret; +} + +void MultiCastAddressMaker::release(uint32_t addr){ + lock_guard lck(_mtx); + _used_addr.erase(addr); +} + +//////////////////////////////////////////////////////////////////////////////////// + +recursive_mutex g_mtx; +unordered_map > g_multi_caster_map; + +void RtpMultiCaster::setDetachCB(void* listener, const onDetach& cb) { + lock_guard lck(_mtx); + if (cb) { + _detach_map.emplace(listener, cb); + } else { + _detach_map.erase(listener); + } +} + +RtpMultiCaster::~RtpMultiCaster() { + _rtp_reader->setReadCB(nullptr); + _rtp_reader->setDetachCB(nullptr); + DebugL; +} + +RtpMultiCaster::RtpMultiCaster(SocketHelper &helper, const string &local_ip, const MediaTuple &tuple, uint32_t multicast_ip, uint16_t video_port, uint16_t audio_port) { + auto src = dynamic_pointer_cast(MediaSource::find(RTSP_SCHEMA, tuple.vhost, tuple.app, tuple.stream)); + if (!src) { + auto err = StrPrinter << "未找到媒体源:" << tuple.shortUrl() << endl; + throw std::runtime_error(err); + } + _multicast_ip = (multicast_ip) ? make_shared(multicast_ip) : MultiCastAddressMaker::Instance().obtain(); + if (!_multicast_ip) { + throw std::runtime_error("获取组播地址失败"); + } + + for (auto i = 0; i < 2; ++i) { + // 创建udp socket, 数组下标为TrackType [AUTO-TRANSLATED:17d153d5] + // Create UDP socket, array index is TrackType + _udp_sock[i] = helper.createSocket(); + if (!_udp_sock[i]->bindUdpSock((i == TrackVideo) ? video_port : audio_port, local_ip.data())) { + auto err = StrPrinter << "绑定UDP端口失败:" << local_ip << endl; + throw std::runtime_error(err); + } + auto fd = _udp_sock[i]->rawFD(); + GET_CONFIG(uint32_t, udpTTL, MultiCast::kUdpTTL); + SockUtil::setMultiTTL(fd, udpTTL); + SockUtil::setMultiLOOP(fd, false); + SockUtil::setMultiIF(fd, local_ip.data()); + + struct sockaddr_in peer; + peer.sin_family = AF_INET; + // 组播目标端口为本地发送端口 [AUTO-TRANSLATED:9eae5d47] + // Multicast target port is the local sending port + peer.sin_port = htons(_udp_sock[i]->get_local_port()); + // 组播目标地址 [AUTO-TRANSLATED:3291a33b] + // Multicast target address + peer.sin_addr.s_addr = htonl(*_multicast_ip); + bzero(&(peer.sin_zero), sizeof peer.sin_zero); + _udp_sock[i]->bindPeerAddr((struct sockaddr *) &peer); + } + + src->pause(false); + _rtp_reader = src->getRing()->attach(helper.getPoller()); + _rtp_reader->setReadCB([this](const RtspMediaSource::RingDataType &pkt) { + size_t i = 0; + auto size = pkt->size(); + pkt->for_each([&](const RtpPacket::Ptr &rtp) { + auto &sock = _udp_sock[rtp->type]; + sock->send(std::make_shared(rtp, 4), nullptr, 0, ++i == size); + }); + }); + + string strKey = StrPrinter << local_ip << " " << tuple.vhost << " " << tuple.app << " " << tuple.stream << endl; + _rtp_reader->setDetachCB([this, strKey]() { + { + lock_guard lck(g_mtx); + auto it = g_multi_caster_map.find(strKey); + if (it != g_multi_caster_map.end()) { + g_multi_caster_map.erase(it); + } + } + + unordered_map _detach_map_copy; + { + lock_guard lck(_mtx); + _detach_map_copy = std::move(_detach_map); + } + for (auto &pr : _detach_map_copy) { + pr.second(); + } + }); + + DebugL << MultiCastAddressMaker::toString(*_multicast_ip) << " " + << _udp_sock[0]->get_local_port() << " " + << _udp_sock[1]->get_local_port() << " " + << tuple.shortUrl(); +} + +uint16_t RtpMultiCaster::getMultiCasterPort(TrackType trackType) { + return _udp_sock[trackType]->get_local_port(); +} + +string RtpMultiCaster::getMultiCasterIP() { + struct in_addr addr; + addr.s_addr = htonl(*_multicast_ip); + return SockUtil::inet_ntoa(addr); +} + +RtpMultiCaster::Ptr RtpMultiCaster::get(SocketHelper &helper, const string &local_ip, const MediaTuple &tuple, uint32_t multicast_ip, uint16_t video_port, uint16_t audio_port) { + static auto on_create = [](SocketHelper &helper, const string &local_ip, const MediaTuple &tuple, uint32_t multicast_ip, uint16_t video_port, uint16_t audio_port){ + try { + auto poller = helper.getPoller(); + auto ret = RtpMultiCaster::Ptr(new RtpMultiCaster(helper, local_ip, tuple, multicast_ip, video_port, audio_port), [poller](RtpMultiCaster *ptr) { + poller->async([ptr]() { + delete ptr; + }); + }); + lock_guard lck(g_mtx); + string strKey = StrPrinter << local_ip << " " << tuple.vhost << " " << tuple.app << " " << tuple.stream << endl; + g_multi_caster_map.emplace(strKey, ret); + return ret; + } catch (std::exception &ex) { + WarnL << ex.what(); + return RtpMultiCaster::Ptr(); + } + }; + + string strKey = StrPrinter << local_ip << " " << tuple.vhost << " " << tuple.app << " " << tuple.stream << endl; + lock_guard lck(g_mtx); + auto it = g_multi_caster_map.find(strKey); + if (it == g_multi_caster_map.end()) { + return on_create(helper, local_ip, tuple, multicast_ip, video_port, audio_port); + } + auto ret = it->second.lock(); + if (!ret) { + g_multi_caster_map.erase(it); + return on_create(helper, local_ip, tuple, multicast_ip, video_port, audio_port); + } + return ret; +} + + +}//namespace mediakit diff --git a/MediaServer/Rtsp/RtpMultiCaster.h b/MediaServer/Rtsp/RtpMultiCaster.h new file mode 100644 index 0000000..fae48f7 --- /dev/null +++ b/MediaServer/Rtsp/RtpMultiCaster.h @@ -0,0 +1,66 @@ +/* + * 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_RTSP_RTPBROADCASTER_H_ +#define SRC_RTSP_RTPBROADCASTER_H_ + +#include +#include +#include +#include +#include "RtspMediaSource.h" +#include "Network/Socket.h" + +namespace mediakit{ + +class MultiCastAddressMaker { +public: + static MultiCastAddressMaker& Instance(); + static bool isMultiCastAddress(uint32_t addr); + static std::string toString(uint32_t addr); + + std::shared_ptr obtain(uint32_t max_try = 10); + +private: + MultiCastAddressMaker() = default; + void release(uint32_t addr); + +private: + uint32_t _addr = 0; + std::recursive_mutex _mtx; + std::unordered_set _used_addr; +}; + +class RtpMultiCaster { +public: + using Ptr = std::shared_ptr; + using onDetach = std::function; + + ~RtpMultiCaster(); + + static Ptr get(toolkit::SocketHelper &helper, const std::string &local_ip, const MediaTuple &tuple, uint32_t multicast_ip = 0, uint16_t video_port = 0, uint16_t audio_port = 0); + void setDetachCB(void *listener,const onDetach &cb); + + std::string getMultiCasterIP(); + uint16_t getMultiCasterPort(TrackType trackType); + +private: + RtpMultiCaster(toolkit::SocketHelper &helper, const std::string &local_ip, const MediaTuple &tuple, uint32_t multicast_ip, uint16_t video_port, uint16_t audio_port); + +private: + std::recursive_mutex _mtx; + toolkit::Socket::Ptr _udp_sock[2]; + std::shared_ptr _multicast_ip; + std::unordered_map _detach_map; + RtspMediaSource::RingType::RingReader::Ptr _rtp_reader; +}; + +}//namespace mediakit +#endif /* SRC_RTSP_RTPBROADCASTER_H_ */ diff --git a/MediaServer/Rtsp/RtpReceiver.cpp b/MediaServer/Rtsp/RtpReceiver.cpp new file mode 100644 index 0000000..f17db01 --- /dev/null +++ b/MediaServer/Rtsp/RtpReceiver.cpp @@ -0,0 +1,155 @@ +/* + * 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 "RtpReceiver.h" + +namespace mediakit { + +RtpTrack::RtpTrack() { + setOnSort([this](uint16_t seq, RtpPacket::Ptr packet) { + onRtpSorted(std::move(packet)); + }); +} + +uint32_t RtpTrack::getSSRC() const { + return _ssrc; +} + +void RtpTrack::clear() { + _ssrc = 0; + _ssrc_alive.resetTime(); + PacketSortor::clear(); +} + +RtpPacket::Ptr RtpTrack::inputRtp(TrackType type, int sample_rate, uint8_t *ptr, size_t len) { + if (len < RtpPacket::kRtpHeaderSize) { + throw BadRtpException("rtp size less than 12"); + } + GET_CONFIG(uint32_t, rtpMaxSize, Rtp::kRtpMaxSize); + if (len > 1024 * rtpMaxSize) { + WarnL << "超大的rtp包:" << len << " > " << 1024 * rtpMaxSize; + return nullptr; + } + if (!sample_rate) { + // 无法把时间戳转换成毫秒 [AUTO-TRANSLATED:ec2d97b6] + // Unable to convert timestamp to milliseconds + return nullptr; + } + RtpHeader *header = (RtpHeader *) ptr; + if (header->version != RtpPacket::kRtpVersion) { + throw BadRtpException("invalid rtp version"); + } + if (header->getPayloadSize(len) < 0) { + // rtp有效负载小于0,非法 [AUTO-TRANSLATED:07eb3ec3] + // RTP payload is less than 0, illegal + throw BadRtpException("invalid rtp payload size"); + } + + // 比对缓存ssrc [AUTO-TRANSLATED:206cb66f] + // Compare cache ssrc + auto ssrc = ntohl(header->ssrc); + + if (_pt == 0xFF) { + _pt = header->pt; + } else if (header->pt != _pt) { + //TraceL << "rtp pt mismatch:" << (int) header->pt << " !=" << (int) _pt; + return nullptr; + } + + if (!_ssrc) { + // 记录并锁定ssrc [AUTO-TRANSLATED:29452029] + // Record and lock ssrc + _ssrc = ssrc; + _ssrc_alive.resetTime(); + } else if (_ssrc == ssrc) { + // ssrc匹配正确,刷新计时器 [AUTO-TRANSLATED:266518e6] + // SSRC matches correctly, refresh timer + _ssrc_alive.resetTime(); + } else { + // ssrc错误 [AUTO-TRANSLATED:b967d497] + // SSRC error + if (_ssrc_alive.elapsedTime() < 3 * 1000) { + // 接收正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp [AUTO-TRANSLATED:2f98c2b5] + // If the RTP with the correct SSRC is received within 10 seconds, we consider it to be multi-path RTP, and ignore the RTP with mismatched SSRC + WarnL << "ssrc mismatch, rtp dropped:" << ssrc << " != " << _ssrc; + return nullptr; + } + InfoL << "rtp ssrc changed:" << _ssrc << " -> " << ssrc; + _ssrc = ssrc; + _ssrc_alive.resetTime(); + } + + auto rtp = RtpPacket::create(); + // 需要添加4个字节的rtp over tcp头 [AUTO-TRANSLATED:a37d639b] + // Need to add 4 bytes of RTP over TCP header + rtp->setCapacity(RtpPacket::kRtpTcpHeaderSize + len); + rtp->setSize(RtpPacket::kRtpTcpHeaderSize + len); + rtp->sample_rate = sample_rate; + rtp->type = type; + + // 赋值4个字节的rtp over tcp头 [AUTO-TRANSLATED:eeb990a9] + // Assign 4 bytes of RTP over TCP header + uint8_t *data = (uint8_t *) rtp->data(); + data[0] = '$'; + data[1] = 2 * type; + data[2] = (len >> 8) & 0xFF; + data[3] = len & 0xFF; + // 拷贝rtp [AUTO-TRANSLATED:3a2466c2] + // Copy RTP + memcpy(&data[4], ptr, len); + if (_disable_ntp) { + // 不支持ntp时间戳,例如国标推流,那么直接使用rtp时间戳 [AUTO-TRANSLATED:20085979] + // Does not support NTP timestamp, such as national standard streaming, so directly use RTP timestamp + rtp->ntp_stamp = rtp->getStamp() * uint64_t(1000) / sample_rate; + } else { + // 设置ntp时间戳 [AUTO-TRANSLATED:5e60d5cf] + // Set NTP timestamp + rtp->ntp_stamp = _ntp_stamp.getNtpStamp(rtp->getStamp(), sample_rate); + } + onBeforeRtpSorted(rtp); + sortPacket(rtp->getSeq(), rtp); + return rtp; +} + +void RtpTrack::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) { + _disable_ntp = rtp_stamp == 0 && ntp_stamp_ms == 0; + if (!_disable_ntp) { + _ntp_stamp.setNtpStamp(rtp_stamp, ntp_stamp_ms); + } +} + +void RtpTrack::setPayloadType(uint8_t pt) { + _pt = pt; +} + +//////////////////////////////////////////////////////////////////////////////////// + +void RtpTrackImp::setOnSorted(OnSorted cb) { + _on_sorted = std::move(cb); +} + +void RtpTrackImp::setBeforeSorted(BeforeSorted cb) { + _on_before_sorted = std::move(cb); +} + +void RtpTrackImp::onRtpSorted(RtpPacket::Ptr rtp) { + if (_on_sorted) { + _on_sorted(std::move(rtp)); + } +} + +void RtpTrackImp::onBeforeRtpSorted(const RtpPacket::Ptr &rtp) { + if (_on_before_sorted) { + _on_before_sorted(rtp); + } +} + +}//namespace mediakit diff --git a/MediaServer/Rtsp/RtpReceiver.h b/MediaServer/Rtsp/RtpReceiver.h new file mode 100644 index 0000000..e650f7a --- /dev/null +++ b/MediaServer/Rtsp/RtpReceiver.h @@ -0,0 +1,402 @@ +/* + * 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_RTPRECEIVER_H +#define ZLMEDIAKIT_RTPRECEIVER_H + +#include +#include +#include +#include "Rtsp/Rtsp.h" +#include "Extension/Frame.h" +// for NtpStamp +#include "Common/Stamp.h" +#include "Util/TimeTicker.h" + +namespace mediakit { + +template +class PacketSortor { +public: + static constexpr SEQ SEQ_MAX = (std::numeric_limits::max)(); + using iterator = typename std::map::iterator; + + virtual ~PacketSortor() = default; + + void setOnSort(std::function cb) { _cb = std::move(cb); } + + /** + * 清空状态 + * Clear the state + + * [AUTO-TRANSLATED:6aadbd77] + */ + void clear() { + _started = false; + _ticker.resetTime(); + _pkt_sort_cache_map.clear(); + } + + /** + * 获取排序缓存长度 + * Get the length of the sorting cache + + * [AUTO-TRANSLATED:8e05a703] + */ + size_t getJitterSize() const { return _pkt_sort_cache_map.size(); } + + /** + * 输入并排序 + * @param seq 序列号 + * @param packet 包负载 + * Input and sort + * @param seq Sequence number + * @param packet Packet payload + + * [AUTO-TRANSLATED:0fbf096e] + */ + void sortPacket(SEQ seq, T packet) { + _latest_seq = seq; + if (!_started) { + // 记录第一个seq [AUTO-TRANSLATED:410c831f] + // Record the first seq + _started = true; + _last_seq_out = seq - 1; + } + auto next_seq = static_cast(_last_seq_out + 1); + if (seq == next_seq) { + // 收到下一个seq [AUTO-TRANSLATED:44960fea] + // Receive the next seq + output(seq, std::move(packet)); + // 清空连续包列表 [AUTO-TRANSLATED:fdaafd3b] + // Clear the continuous packet list + flushPacket(); + _pkt_drop_cache_map.clear(); + return; + } + + if (seq < next_seq && !mayLooped(next_seq, seq)) { + // 无回环风险, 缓存seq回退包 [AUTO-TRANSLATED:4200dd1b] + // No loop risk, cache seq rollback packets + _pkt_drop_cache_map.emplace(seq, std::move(packet)); + if (_pkt_drop_cache_map.size() > _max_distance || _ticker.elapsedTime() > _max_buffer_ms) { + // seq回退包太多,可能源端重置seq计数器,这部分数据需要输出 [AUTO-TRANSLATED:d31aead7] + // Too many seq rollback packets, the source may reset the seq counter, this part of data needs to be output + forceFlush(next_seq); + // 旧的seq计数器的数据清空后把新seq计数器的数据赋值给排序列队 [AUTO-TRANSLATED:f69f864c] + // After clearing the data of the old seq counter, assign the data of the new seq counter to the sorting queue + _pkt_sort_cache_map = std::move(_pkt_drop_cache_map); + popIterator(_pkt_sort_cache_map.begin()); + } + return; + } + _pkt_sort_cache_map.emplace(seq, std::move(packet)); + + if (needForceFlush(seq)) { + forceFlush(next_seq); + } + } + + void flush() { + if (!_pkt_sort_cache_map.empty()) { + forceFlush(static_cast(_last_seq_out + 1)); + _pkt_sort_cache_map.clear(); + } + } + + void setParams(size_t max_buffer_size, size_t max_buffer_ms, size_t max_distance) { + _max_buffer_size = max_buffer_size; + _max_buffer_ms = max_buffer_ms; + _max_distance = max_distance; + } + +private: + SEQ distance(SEQ seq) { + SEQ ret; + auto next_seq = static_cast(_last_seq_out + 1); + if (seq > next_seq) { + ret = seq - next_seq; + } else { + ret = next_seq - seq; + } + if (ret > SEQ_MAX >> 1) { + return SEQ_MAX - ret; + } + return ret; + } + + bool needForceFlush(SEQ seq) { + return _pkt_sort_cache_map.size() > _max_buffer_size || distance(seq) > _max_distance || _ticker.elapsedTime() > _max_buffer_ms; + } + + void forceFlush(SEQ next_seq) { + if (_pkt_sort_cache_map.empty()) { + return; + } + // 寻找距离比next_seq大的最近的seq [AUTO-TRANSLATED:d2de6f5b] + // Find the nearest seq that is greater than next_seq + auto it = _pkt_sort_cache_map.lower_bound(next_seq); + if (it == _pkt_sort_cache_map.end()) { + // 没有比next_seq更大的seq,应该是回环时丢包导致 [AUTO-TRANSLATED:d0d6970b] + // There is no seq greater than next_seq, it should be caused by packet loss during loopback + it = _pkt_sort_cache_map.begin(); + } + // 丢包无法恢复,把这个包当做next_seq [AUTO-TRANSLATED:2d8c0b9e] + // Packet loss cannot be recovered, treat this packet as next_seq + popIterator(it); + // 清空连续包列表 [AUTO-TRANSLATED:fdaafd3b] + // Clear the continuous packet list + flushPacket(); + // 删除距离next_seq太大的包 [AUTO-TRANSLATED:9e774c5e] + // Delete packets that are too far away from next_seq + for (auto it = _pkt_sort_cache_map.begin(); it != _pkt_sort_cache_map.end();) { + if (distance(it->first) > _max_distance) { + it = _pkt_sort_cache_map.erase(it); + } else { + ++it; + } + } + } + + bool mayLooped(SEQ last_seq, SEQ now_seq) { return last_seq > SEQ_MAX - _max_distance || now_seq < _max_distance; } + + void flushPacket() { + if (_pkt_sort_cache_map.empty()) { + return; + } + auto next_seq = static_cast(_last_seq_out + 1); + auto it = _pkt_sort_cache_map.lower_bound(next_seq); + if (!mayLooped(next_seq, next_seq)) { + // 无回环风险, 清空 < next_seq的值 [AUTO-TRANSLATED:10c77bf9] + // No loop risk, clear values less than next_seq + it = _pkt_sort_cache_map.erase(_pkt_sort_cache_map.begin(), it); + } + + while (it != _pkt_sort_cache_map.end()) { + // 找到下一个包 [AUTO-TRANSLATED:8e20ab9f] + // Find the next packet + if (it->first == static_cast(_last_seq_out + 1)) { + it = popIterator(it); + continue; + } + break; + } + } + + iterator popIterator(iterator it) { + output(it->first, std::move(it->second)); + return _pkt_sort_cache_map.erase(it); + } + + void output(SEQ seq, T packet) { + auto next_seq = static_cast(_last_seq_out + 1); + if (seq != next_seq) { + WarnL << "packet dropped: " << next_seq << " -> " << static_cast(seq - 1) + << ", latest seq: " << _latest_seq + << ", jitter buffer size: " << _pkt_sort_cache_map.size() + << ", jitter buffer ms: " << _ticker.elapsedTime(); + } + _last_seq_out = seq; + _cb(seq, std::move(packet)); + _ticker.resetTime(); + } + +private: + bool _started = false; + // 排序缓存最大保存数据长度,单位毫秒 [AUTO-TRANSLATED:ed217b1c] + // Maximum data length of sorting cache, unit: milliseconds + size_t _max_buffer_ms = 1000; + // 排序缓存最大保存数据个数 [AUTO-TRANSLATED:9cfa91b4] + // Maximum number of data in sorting cache + size_t _max_buffer_size = 1024; + // seq最大跳跃距离 [AUTO-TRANSLATED:bb663e41] + // Maximum seq jump distance + size_t _max_distance = 256; + // 记录上次output至今的时间 [AUTO-TRANSLATED:83e53e42] + // Record the time since the last output + toolkit::Ticker _ticker; + // 最近输入的seq [AUTO-TRANSLATED:24ca96ee] + // The most recently input seq + SEQ _latest_seq = 0; + // 下次应该输出的SEQ [AUTO-TRANSLATED:e757a4fa] + // The next SEQ to be output + SEQ _last_seq_out = 0; + // pkt排序缓存,根据seq排序 [AUTO-TRANSLATED:3787f9a6] + // pkt sorting cache, sorted by seq + std::map _pkt_sort_cache_map; + // 预丢弃包列表 [AUTO-TRANSLATED:67e57ebc] + // Pre-discard packet list + std::map _pkt_drop_cache_map; + // 回调 [AUTO-TRANSLATED:03bad27d] + // Callback + std::function _cb; +}; + +class RtpTrack : public PacketSortor { +public: + class BadRtpException : public std::invalid_argument { + public: + template + BadRtpException(Type &&type) : invalid_argument(std::forward(type)) {} + }; + + RtpTrack(); + + void clear(); + uint32_t getSSRC() const; + RtpPacket::Ptr inputRtp(TrackType type, int sample_rate, uint8_t *ptr, size_t len); + void setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms); + void setPayloadType(uint8_t pt); + +protected: + virtual void onRtpSorted(RtpPacket::Ptr rtp) {} + virtual void onBeforeRtpSorted(const RtpPacket::Ptr &rtp) {} + +private: + bool _disable_ntp = false; + uint8_t _pt = 0xFF; + uint32_t _ssrc = 0; + toolkit::Ticker _ssrc_alive; + NtpStamp _ntp_stamp; +}; + +class RtpTrackImp : public RtpTrack{ +public: + using OnSorted = std::function; + using BeforeSorted = std::function; + + void setOnSorted(OnSorted cb); + void setBeforeSorted(BeforeSorted cb); + +protected: + void onRtpSorted(RtpPacket::Ptr rtp) override; + void onBeforeRtpSorted(const RtpPacket::Ptr &rtp) override; + +private: + OnSorted _on_sorted; + BeforeSorted _on_before_sorted; +}; + +template +class RtpMultiReceiver { +public: + RtpMultiReceiver() { + int index = 0; + for (auto &track : _track) { + track.setOnSorted([this, index](RtpPacket::Ptr rtp) { + onRtpSorted(std::move(rtp), index); + }); + track.setBeforeSorted([this, index](const RtpPacket::Ptr &rtp) { + onBeforeRtpSorted(rtp, index); + }); + ++index; + } + } + + virtual ~RtpMultiReceiver() = default; + + /** + * 输入数据指针生成并排序rtp包 + * @param index track下标索引 + * @param type track类型 + * @param samplerate rtp时间戳基准时钟,视频为90000,音频为采样率 + * @param ptr rtp数据指针 + * @param len rtp数据指针长度 + * @return 解析成功返回true + * Generate and sort rtp packets from input data pointer + * @param index Track index + * @param type Track type + * @param samplerate RTP timestamp base clock, 90000 for video, sample rate for audio + * @param ptr RTP data pointer + * @param len RTP data pointer length + * @return Return true if parsing is successful + + * [AUTO-TRANSLATED:4ec12e4a] + */ + bool handleOneRtp(int index, TrackType type, int sample_rate, uint8_t *ptr, size_t len) { + assert(index < kCount && index >= 0); + return _track[index].inputRtp(type, sample_rate, ptr, len).operator bool(); + } + + /** + * 设置ntp时间戳,在收到rtcp sender report时设置 + * 如果rtp_stamp/sample_rate/ntp_stamp_ms都为0,那么采用rtp时间戳为ntp时间戳 + * @param index track下标索引 + * @param rtp_stamp rtp时间戳 + * @param ntp_stamp_ms ntp时间戳 + * Set ntp timestamp, set when receiving rtcp sender report + * If rtp_stamp/sample_rate/ntp_stamp_ms are all 0, then use rtp timestamp as ntp timestamp + * @param index Track index + * @param rtp_stamp RTP timestamp + * @param ntp_stamp_ms NTP timestamp + + * [AUTO-TRANSLATED:1e50904e] + */ + void setNtpStamp(int index, uint32_t rtp_stamp, uint64_t ntp_stamp_ms) { + assert(index < kCount && index >= 0); + _track[index].setNtpStamp(rtp_stamp, ntp_stamp_ms); + } + + void setPayloadType(int index, uint8_t pt){ + assert(index < kCount && index >= 0); + _track[index].setPayloadType(pt); + } + + void clear() { + for (auto &track : _track) { + track.clear(); + } + } + + size_t getJitterSize(int index) const { + assert(index < kCount && index >= 0); + return _track[index].getJitterSize(); + } + + uint32_t getSSRC(int index) const { + assert(index < kCount && index >= 0); + return _track[index].getSSRC(); + } + +protected: + /** + * rtp数据包排序后输出 + * @param rtp rtp数据包 + * @param track_index track索引 + * Output rtp data packets after sorting + * @param rtp RTP data packet + * @param track_index Track index + + * [AUTO-TRANSLATED:55022da9] + */ + virtual void onRtpSorted(RtpPacket::Ptr rtp, int index) {} + + /** + * 解析出rtp但还未排序 + * @param rtp rtp数据包 + * @param track_index track索引 + * RTP data packet parsed but not yet sorted + * @param rtp RTP data packet + * @param track_index Track index + + * [AUTO-TRANSLATED:c1636911] + */ + virtual void onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int index) {} + +private: + RtpTrackImp _track[kCount]; +}; + +using RtpReceiver = RtpMultiReceiver<2>; + +}//namespace mediakit + + +#endif //ZLMEDIAKIT_RTPRECEIVER_H diff --git a/MediaServer/Rtsp/Rtsp.cpp b/MediaServer/Rtsp/Rtsp.cpp new file mode 100644 index 0000000..19d8da5 --- /dev/null +++ b/MediaServer/Rtsp/Rtsp.cpp @@ -0,0 +1,722 @@ +/* + * 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 +#include +#include +#include "Rtsp.h" +#include "Common/Parser.h" +#include "Common/config.h" +#include "Network/Socket.h" +#include "Extension/Factory.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +int RtpPayload::getClockRate(int pt) { + switch (pt) { +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return clock_rate; + RTP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return 90000; + } +} + +TrackType RtpPayload::getTrackType(int pt) { + switch (pt) { +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return type; + RTP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return TrackInvalid; + } +} + +int RtpPayload::getAudioChannel(int pt) { + switch (pt) { +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return channel; + RTP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return 1; + } +} + +const char *RtpPayload::getName(int pt) { + switch (pt) { +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; + RTP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return "unknown payload type"; + } +} + +CodecId RtpPayload::getCodecId(int pt) { + switch (pt) { +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return codec_id; + RTP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return CodecInvalid; + } +} + +static void getAttrSdp(const multimap &attr, _StrPrinter &printer) { + const map::value_type *ptr = nullptr; + for (auto &pr : attr) { + if (pr.first == "control") { + ptr = ≺ + continue; + } + if (pr.second.empty()) { + printer << "a=" << pr.first << "\r\n"; + } else { + printer << "a=" << pr.first << ":" << pr.second << "\r\n"; + } + } + if (ptr) { + printer << "a=" << ptr->first << ":" << ptr->second << "\r\n"; + } +} + +string SdpTrack::getName() const { + switch (_pt) { +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; + RTP_PT_MAP(SWITCH_CASE) +#undef SWITCH_CASE + default: return _codec; + } +} + +string SdpTrack::getControlUrl(const string &base_url) const { + if (_control.find("://") != string::npos) { + // 以rtsp://开头 [AUTO-TRANSLATED:293b3f8c] + // Starts with rtsp:// + return _control; + } + return base_url + "/" + _control; +} + +string SdpTrack::toString(uint16_t port) const { + _StrPrinter _printer; + switch (_type) { + case TrackTitle: { + TitleSdp title(_duration); + _printer << title.getSdp(); + break; + } + case TrackAudio: + case TrackVideo: { + if (_type == TrackAudio) { + _printer << "m=audio " << port << " RTP/AVP " << _pt << "\r\n"; + } else { + _printer << "m=video " << port << " RTP/AVP " << _pt << "\r\n"; + } + if (!_b.empty()) { + _printer << "b=" << _b << "\r\n"; + } + getAttrSdp(_attr, _printer); + break; + } + default: break; + } + return std::move(_printer); +} + +static TrackType toTrackType(const string &str) { + if (str == "") { + return TrackTitle; + } + + if (str == "video") { + return TrackVideo; + } + + if (str == "audio") { + return TrackAudio; + } + + return TrackInvalid; +} + +void SdpParser::load(const string &sdp) { + { + _track_vec.clear(); + SdpTrack::Ptr track = std::make_shared(); + track->_type = TrackTitle; + _track_vec.emplace_back(track); + + auto lines = split(sdp, "\n"); + for (auto &line : lines) { + trim(line); + if (line.size() < 2 || line[1] != '=') { + continue; + } + char opt = line[0]; + string opt_val = line.substr(2); + switch (opt) { + case 't': + track->_t = opt_val; + break; + case 'b': + track->_b = opt_val; + break; + case 'm': { + track = std::make_shared(); + int pt, port, port_count; + char rtp[16] = {0}, type[16]; + if (4 == sscanf(opt_val.data(), " %15[^ ] %d %15[^ ] %d", type, &port, rtp, &pt) || + 5 == sscanf(opt_val.data(), " %15[^ ] %d/%d %15[^ ] %d", type, &port, &port_count, rtp, &pt)) { + track->_pt = pt; + track->_samplerate = RtpPayload::getClockRate(pt); + track->_channel = RtpPayload::getAudioChannel(pt); + track->_type = toTrackType(type); + track->_port = port; + _track_vec.emplace_back(track); + } + break; + } + case 'a': { + string attr = findSubString(opt_val.data(), nullptr, ":"); + if (attr.empty()) { + track->_attr.emplace(opt_val, ""); + } else { + track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); + } + break; + } + default: track->_other[opt] = opt_val; break; + } + } + } + + for (auto &track_ptr : _track_vec) { + auto &track = *track_ptr; + auto it = track._attr.find("range"); + if (it != track._attr.end()) { + char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 }; + int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end); + if (3 == ret || 2 == ret) { + if (strcmp(start, "now") == 0) { + strcpy(start, "0"); + } + track._start = (float)atof(start); + track._end = (float)atof(end); + track._duration = track._end - track._start; + } + } + + for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) { + auto &rtpmap = it->second; + int pt, samplerate, channel; + char codec[16] = { 0 }; + + sscanf(rtpmap.data(), "%d", &pt); + if (track._pt != pt && track._pt != 0xff) { + // pt不匹配 [AUTO-TRANSLATED:ce7abb0a] + // pt mismatch + it = track._attr.erase(it); + continue; + } + if (4 == sscanf(rtpmap.data(), "%d %15[^/]/%d/%d", &pt, codec, &samplerate, &channel)) { + track._codec = codec; + track._samplerate = samplerate; + track._channel = channel; + } else if (3 == sscanf(rtpmap.data(), "%d %15[^/]/%d", &pt, codec, &samplerate)) { + track._pt = pt; + track._codec = codec; + track._samplerate = samplerate; + } + ++it; + } + + for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) { + auto &fmtp = it->second; + int pt; + sscanf(fmtp.data(), "%d", &pt); + if (track._pt != pt && track._pt != 0xff) { + // pt不匹配 [AUTO-TRANSLATED:ce7abb0a] + // pt mismatch + it = track._attr.erase(it); + continue; + } + track._fmtp = findSubString(fmtp.data(), " ", nullptr); + ++it; + } + + it = track._attr.find("control"); + if (it != track._attr.end()) { + track._control = it->second; + } + + if (!track._samplerate && track._type == TrackVideo) { + // 未设置视频采样率时,赋值为90000 [AUTO-TRANSLATED:416c4f0f] + // If video sampling rate is not set, assign it to 90000 + track._samplerate = 90000; + } else if (!track._samplerate && track._type == TrackAudio) { + // some rtsp sdp no sample rate but has fmt config to parser get sample rate + auto t = Factory::getTrackBySdp(track_ptr); + if (t) { + track._samplerate = std::static_pointer_cast(t)->getAudioSampleRate(); + } + } + } +} + +bool SdpParser::available() const { + return getTrack(TrackAudio) || getTrack(TrackVideo); +} + +SdpTrack::Ptr SdpParser::getTrack(TrackType type) const { + for (auto &track : _track_vec) { + if (track->_type == type) { + return track; + } + } + return nullptr; +} + +vector SdpParser::getAvailableTrack() const { + vector ret; + bool audio_added = false; + bool video_added = false; + for (auto &track : _track_vec) { + if (track->_type == TrackAudio) { + if (!audio_added) { + ret.emplace_back(track); + audio_added = true; + } + continue; + } + + if (track->_type == TrackVideo) { + if (!video_added) { + ret.emplace_back(track); + video_added = true; + } + continue; + } + } + return ret; +} + +string SdpParser::toString() const { + string title, audio, video; + for (auto &track : _track_vec) { + switch (track->_type) { + case TrackTitle: { + title = track->toString(); + break; + } + case TrackVideo: { + video = track->toString(); + break; + } + case TrackAudio: { + audio = track->toString(); + break; + } + default: break; + } + } + return title + video + audio; +} + +std::string SdpParser::getControlUrl(const std::string &url) const { + auto title_track = getTrack(TrackTitle); + if (title_track && title_track->_control.find("://") != string::npos) { + // 以rtsp://开头 [AUTO-TRANSLATED:293b3f8c] + // Starts with rtsp:// + return title_track->_control; + } + return url; +} + +template +class PortManager : public std::enable_shared_from_this> { +public: + PortManager() { + static auto func = [](const string &str, int index) { + uint16_t port[] = { 30000, 35000 }; + sscanf(str.data(), "%" SCNu16 "-%" SCNu16, port, port + 1); + return port[index]; + }; + GET_CONFIG_FUNC(uint16_t, s_min_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 0); }); + GET_CONFIG_FUNC(uint16_t, s_max_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 1); }); + assert(s_max_port >= s_min_port + 36 - 1); + setRange((s_min_port + 1) / 2, s_max_port / 2); + } + + static PortManager &Instance() { + static auto instance = std::make_shared(); + return *instance; + } + + void makeSockPair(std::pair &pair, const string &local_ip, bool re_use_port, bool is_udp) { + auto sock_pair = getPortPair(); + if (!sock_pair) { + throw runtime_error("none reserved port in pool"); + } + makeSockPair_l(sock_pair, pair, local_ip, re_use_port, is_udp); + + // 确保udp和tcp模式都能打开 [AUTO-TRANSLATED:dcb46232] + // Ensure both udp and tcp modes are open + auto new_pair = std::make_pair(Socket::createSocket(), Socket::createSocket()); + makeSockPair_l(sock_pair, new_pair, local_ip, re_use_port, !is_udp); + } + + void makeSockPair_l(const std::shared_ptr &sock_pair, std::pair &pair, const string &local_ip, bool re_use_port, bool is_udp) { + auto &sock0 = pair.first; + auto &sock1 = pair.second; + if (is_udp) { + if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) { + // 分配端口失败 [AUTO-TRANSLATED:59ecd25d] + // Port allocation failed + throw runtime_error("open udp socket[0] failed"); + } + + if (!sock1->bindUdpSock(2 * *sock_pair + 1, local_ip.data(), re_use_port)) { + // 分配端口失败 [AUTO-TRANSLATED:59ecd25d] + // Port allocation failed + throw runtime_error("open udp socket[1] failed"); + } + + auto on_cycle = [sock_pair](Socket::Ptr &, std::shared_ptr &) {}; + // udp socket没onAccept事件,设置该回调,目的是为了在销毁socket时,回收对象 [AUTO-TRANSLATED:ee91256f] + // UDP socket has no onAccept event, set this callback to recycle the object when destroying the socket + sock0->setOnAccept(on_cycle); + sock1->setOnAccept(on_cycle); + } else { + if (!sock0->listen(2 * *sock_pair, local_ip.data())) { + // 分配端口失败 [AUTO-TRANSLATED:59ecd25d] + // Port allocation failed + throw runtime_error("listen tcp socket[0] failed"); + } + + if (!sock1->listen(2 * *sock_pair + 1, local_ip.data())) { + // 分配端口失败 [AUTO-TRANSLATED:59ecd25d] + // Port allocation failed + throw runtime_error("listen tcp socket[1] failed"); + } + + auto on_cycle = [sock_pair](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {}; + // udp socket没onAccept事件,设置该回调,目的是为了在销毁socket时,回收对象 [AUTO-TRANSLATED:ee91256f] + // UDP socket has no onAccept event, set this callback to recycle the object when destroying the socket + sock0->setOnRead(on_cycle); + sock1->setOnRead(on_cycle); + } + } + +private: + void setRange(uint16_t start_pos, uint16_t end_pos) { + std::mt19937 rng(std::random_device {}()); + lock_guard lck(_pool_mtx); + auto it = _port_pair_pool.begin(); + while (start_pos < end_pos) { + // 随机端口排序,防止重启后导致分配的端口重复 [AUTO-TRANSLATED:b622db1c] + // Randomly sort ports to prevent duplicate port allocation after restart + _port_pair_pool.insert(it, start_pos++); + it = _port_pair_pool.begin() + (rng() % (1 + _port_pair_pool.size())); + } + } + + std::shared_ptr getPortPair() { + lock_guard lck(_pool_mtx); + if (_port_pair_pool.empty()) { + return nullptr; + } + auto pos = _port_pair_pool.front(); + _port_pair_pool.pop_front(); + InfoL << "got port from pool:" << 2 * pos << "-" << 2 * pos + 1; + + weak_ptr weak_self = this->shared_from_this(); + std::shared_ptr ret(new uint16_t(pos), [weak_self, pos](uint16_t *ptr) { + delete ptr; + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + InfoL << "return port to pool:" << 2 * pos << "-" << 2 * pos + 1; + // 回收端口号 [AUTO-TRANSLATED:646a5284] + // Recycle port number + lock_guard lck(strong_self->_pool_mtx); + strong_self->_port_pair_pool.emplace_back(pos); + }); + return ret; + } + +private: + recursive_mutex _pool_mtx; + deque _port_pair_pool; +}; + +void makeSockPair(std::pair &pair, const string &local_ip, bool re_use_port, bool is_udp) { + int try_count = 0; + while (true) { + try { + // udp和tcp端口池使用相同算法和范围分配,但是互不相干 [AUTO-TRANSLATED:86200c72] + // UDP and TCP port pools use the same algorithm and range for allocation, but are independent of each other + if (is_udp) { + PortManager<0>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp); + } else { + PortManager<1>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp); + } + break; + } catch (exception &ex) { + if (++try_count == 3) { + throw; + } + WarnL << "open socket failed:" << ex.what() << ", retry: " << try_count; + } + } +} + +string printSSRC(uint32_t ui32Ssrc) { + char tmp[9] = { 0 }; + ui32Ssrc = htonl(ui32Ssrc); + uint8_t *pSsrc = (uint8_t *)&ui32Ssrc; + for (int i = 0; i < 4; i++) { + sprintf(tmp + 2 * i, "%02X", pSsrc[i]); + } + return tmp; +} + +bool getSSRC(const char *data, size_t data_len, uint32_t &ssrc) { + if (data_len < 12) { + return false; + } + uint32_t *ssrc_ptr = (uint32_t *)(data + 8); + ssrc = ntohl(*ssrc_ptr); + return true; +} + +bool isRtp(const char *buf, size_t size) { + if (size < 2) { + return false; + } + RtpHeader *header = (RtpHeader *)buf; + return ((header->pt < 64) || (header->pt >= 96)) && header->version == RtpPacket::kRtpVersion; +} + +bool isRtcp(const char *buf, size_t size) { + if (size < 2) { + return false; + } + RtpHeader *header = (RtpHeader *)buf; + return ((header->pt >= 64) && (header->pt < 96)); +} + +Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved) { + auto rtp_tcp = BufferRaw::create(); + rtp_tcp->setCapacity(RtpPacket::kRtpTcpHeaderSize); + rtp_tcp->setSize(RtpPacket::kRtpTcpHeaderSize); + auto ptr = rtp_tcp->data(); + ptr[0] = '$'; + ptr[1] = interleaved; + ptr[2] = (size >> 8) & 0xFF; + ptr[3] = size & 0xFF; + return rtp_tcp; +} + +#define AV_RB16(x) ((((const uint8_t *)(x))[0] << 8) | ((const uint8_t *)(x))[1]) + +size_t RtpHeader::getCsrcSize() const { + // 每个csrc占用4字节 [AUTO-TRANSLATED:6237ca37] + // Each csrc occupies 4 bytes + return csrc << 2; +} + +uint8_t *RtpHeader::getCsrcData() { + if (!csrc) { + return nullptr; + } + return &payload; +} + +size_t RtpHeader::getExtSize() const { + // rtp有ext [AUTO-TRANSLATED:d5d9af4f] + // RTP has ext + if (!ext) { + return 0; + } + auto ext_ptr = &payload + getCsrcSize(); + // uint16_t reserved = AV_RB16(ext_ptr); + // 每个ext占用4字节 [AUTO-TRANSLATED:93e9b453] + // Each ext occupies 4 bytes + return AV_RB16(ext_ptr + 2) << 2; +} + +uint16_t RtpHeader::getExtReserved() const { + // rtp有ext [AUTO-TRANSLATED:d5d9af4f] + // RTP has ext + if (!ext) { + return 0; + } + auto ext_ptr = &payload + getCsrcSize(); + return AV_RB16(ext_ptr); +} + +uint8_t *RtpHeader::getExtData() { + if (!ext) { + return nullptr; + } + auto ext_ptr = &payload + getCsrcSize(); + // 多出的4个字节分别为reserved、ext_len [AUTO-TRANSLATED:070138f4] + // The extra 4 bytes are reserved, ext_len + return ext_ptr + 4; +} + +size_t RtpHeader::getPayloadOffset() const { + // 有ext时,还需要忽略reserved、ext_len 4个字节 [AUTO-TRANSLATED:3e222997] + // When there is ext, you also need to ignore the reserved, ext_len 4 bytes + return getCsrcSize() + (ext ? (4 + getExtSize()) : 0); +} + +uint8_t *RtpHeader::getPayloadData() { + return &payload + getPayloadOffset(); +} + +size_t RtpHeader::getPaddingSize(size_t rtp_size) const { + if (!padding) { + return 0; + } + auto end = (uint8_t *)this + rtp_size - 1; + return *end; +} + +ssize_t RtpHeader::getPayloadSize(size_t rtp_size) const { + auto invalid_size = getPayloadOffset() + getPaddingSize(rtp_size); + return (ssize_t)rtp_size - invalid_size - RtpPacket::kRtpHeaderSize; +} + +string RtpHeader::dumpString(size_t rtp_size) const { + _StrPrinter printer; + printer << "version:" << (int)version << "\r\n"; + printer << "padding:" << getPaddingSize(rtp_size) << "\r\n"; + printer << "ext:" << getExtSize() << "\r\n"; + printer << "csrc:" << getCsrcSize() << "\r\n"; + printer << "mark:" << (int)mark << "\r\n"; + printer << "pt:" << (int)pt << "\r\n"; + printer << "seq:" << ntohs(seq) << "\r\n"; + printer << "stamp:" << ntohl(stamp) << "\r\n"; + printer << "ssrc:" << ntohl(ssrc) << "\r\n"; + printer << "rtp size:" << rtp_size << "\r\n"; + printer << "payload offset:" << getPayloadOffset() << "\r\n"; + printer << "payload size:" << getPayloadSize(rtp_size) << "\r\n"; + return std::move(printer); +} + +/////////////////////////////////////////////////////////////////////// + +RtpHeader *RtpPacket::getHeader() { + // 需除去rtcp over tcp 4个字节长度 [AUTO-TRANSLATED:936f6f5b] + // Need to remove the rtcp over tcp 4 byte length + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); +} + +const RtpHeader *RtpPacket::getHeader() const { + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); +} + +string RtpPacket::dumpString() const { + return ((RtpPacket *)this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); +} + +uint16_t RtpPacket::getSeq() const { + return ntohs(getHeader()->seq); +} + +uint32_t RtpPacket::getStamp() const { + return ntohl(getHeader()->stamp); +} + +uint64_t RtpPacket::getStampMS(bool ntp) const { + return ntp ? ntp_stamp : getStamp() * uint64_t(1000) / sample_rate; +} + +uint32_t RtpPacket::getSSRC() const { + return ntohl(getHeader()->ssrc); +} + +uint8_t *RtpPacket::getPayload() { + return getHeader()->getPayloadData(); +} + +size_t RtpPacket::getPayloadSize() const { + // 需除去rtcp over tcp 4个字节长度 [AUTO-TRANSLATED:936f6f5b] + // Need to remove the rtcp over tcp 4 byte length + return getHeader()->getPayloadSize(size() - kRtpTcpHeaderSize); +} + +RtpPacket::Ptr RtpPacket::create() { +#if 0 + static ResourcePool packet_pool; + static onceToken token([]() { + packet_pool.setSize(1024); + }); + auto ret = packet_pool.obtain2(); + ret->setSize(0); + return ret; +#else + return Ptr(new RtpPacket); +#endif +} + +/** + * 构造title类型sdp + * @param dur_sec rtsp点播时长,0代表直播,单位秒 + * @param header 自定义sdp描述 + * @param version sdp版本 + * Construct title type sdp + * @param dur_sec rtsp on-demand duration, 0 represents live broadcast, unit is seconds + * @param header Custom sdp description + * @param version sdp version + + * [AUTO-TRANSLATED:a548fc69] + */ + +TitleSdp::TitleSdp(float dur_sec, const std::map &header, int version) + : Sdp(0, 0) { + _printer << "v=" << version << "\r\n"; + + if (!header.empty()) { + for (auto &pr : header) { + _printer << pr.first << "=" << pr.second << "\r\n"; + } + } else { + _printer << "o=- 0 0 IN IP4 0.0.0.0\r\n"; + _printer << "s=Streamed by " << kServerName << "\r\n"; + _printer << "c=IN IP4 0.0.0.0\r\n"; + _printer << "t=0 0\r\n"; + } + + if (dur_sec <= 0) { + // 直播 [AUTO-TRANSLATED:079c0cbc] + // Live broadcast + _printer << "a=range:npt=now-\r\n"; + } else { + // 点播 [AUTO-TRANSLATED:f0b0f74a] + // On-demand + _dur_sec = dur_sec; + _printer << "a=range:npt=0-" << dur_sec << "\r\n"; + } + _printer << "a=control:*\r\n"; +} + +} // namespace mediakit + +namespace toolkit { +StatisticImp(mediakit::RtpPacket); +} \ No newline at end of file diff --git a/MediaServer/Rtsp/Rtsp.h b/MediaServer/Rtsp/Rtsp.h new file mode 100644 index 0000000..a46e492 --- /dev/null +++ b/MediaServer/Rtsp/Rtsp.h @@ -0,0 +1,390 @@ +/* + * 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 RTSP_RTSP_H_ +#define RTSP_RTSP_H_ + +#include "Common/macros.h" +#include "Extension/Frame.h" +#include "Network/Socket.h" +#include +#include +#include +#include + +namespace mediakit { + +namespace Rtsp { +typedef enum { + RTP_Invalid = -1, + RTP_TCP = 0, + RTP_UDP = 1, + RTP_MULTICAST = 2, +} eRtpType; + +#define RTP_PT_MAP(XX) \ + XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ + XX(GSM, TrackAudio, 3, 8000, 1, CodecInvalid) \ + XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ + XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ + XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ + XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ + XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ + XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ + XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ + XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ + XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ + XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ + XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ + XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ + XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ + XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ + XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ + XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ + XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ + XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ + XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ + XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ + XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ + XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) + +typedef enum { +#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_##name = value, + RTP_PT_MAP(ENUM_DEF) +#undef ENUM_DEF + PT_MAX + = 128 +} PayloadType; + +}; // namespace Rtsp + +#pragma pack(push, 1) + +class RtpHeader { +public: +#if __BYTE_ORDER == __BIG_ENDIAN + // 版本号,固定为2 [AUTO-TRANSLATED:08ed82fa] + // Version number, fixed to 2 + uint32_t version : 2; + // padding + uint32_t padding : 1; + // 扩展 [AUTO-TRANSLATED:6189584a] + // Extension + uint32_t ext : 1; + // csrc + uint32_t csrc : 4; + // mark + uint32_t mark : 1; + // 负载类型 [AUTO-TRANSLATED:09b49a77] + // Payload type + uint32_t pt : 7; +#else + // csrc + uint32_t csrc : 4; + // 扩展 [AUTO-TRANSLATED:6189584a] + // Extension + uint32_t ext : 1; + // padding + uint32_t padding : 1; + // 版本号,固定为2 [AUTO-TRANSLATED:08ed82fa] + // Version number, fixed to 2 + uint32_t version : 2; + // 负载类型 [AUTO-TRANSLATED:09b49a77] + // Payload type + uint32_t pt : 7; + // mark + uint32_t mark : 1; +#endif + // 序列号 [AUTO-TRANSLATED:fe421425] + // Sequence number + uint32_t seq : 16; + // 时间戳 [AUTO-TRANSLATED:516f43a9] + // Timestamp + uint32_t stamp; + // ssrc + uint32_t ssrc; + // 负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) [AUTO-TRANSLATED:fcd87b19] + // Payload, if csrc and ext exist, the front is 4 * csrc + (4 + 4 * ext_len) + uint8_t payload; + +public: + // 返回csrc字段字节长度 [AUTO-TRANSLATED:2203e1db] + // Return the byte length of the csrc field + size_t getCsrcSize() const; + // 返回csrc字段首地址,不存在时返回nullptr [AUTO-TRANSLATED:2c89718a] + // Return the starting address of the csrc field, return nullptr if it does not exist + uint8_t *getCsrcData(); + + // 返回ext字段字节长度 [AUTO-TRANSLATED:487dcc4e] + // Return the byte length of the ext field + size_t getExtSize() const; + // 返回ext reserved值 [AUTO-TRANSLATED:131355c0] + // Return the ext reserved value + uint16_t getExtReserved() const; + // 返回ext段首地址,不存在时返回nullptr [AUTO-TRANSLATED:6662b24f] + // Return the starting address of the ext segment, return nullptr if it does not exist + uint8_t *getExtData(); + + // 返回有效负载指针,跳过csrc、ext [AUTO-TRANSLATED:139a5683] + // Return the valid payload pointer, skip csrc, ext + uint8_t *getPayloadData(); + // 返回有效负载总长度,不包括csrc、ext、padding [AUTO-TRANSLATED:b4ec90d4] + // Return the total length of the valid payload, excluding csrc, ext, padding + ssize_t getPayloadSize(size_t rtp_size) const; + // 打印调试信息 [AUTO-TRANSLATED:f36e06da] + // Print debug information + std::string dumpString(size_t rtp_size) const; + +private: + // 返回有效负载偏移量 [AUTO-TRANSLATED:47496d37] + // Return the valid payload offset + size_t getPayloadOffset() const; + // 返回padding长度 [AUTO-TRANSLATED:6dabd44d] + // Return the padding length + size_t getPaddingSize(size_t rtp_size) const; +}; + +#pragma pack(pop) + +// 此rtp为rtp over tcp形式,需要忽略前4个字节 [AUTO-TRANSLATED:ceb00f83] +// This rtp is in the form of rtp over tcp, the first 4 bytes need to be ignored +class RtpPacket : public toolkit::BufferRaw { +public: + using Ptr = std::shared_ptr; + enum { kRtpVersion = 2, kRtpHeaderSize = 12, kRtpTcpHeaderSize = 4 }; + + // 获取rtp头 [AUTO-TRANSLATED:41d58919] + // Get the rtp header + RtpHeader *getHeader(); + const RtpHeader *getHeader() const; + + // 打印调试信息 [AUTO-TRANSLATED:f36e06da] + // Print debug information + std::string dumpString() const; + + // 主机字节序的seq [AUTO-TRANSLATED:67c278dd] + // Host byte order seq + uint16_t getSeq() const; + uint32_t getStamp() const; + // 主机字节序的时间戳,已经转换为毫秒 [AUTO-TRANSLATED:14cdf080] + // Host byte order timestamp, converted to milliseconds + uint64_t getStampMS(bool ntp = true) const; + // 主机字节序的ssrc [AUTO-TRANSLATED:37d06f6c] + // Host byte order ssrc + uint32_t getSSRC() const; + // 有效负载,跳过csrc、ext [AUTO-TRANSLATED:e4e97453] + // Valid payload, skip csrc, ext + uint8_t *getPayload(); + // 有效负载长度,不包括csrc、ext、padding [AUTO-TRANSLATED:a93e4b08] + // Valid payload length, excluding csrc, ext, padding + size_t getPayloadSize() const; + + // 音视频类型 [AUTO-TRANSLATED:dc0fa851] + // Audio and video type + TrackType type; + // 音频为采样率,视频一般为90000 [AUTO-TRANSLATED:8bd1854b] + // Audio is the sampling rate, video is generally 90000 + uint32_t sample_rate; + // ntp时间戳 [AUTO-TRANSLATED:912cacf2] + // ntp timestamp + uint64_t ntp_stamp; + + int track_index; + + static Ptr create(); + +private: + friend class toolkit::ResourcePool_l; + RtpPacket() = default; + +private: + // 对象个数统计 [AUTO-TRANSLATED:f4a012d0] + // Object Count Statistics + toolkit::ObjectStatistic _statistic; +}; + +class RtpPayload { +public: + static int getClockRate(int pt); + static TrackType getTrackType(int pt); + static int getAudioChannel(int pt); + static const char *getName(int pt); + static CodecId getCodecId(int pt); + +private: + RtpPayload() = delete; + ~RtpPayload() = delete; +}; + +class SdpTrack { +public: + using Ptr = std::shared_ptr; + std::string _t; + std::string _b; + uint16_t _port; + + float _duration = 0; + float _start = 0; + float _end = 0; + + std::map _other; + std::multimap _attr; + + std::string toString(uint16_t port = 0) const; + std::string getName() const; + std::string getControlUrl(const std::string &base_url) const; + +public: + int _pt = 0xff; + int _channel; + int _samplerate; + TrackType _type; + std::string _codec; + std::string _fmtp; + std::string _control; + +public: + bool _inited = false; + uint8_t _interleaved = 0; + uint16_t _seq = 0; + uint32_t _ssrc = 0; + // 时间戳,单位毫秒 [AUTO-TRANSLATED:9513087f] + // Timestamp, unit: milliseconds + uint32_t _time_stamp = 0; +}; + +class SdpParser { +public: + using Ptr = std::shared_ptr; + + SdpParser() = default; + SdpParser(const std::string &sdp) { load(sdp); } + + void load(const std::string &sdp); + bool available() const; + SdpTrack::Ptr getTrack(TrackType type) const; + std::vector getAvailableTrack() const; + std::string toString() const; + std::string getControlUrl(const std::string &url) const; + +private: + std::vector _track_vec; +}; + +/** + * rtsp sdp基类 + * rtsp sdp base class + + * [AUTO-TRANSLATED:05395f48] + */ +class Sdp { +public: + using Ptr = std::shared_ptr; + + /** + * 构造sdp + * @param sample_rate 采样率 + * @param payload_type pt类型 + * Construct sdp + * @param sample_rate Sampling rate + * @param payload_type pt type + + * [AUTO-TRANSLATED:49f61fff] + */ + Sdp(uint32_t sample_rate, uint8_t payload_type) { + _sample_rate = sample_rate; + _payload_type = payload_type; + } + + virtual ~Sdp() = default; + + /** + * 获取sdp字符串 + * @return + * Get sdp string + * @return + + * [AUTO-TRANSLATED:ca0e2ad7] + */ + virtual std::string getSdp() const = 0; + + /** + * 获取pt + * @return + * Get pt + * @return + + * [AUTO-TRANSLATED:872f1b6a] + */ + uint8_t getPayloadType() const { return _payload_type; } + + /** + * 获取采样率 + * @return + * Get sampling rate + * @return + + * [AUTO-TRANSLATED:463a0075] + */ + uint32_t getSampleRate() const { return _sample_rate; } + +private: + uint8_t _payload_type; + uint32_t _sample_rate; +}; + +/** + * sdp中除音视频外的其他描述部分 + * Other description part in sdp except audio and video + + * [AUTO-TRANSLATED:3a1dad65] + */ +class TitleSdp : public Sdp { +public: + using Ptr = std::shared_ptr; + /** + * 构造title类型sdp + * @param dur_sec rtsp点播时长,0代表直播,单位秒 + * @param header 自定义sdp描述 + * @param version sdp版本 + * Construct title type sdp + * @param dur_sec rtsp on-demand duration, 0 represents live broadcast, unit: seconds + * @param header Custom sdp description + * @param version sdp version + + * [AUTO-TRANSLATED:bcd1dd5a] + */ + TitleSdp(float dur_sec = 0, const std::map &header = std::map(), int version = 0); + + std::string getSdp() const override { return _printer; } + + float getDuration() const { return _dur_sec; } + +private: + float _dur_sec = 0; + toolkit::_StrPrinter _printer; +}; + +// 创建rtp over tcp4个字节的头 [AUTO-TRANSLATED:25031f0d] +// Create 4-byte header for rtp over tcp +toolkit::Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved); +// 创建rtp-rtcp端口对 [AUTO-TRANSLATED:f8abe61b] +// Create rtp-rtcp port pair +void makeSockPair(std::pair &pair, const std::string &local_ip, bool re_use_port = false, bool is_udp = true); +// 十六进制方式打印ssrc [AUTO-TRANSLATED:c58cd95f] +// Print ssrc in hexadecimal format +std::string printSSRC(uint32_t ui32Ssrc); +bool getSSRC(const char *data, size_t data_len, uint32_t &ssrc); + +bool isRtp(const char *buf, size_t size); +bool isRtcp(const char *buf, size_t size); + +} // namespace mediakit +#endif // RTSP_RTSP_H_ diff --git a/MediaServer/Rtsp/RtspDemuxer.cpp b/MediaServer/Rtsp/RtspDemuxer.cpp new file mode 100644 index 0000000..b43a446 --- /dev/null +++ b/MediaServer/Rtsp/RtspDemuxer.cpp @@ -0,0 +1,137 @@ +/* + * 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 +#include +#include "RtpCodec.h" +#include "RtspDemuxer.h" +#include "Util/base64.h" +#include "Extension/Factory.h" + +using namespace std; + +namespace mediakit { + +void RtspDemuxer::loadSdp(const string &sdp) { + loadSdp(SdpParser(sdp)); +} + +void RtspDemuxer::loadSdp(const SdpParser &attr) { + auto tracks = attr.getAvailableTrack(); + for (auto &track : tracks) { + switch (track->_type) { + case TrackVideo: { + makeVideoTrack(track); + } + break; + case TrackAudio: { + makeAudioTrack(track); + } + break; + default: + break; + } + } + // rtsp能通过sdp立即知道有多少个track [AUTO-TRANSLATED:66a4c8d3] + // rtsp can immediately know how many tracks there are through sdp + addTrackCompleted(); + + auto titleTrack = attr.getTrack(TrackTitle); + if (titleTrack) { + _duration = titleTrack->_duration; + } +} + +float RtspDemuxer::getDuration() const { + return _duration; +} + +bool RtspDemuxer::inputRtp(const RtpPacket::Ptr &rtp) { + switch (rtp->type) { + case TrackVideo: { + if (_video_rtp_decoder) { + return _video_rtp_decoder->inputRtp(rtp, true); + } + return false; + } + case TrackAudio: { + if (_audio_rtp_decoder) { + _audio_rtp_decoder->inputRtp(rtp, false); + return false; + } + return false; + } + default: return false; + } +} + +static void setBitRate(const SdpTrack::Ptr &sdp, const Track::Ptr &track) { + if (!sdp->_b.empty()) { + int data_rate = 0; + sscanf(sdp->_b.data(), "AS:%d", &data_rate); + if (data_rate) { + track->setBitRate(data_rate * 1024); + } + } +} + +void RtspDemuxer::makeAudioTrack(const SdpTrack::Ptr &audio) { + if (_audio_rtp_decoder) { + return; + } + // 生成Track对象 [AUTO-TRANSLATED:c2f2ac3b] + // Generate Track object + _audio_track = dynamic_pointer_cast(Factory::getTrackBySdp(audio)); + if (!_audio_track) { + return; + } + setBitRate(audio, _audio_track); + // 生成RtpCodec对象以便解码rtp [AUTO-TRANSLATED:889376fd] + // Generate RtpCodec object to decode rtp + _audio_rtp_decoder = Factory::getRtpDecoderByCodecId(_audio_track->getCodecId()); + if (!_audio_rtp_decoder) { + // 找不到相应的rtp解码器,该track无效 [AUTO-TRANSLATED:1c8c5eab] + // Cannot find the corresponding rtp decoder, the track is invalid + _audio_track.reset(); + return; + } + // 设置rtp解码器代理,生成的frame写入该Track [AUTO-TRANSLATED:b2a2362e] + // Set the rtp decoder proxy, the generated frame is written to this Track + _audio_rtp_decoder->addDelegate(_audio_track); + addTrack(_audio_track); +} + +void RtspDemuxer::makeVideoTrack(const SdpTrack::Ptr &video) { + if (_video_rtp_decoder) { + return; + } + // 生成Track对象 [AUTO-TRANSLATED:c2f2ac3b] + // Generate Track object + _video_track = dynamic_pointer_cast(Factory::getTrackBySdp(video)); + if (!_video_track) { + return; + } + setBitRate(video, _video_track); + // 生成RtpCodec对象以便解码rtp [AUTO-TRANSLATED:889376fd] + // Generate RtpCodec object to decode rtp + _video_rtp_decoder = Factory::getRtpDecoderByCodecId(_video_track->getCodecId()); + if (!_video_rtp_decoder) { + // 找不到相应的rtp解码器,该track无效 [AUTO-TRANSLATED:1c8c5eab] + // Cannot find the corresponding rtp decoder, the track is invalid + _video_track.reset(); + return; + } + // 设置rtp解码器代理,生成的frame写入该Track [AUTO-TRANSLATED:b2a2362e] + // Set the rtp decoder proxy, the generated frame is written to this Track + _video_rtp_decoder->addDelegate(_video_track); + addTrack(_video_track); +} + +} /* namespace mediakit */ diff --git a/MediaServer/Rtsp/RtspDemuxer.h b/MediaServer/Rtsp/RtspDemuxer.h new file mode 100644 index 0000000..1379596 --- /dev/null +++ b/MediaServer/Rtsp/RtspDemuxer.h @@ -0,0 +1,70 @@ +/* + * 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_RTP_RTSPDEMUXER_H_ +#define SRC_RTP_RTSPDEMUXER_H_ + +#include +#include "Rtsp/RtpCodec.h" +#include "Common/MediaSink.h" + +namespace mediakit { + +class RtspDemuxer : public Demuxer { +public: + using Ptr = std::shared_ptr; + + /** + * 加载sdp + * Load sdp + + * [AUTO-TRANSLATED:235be34f] + */ + void loadSdp(const std::string &sdp); + + /** + * 开始解复用 + * @param rtp rtp包 + * @return true 代表是i帧第一个rtp包 + * Start demultiplexing + * @param rtp rtp packet + * @return true represents the first rtp packet of the i-frame + + * [AUTO-TRANSLATED:116d3186] + */ + bool inputRtp(const RtpPacket::Ptr &rtp); + + /** + * 获取节目总时长 + * @return 节目总时长,单位秒 + * Get the total duration of the program + * @return Total duration of the program, in seconds + + + * [AUTO-TRANSLATED:6b2ec56c] + */ + float getDuration() const; + +private: + void makeAudioTrack(const SdpTrack::Ptr &audio); + void makeVideoTrack(const SdpTrack::Ptr &video); + void loadSdp(const SdpParser &parser); + +private: + float _duration = 0; + AudioTrack::Ptr _audio_track; + VideoTrack::Ptr _video_track; + RtpCodec::Ptr _audio_rtp_decoder; + RtpCodec::Ptr _video_rtp_decoder; +}; + +} /* namespace mediakit */ + +#endif /* SRC_RTP_RTSPDEMUXER_H_ */ diff --git a/MediaServer/Rtsp/RtspMediaSource.h b/MediaServer/Rtsp/RtspMediaSource.h new file mode 100644 index 0000000..a878c13 --- /dev/null +++ b/MediaServer/Rtsp/RtspMediaSource.h @@ -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. + */ + +#ifndef SRC_RTSP_RTSPMEDIASOURCE_H_ +#define SRC_RTSP_RTSPMEDIASOURCE_H_ + +#include +#include +#include +#include +#include "Common/MediaSource.h" +#include "Common/PacketCache.h" +#include "Util/RingBuffer.h" + +#define RTP_GOP_SIZE 512 + +namespace mediakit { + +/** + * rtsp媒体源的数据抽象 + * rtsp有关键的两要素,分别是sdp、rtp包 + * 只要生成了这两要素,那么要实现rtsp推流、rtsp服务器就很简单了 + * rtsp推拉流协议中,先传递sdp,然后再协商传输方式(tcp/udp/组播),最后一直传递rtp + * Data abstraction of rtsp media source + * Rtsp has two key elements, sdp and rtp packets + * As long as these two elements are generated, it is very simple to implement rtsp push stream and rtsp server + * In the rtsp push and pull stream protocol, sdp is transmitted first, then the transmission method (tcp/udp/multicast) is negotiated, and finally rtp is continuously transmitted + + * [AUTO-TRANSLATED:e04eee56] + */ +class RtspMediaSource : public MediaSource, public toolkit::RingDelegate, private PacketCache { +public: + using Ptr = std::shared_ptr; + using RingDataType = std::shared_ptr >; + using RingType = toolkit::RingBuffer; + + /** + * 构造函数 + * @param vhost 虚拟主机名 + * @param app 应用名 + * @param stream_id 流id + * @param ring_size 可以设置固定的环形缓冲大小,0则自适应 + * Constructor + * @param vhost Virtual host name + * @param app Application name + * @param stream_id Stream id + * @param ring_size You can set a fixed ring buffer size, 0 is adaptive + + * [AUTO-TRANSLATED:5dd23423] + */ + RtspMediaSource(const MediaTuple& tuple, int ring_size = RTP_GOP_SIZE): MediaSource(RTSP_SCHEMA, tuple), _ring_size(ring_size) {} + + ~RtspMediaSource() override { + try { + flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + + /** + * 获取媒体源的环形缓冲 + * Get the ring buffer of the media source + + * [AUTO-TRANSLATED:91a762bc] + */ + const RingType::Ptr &getRing() const { + return _ring; + } + + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { + assert(_ring); + _ring->getInfoList(cb, on_change); + } + + bool broadcastMessage(const toolkit::Any &data) override { + assert(_ring); + _ring->sendMessage(data); + return true; + } + + /** + * 获取播放器个数 + * Get the number of players + + * [AUTO-TRANSLATED:a451c846] + */ + int readerCount() override { + return _ring ? _ring->readerCount() : 0; + } + + /** + * 获取该源的sdp + * Get the sdp of this source + + * [AUTO-TRANSLATED:ebc43430] + */ + const std::string &getSdp() const { + return _sdp; + } + + virtual RtspMediaSource::Ptr clone(const std::string& stream) { + return nullptr; + } + + /** + * 获取相应轨道的ssrc + * Get the ssrc of the corresponding track + + * [AUTO-TRANSLATED:d26d7f76] + */ + virtual uint32_t getSsrc(TrackType trackType) { + assert(trackType >= 0 && trackType < TrackMax); + auto &track = _tracks[trackType]; + if (!track) { + return 0; + } + return track->_ssrc; + } + + /** + * 获取相应轨道的seqence + * Get the sequence of the corresponding track + + * [AUTO-TRANSLATED:24b0ee74] + */ + virtual uint16_t getSeqence(TrackType trackType) { + assert(trackType >= 0 && trackType < TrackMax); + auto &track = _tracks[trackType]; + if (!track) { + return 0; + } + return track->_seq; + } + + /** + * 获取相应轨道的时间戳,单位毫秒 + * Get the timestamp of the corresponding track, in milliseconds + + * [AUTO-TRANSLATED:564a0794] + */ + uint32_t getTimeStamp(TrackType trackType) override; + + /** + * 更新时间戳 + * Update timestamp + + * [AUTO-TRANSLATED:8defe253] + */ + void setTimeStamp(uint32_t stamp) override; + + /** + * 设置sdp + * Set sdp + + * [AUTO-TRANSLATED:76a533c4] + */ + virtual void setSdp(const std::string &sdp); + + /** + * 输入rtp + * @param rtp rtp包 + * @param keyPos 该包是否为关键帧的第一个包 + * Input rtp + * @param rtp rtp packet + * @param keyPos Whether this packet is the first packet of a key frame + + * [AUTO-TRANSLATED:fe55afe8] + */ + void onWrite(RtpPacket::Ptr rtp, bool keyPos) override; + + void clearCache() override{ + PacketCache::clearCache(); + _ring->clearCache(); + } + +private: + /** + * 批量flush rtp包时触发该函数 + * @param rtp_list rtp包列表 + * @param key_pos 是否包含关键帧 + * Trigger this function when flushing rtp packets in batches + * @param rtp_list rtp packet list + * @param key_pos Whether it contains a key frame + + * [AUTO-TRANSLATED:612c574b] + */ + void onFlush(std::shared_ptr > rtp_list, bool key_pos) override { + // 如果不存在视频,那么就没有存在GOP缓存的意义,所以is_key一直为true确保一直清空GOP缓存 [AUTO-TRANSLATED:5818a8d8] + // If there is no video, then there is no point in having a GOP cache, so is_key is always true to ensure that the GOP cache is always cleared + _ring->write(std::move(rtp_list), _have_video ? key_pos : true); + } + +private: + bool _have_video = false; + int _ring_size; + std::string _sdp; + RingType::Ptr _ring; + SdpTrack::Ptr _tracks[TrackMax]; +}; + +} /* namespace mediakit */ + +#endif /* SRC_RTSP_RTSPMEDIASOURCE_H_ */ diff --git a/MediaServer/Rtsp/RtspMediaSourceImp.cpp b/MediaServer/Rtsp/RtspMediaSourceImp.cpp new file mode 100644 index 0000000..513e9d9 --- /dev/null +++ b/MediaServer/Rtsp/RtspMediaSourceImp.cpp @@ -0,0 +1,154 @@ +#include "RtspMediaSourceImp.h" +#include "RtspDemuxer.h" +#include "Common/config.h" +namespace mediakit { +void RtspMediaSource::setSdp(const std::string &sdp) { + SdpParser sdp_parser(sdp); + _tracks[TrackVideo] = sdp_parser.getTrack(TrackVideo); + _tracks[TrackAudio] = sdp_parser.getTrack(TrackAudio); + _have_video = (bool)_tracks[TrackVideo]; + _sdp = sdp_parser.toString(); + if (_ring) { + regist(); + } +} + +uint32_t RtspMediaSource::getTimeStamp(TrackType trackType) { + assert(trackType >= TrackInvalid && trackType < TrackMax); + if (trackType != TrackInvalid) { + // 获取某track的时间戳 [AUTO-TRANSLATED:e3fecb81] + // Get the timestamp of a track + auto &track = _tracks[trackType]; + if (track) { + return track->_time_stamp; + } + } + + // 获取所有track的最小时间戳 [AUTO-TRANSLATED:8cf9218b] + // Get the minimum timestamp of all tracks + uint32_t ret = UINT32_MAX; + for (auto &track : _tracks) { + if (track && track->_time_stamp < ret) { + ret = track->_time_stamp; + } + } + return ret; +} + +/** + * 更新时间戳 + * Update timestamp + + * [AUTO-TRANSLATED:fa02a89e] + */ +void RtspMediaSource::setTimeStamp(uint32_t stamp) { + for (auto &track : _tracks) { + if (track) { + track->_time_stamp = stamp; + } + } +} + +void RtspMediaSource::onWrite(RtpPacket::Ptr rtp, bool keyPos) { + _speed[rtp->type] += rtp->size(); + assert(rtp->type >= 0 && rtp->type < TrackMax); + auto &track = _tracks[rtp->type]; + auto stamp = rtp->getStampMS(); + bool is_video = rtp->type == TrackVideo; + // 音频总是更新,视频在关键包时更新 + if (track && ((keyPos && _have_video && is_video) || (!is_video))) { + track->_seq = rtp->getSeq(); + track->_time_stamp = rtp->getStamp() * uint64_t(1000) / rtp->sample_rate; + track->_ssrc = rtp->getSSRC(); + } + if (!_ring) { + std::weak_ptr weakSelf = std::static_pointer_cast(shared_from_this()); + auto lam = [weakSelf](int size) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + strongSelf->onReaderChanged(size); + }; + // GOP默认缓冲512组RTP包,每组RTP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTP包), [AUTO-TRANSLATED:dc09b92e] + // GOP defaults to buffering 512 groups of RTP packets, each group of RTP packets has the same timestamp (if merge writing is enabled, then each group is the RTP packet within the merge writing time), + // 每次遇到关键帧第一个RTP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开) [AUTO-TRANSLATED:db44dc72] + // Every time a key frame's first RTP packet is encountered, the GOP cache will be cleared (because there is a new key frame, which can also achieve instant playback) + _ring = std::make_shared(_ring_size, std::move(lam)); + if (!_sdp.empty()) { + regist(); + } + } + + PacketCache::inputPacket(stamp, is_video, std::move(rtp), keyPos); +} + +RtspMediaSourceImp::RtspMediaSourceImp(const MediaTuple& tuple, int ringSize): RtspMediaSource(tuple, ringSize) +{ + _demuxer = std::make_shared(); + _demuxer->setTrackListener(this); +} + +void RtspMediaSourceImp::setSdp(const std::string &strSdp) +{ + if (!getSdp().empty()) { + return; + } + _demuxer->loadSdp(strSdp); + RtspMediaSource::setSdp(strSdp); +} + +void RtspMediaSourceImp::onWrite(RtpPacket::Ptr rtp, bool key_pos) +{ + if (_all_track_ready && !_muxer->isEnabled()) { + // 获取到所有Track后,并且未开启转协议,那么不需要解复用rtp [AUTO-TRANSLATED:31cbc558] + // After getting all Tracks and not enabling protocol conversion, there is no need to demultiplex rtp + // 在关闭rtp解复用后,无法知道是否为关键帧,这样会导致无法秒开,或者开播花屏 [AUTO-TRANSLATED:279f1332] + // After closing rtp demultiplexing, it is impossible to know whether it is a key frame, which will lead to the inability to achieve instant playback or the screen will be garbled when playing + key_pos = rtp->type == TrackVideo; + } else { + // 需要解复用rtp [AUTO-TRANSLATED:0deaf9f1] + // Need to demultiplex rtp + key_pos = _demuxer->inputRtp(rtp); + } + GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy); + if (directProxy) { + // 直接代理模式才直接使用原始rtp [AUTO-TRANSLATED:afd4ae3b] + // Only the direct proxy mode directly uses the original rtp + RtspMediaSource::onWrite(std::move(rtp), key_pos); + } +} + +void RtspMediaSourceImp::setProtocolOption(const ProtocolOption &option) +{ + GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy); + // 开启直接代理模式时,rtsp直接代理,不重复产生;但是有些rtsp推流端,由于sdp中已有sps pps,rtp中就不再包括sps pps, [AUTO-TRANSLATED:1bbc0e31] + // When direct proxy mode is enabled, rtsp is directly proxied and not duplicated; however, some rtsp push stream ends, because there are already sps pps in the sdp, rtp no longer includes sps pps, + // 导致rtc无法播放,所以在rtsp推流rtc播放时,建议关闭直接代理模式 [AUTO-TRANSLATED:2c705dec] + // This leads to the inability of rtc to play, so it is recommended to turn off direct proxy mode when rtsp pushes the stream and rtc plays + _option = option; + _option.enable_rtsp = !direct_proxy; + _muxer = std::make_shared(_tuple, _demuxer->getDuration(), _option); + _muxer->setMediaListener(getListener()); + _muxer->setTrackListener(std::static_pointer_cast(shared_from_this())); + // 让_muxer对象拦截一部分事件(比如说录像相关事件) [AUTO-TRANSLATED:32320477] + // Let the _muxer object intercept some events (such as recording related events) + MediaSource::setListener(_muxer); + + for (auto &track : _demuxer->getTracks(false)) { + _muxer->addTrack(track); + track->addDelegate(_muxer); + } +} + +RtspMediaSource::Ptr RtspMediaSourceImp::clone(const std::string &stream) { + auto tuple = _tuple; + tuple.stream = stream; + auto src_imp = std::make_shared(tuple); + src_imp->setSdp(getSdp()); + src_imp->setProtocolOption(getProtocolOption()); + return src_imp; +} + +} + diff --git a/MediaServer/Rtsp/RtspMediaSourceImp.h b/MediaServer/Rtsp/RtspMediaSourceImp.h new file mode 100644 index 0000000..a1bd7b3 --- /dev/null +++ b/MediaServer/Rtsp/RtspMediaSourceImp.h @@ -0,0 +1,151 @@ +/* + * 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_RTSP_RTSPTORTMPMEDIASOURCE_H_ +#define SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ + +#include "RtspMediaSource.h" +#include "RtspDemuxer.h" +#include "Common/MultiMediaSourceMuxer.h" + +namespace mediakit { +class RtspDemuxer; +class RtspMediaSourceImp final : public RtspMediaSource, private TrackListener, public MultiMediaSourceMuxer::Listener { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param id 流id + * @param ringSize 环形缓存大小 + * Constructor + * @param vhost Virtual host + * @param app Application name + * @param id Stream id + * @param ringSize Ring buffer size + + * [AUTO-TRANSLATED:7679d212] + */ + RtspMediaSourceImp(const MediaTuple& tuple, int ringSize = RTP_GOP_SIZE); + + /** + * 设置sdp + * Set sdp + + * [AUTO-TRANSLATED:76a533c4] + */ + void setSdp(const std::string &strSdp) override; + + /** + * 输入rtp并解析 + * Input rtp and parse + + * [AUTO-TRANSLATED:778f743f] + */ + void onWrite(RtpPacket::Ptr rtp, bool key_pos) override; + + /** + * 获取观看总人数,包括(hls/rtsp/rtmp) + * Get total number of viewers, including (hls/rtsp/rtmp) + + * [AUTO-TRANSLATED:19a26d5a] + */ + int totalReaderCount() override { + return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0); + } + + /** + * 设置协议转换选项 + * Set protocol conversion options + + * [AUTO-TRANSLATED:a6a9b24a] + */ + void setProtocolOption(const ProtocolOption &option); + + const ProtocolOption &getProtocolOption() const { + return _option; + } + + /** + * _demuxer触发的添加Track事件 + * _demuxer triggered add Track event + + * [AUTO-TRANSLATED:80dbcf16] + */ + bool addTrack(const Track::Ptr &track) override { + if (_muxer) { + if (_muxer->addTrack(track)) { + track->addDelegate(_muxer); + return true; + } + } + return false; + } + + /** + * _demuxer触发的Track添加完毕事件 + * _demuxer triggered Track add complete event + + * [AUTO-TRANSLATED:939cb312] + */ + void addTrackCompleted() override { + if (_muxer) { + _muxer->addTrackCompleted(); + } + } + + void resetTracks() override { + if (_muxer) { + _muxer->resetTracks(); + } + } + + /** + * _muxer触发的所有Track就绪的事件 + * _muxer triggered all Track ready event + + * [AUTO-TRANSLATED:1d34b7e0] + */ + void onAllTrackReady() override{ + _all_track_ready = true; + } + + /** + * 设置事件监听器 + * @param listener 监听器 + * Set event listener + * @param listener Listener + + * [AUTO-TRANSLATED:d829419b] + */ + void setListener(const std::weak_ptr &listener) override{ + if (_muxer) { + // _muxer对象不能处理的事件再给listener处理 [AUTO-TRANSLATED:47858305] + // _muxer object cannot handle the event, then give it to the listener + _muxer->setMediaListener(listener); + } else { + // 未创建_muxer对象,事件全部给listener处理 [AUTO-TRANSLATED:eec04bc3] + // The _muxer object is not created, all events are given to the listener + MediaSource::setListener(listener); + } + } + + RtspMediaSource::Ptr clone(const std::string& stream) override; +private: + bool _all_track_ready = false; + ProtocolOption _option; + RtspDemuxer::Ptr _demuxer; + MultiMediaSourceMuxer::Ptr _muxer; +}; +} /* namespace mediakit */ + +#endif /* SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ */ diff --git a/MediaServer/Rtsp/RtspMediaSourceMuxer.h b/MediaServer/Rtsp/RtspMediaSourceMuxer.h new file mode 100644 index 0000000..8e4d9b7 --- /dev/null +++ b/MediaServer/Rtsp/RtspMediaSourceMuxer.h @@ -0,0 +1,92 @@ +/* + * 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_RTSPMEDIASOURCEMUXER_H +#define ZLMEDIAKIT_RTSPMEDIASOURCEMUXER_H + +#include "RtspMuxer.h" +#include "Rtsp/RtspMediaSource.h" + +namespace mediakit { + +class RtspMediaSourceMuxer final : public RtspMuxer, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + RtspMediaSourceMuxer(const MediaTuple& tuple, + const ProtocolOption &option, + const TitleSdp::Ptr &title = nullptr) : RtspMuxer(title) { + _option = option; + _media_src = std::make_shared(tuple); + getRtpRing()->setDelegate(_media_src); + } + + ~RtspMediaSourceMuxer() override { + try { + RtspMuxer::flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } + + void setListener(const std::weak_ptr &listener){ + setDelegate(listener); + _media_src->setListener(shared_from_this()); + } + + int readerCount() const{ + return _media_src->readerCount(); + } + + void setTimeStamp(uint32_t stamp){ + _media_src->setTimeStamp(stamp); + } + + void addTrackCompleted() override { + RtspMuxer::addTrackCompleted(); + _media_src->setSdp(getSdp()); + } + + void onReaderChanged(MediaSource &sender, int size) override { + _enabled = _option.rtsp_demand ? size : true; + if (!size && _option.rtsp_demand) { + _clear_cache = true; + } + MediaSourceEventInterceptor::onReaderChanged(sender, size); + } + + bool inputFrame(const Frame::Ptr &frame) override { + if (_clear_cache && _option.rtsp_demand) { + _clear_cache = false; + _media_src->clearCache(); + } + if (_enabled || !_option.rtsp_demand) { + return RtspMuxer::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.rtsp_demand ? (_clear_cache ? true : _enabled) : true; + } + +private: + bool _enabled = true; + bool _clear_cache = false; + ProtocolOption _option; + RtspMediaSource::Ptr _media_src; +}; + + +}//namespace mediakit +#endif //ZLMEDIAKIT_RTSPMEDIASOURCEMUXER_H diff --git a/MediaServer/Rtsp/RtspMuxer.cpp b/MediaServer/Rtsp/RtspMuxer.cpp new file mode 100644 index 0000000..e28b526 --- /dev/null +++ b/MediaServer/Rtsp/RtspMuxer.cpp @@ -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. + */ + +#include "RtspMuxer.h" +#include "Common/config.h" +#include "Extension/Factory.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +void RtspMuxer::onRtp(RtpPacket::Ptr in, bool is_key) { + if (_live) { + auto &ref = _tracks[in->track_index]; + if (ref.rtp_stamp != in->getHeader()->stamp) { + // rtp时间戳变化才计算ntp,节省cpu资源 [AUTO-TRANSLATED:729d54f2] + // Only calculate NTP when the RTP timestamp changes, saving CPU resources + int64_t stamp_ms = in->getStamp() * uint64_t(1000) / in->sample_rate; + int64_t stamp_ms_inc; + // 求rtp时间戳增量 [AUTO-TRANSLATED:f6ba022f] + // Get the RTP timestamp increment + ref.stamp.revise(stamp_ms, stamp_ms, stamp_ms_inc, stamp_ms_inc); + ref.rtp_stamp = in->getHeader()->stamp; + ref.ntp_stamp = stamp_ms_inc + _ntp_stamp_start; + } + + // rtp拦截入口,此处统一赋值ntp [AUTO-TRANSLATED:1412435a] + // RTP interception entry, set NTP here uniformly + in->ntp_stamp = ref.ntp_stamp; + } else { + // 点播情况下设置ntp时间戳为rtp时间戳加基准ntp时间戳 [AUTO-TRANSLATED:b9f77de4] + // In on-demand scenarios, set the NTP timestamp to the RTP timestamp plus the base NTP timestamp + in->ntp_stamp = _ntp_stamp_start + (in->getStamp() * uint64_t(1000) / in->sample_rate); + } + _rtpRing->write(std::move(in), is_key); +} + +RtspMuxer::RtspMuxer(const TitleSdp::Ptr &title) { + if (!title) { + _sdp = std::make_shared()->getSdp(); + } else { + _live = title->getDuration() == 0; + _sdp = title->getSdp(); + } + _rtpRing = std::make_shared(); + _rtpInterceptor = std::make_shared(); + _rtpInterceptor->setDelegate(std::make_shared([this](RtpPacket::Ptr in, bool is_key) { + onRtp(std::move(in), is_key); + })); + + _ntp_stamp_start = getCurrentMillisecond(true); +} + +bool RtspMuxer::addTrack(const Track::Ptr &track) { + if (_track_existed[track->getTrackType()]) { + // rtsp不支持多个同类型track [AUTO-TRANSLATED:87262d86] + // RTSP does not support multiple tracks of the same type + WarnL << "Already add a track kind of: " << track->getTrackTypeStr() << ", ignore track: " << track->getCodecName(); + return false; + } + + auto &ref = _tracks[track->getIndex()]; + auto &encoder = ref.encoder; + CHECK(!encoder); + + // payload type 96以后则为动态pt [AUTO-TRANSLATED:812ac0a2] + // Payload type 96 and above is dynamic PT + Sdp::Ptr sdp = track->getSdp(96 + _index); + if (!sdp) { + WarnL << "Unsupported codec: " << track->getCodecName(); + return false; + } + + encoder = Factory::getRtpEncoderByCodecId(track->getCodecId(), sdp->getPayloadType()); + if (!encoder) { + return false; + } + + // 标记已经存在该类型track [AUTO-TRANSLATED:ed79ebb5] + // Mark that a track of this type already exists + _track_existed[track->getTrackType()] = true; + + { + static atomic s_ssrc(0); + uint32_t ssrc = s_ssrc++; + if (!ssrc) { + // ssrc不能为0 [AUTO-TRANSLATED:312a1b47] + // SSRC cannot be 0 + ssrc = s_ssrc++; + } + if (track->getTrackType() == TrackVideo) { + // 视频的ssrc是偶数,方便调试 [AUTO-TRANSLATED:c22cd03f] + // The video SSRC is even for debugging convenience + ssrc = 2 * ssrc; + } else { + // 音频ssrc是奇数 [AUTO-TRANSLATED:50688636] + // The audio SSRC is odd + ssrc = 2 * ssrc + 1; + } + GET_CONFIG(uint32_t, audio_mtu, Rtp::kAudioMtuSize); + GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize); + auto mtu = track->getTrackType() == TrackVideo ? video_mtu : audio_mtu; + encoder->setRtpInfo(ssrc, mtu, sdp->getSampleRate(), sdp->getPayloadType(), 2 * track->getTrackType(), track->getIndex()); + } + + // 设置rtp输出环形缓存 [AUTO-TRANSLATED:5ac7e24a] + // Set the RTP output circular buffer + encoder->setRtpRing(_rtpInterceptor); + + auto str = sdp->getSdp(); + str += "a=control:trackID="; + str += std::to_string(_index); + str += "\r\n"; + + // 添加其sdp [AUTO-TRANSLATED:80958925] + // Add its SDP + _sdp.append(str); + trySyncTrack(); + + // rtp的时间戳是pts,允许回退 [AUTO-TRANSLATED:f4a977fc] + // The RTP timestamp is PTS, allowing rollback + if (track->getTrackType() == TrackVideo) { + ref.stamp.enableRollback(true); + } + ++_index; + return true; +} + +void RtspMuxer::trySyncTrack() { + Stamp *first = nullptr; + for (auto &pr : _tracks) { + if (!first) { + first = &pr.second.stamp; + } else { + pr.second.stamp.syncTo(*first); + } + } +} + +bool RtspMuxer::inputFrame(const Frame::Ptr &frame) { + auto &encoder = _tracks[frame->getIndex()].encoder; + return encoder ? encoder->inputFrame(frame) : false; +} + +void RtspMuxer::flush() { + for (auto &pr : _tracks) { + if (pr.second.encoder) { + pr.second.encoder->flush(); + } + } +} + +string RtspMuxer::getSdp() { + return _sdp; +} + +RtpRing::RingType::Ptr RtspMuxer::getRtpRing() const { + return _rtpRing; +} + +void RtspMuxer::resetTracks() { + _sdp.clear(); + _tracks.clear(); + CLEAR_ARR(_track_existed); +} + +} /* namespace mediakit */ \ No newline at end of file diff --git a/MediaServer/Rtsp/RtspMuxer.h b/MediaServer/Rtsp/RtspMuxer.h new file mode 100644 index 0000000..94a3bb6 --- /dev/null +++ b/MediaServer/Rtsp/RtspMuxer.h @@ -0,0 +1,137 @@ +/* + * 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_RTSPMUXER_H +#define ZLMEDIAKIT_RTSPMUXER_H + +#include "Extension/Frame.h" +#include "Common/MediaSink.h" +#include "Common/Stamp.h" +#include "RtpCodec.h" + +namespace mediakit{ + +class RingDelegateHelper : public toolkit::RingDelegate { +public: + using onRtp = std::function ; + + RingDelegateHelper(onRtp on_rtp) { + _on_rtp = std::move(on_rtp); + } + + void onWrite(RtpPacket::Ptr in, bool is_key) override { + _on_rtp(std::move(in), is_key); + } + +private: + onRtp _on_rtp; +}; + +/** +* rtsp生成器 + * RTSP generator + + * [AUTO-TRANSLATED:2a72d801] +*/ +class RtspMuxer : public MediaSinkInterface { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * Constructor + + * [AUTO-TRANSLATED:41469869] + */ + RtspMuxer(const TitleSdp::Ptr &title = nullptr); + + /** + * 获取完整的SDP字符串 + * @return SDP字符串 + * Get the complete SDP string + * @return SDP string + + * [AUTO-TRANSLATED:f5d1b0a6] + */ + std::string getSdp() ; + + /** + * 获取rtp环形缓存 + * @return + * Get the RTP ring buffer + * @return + + * [AUTO-TRANSLATED:644e8634] + */ + RtpRing::RingType::Ptr getRtpRing() const; + + /** + * 添加ready状态的track + * Add a ready state track + + * [AUTO-TRANSLATED:2d8138b3] + */ + bool addTrack(const Track::Ptr & track) override; + + /** + * 写入帧数据 + * @param frame 帧 + * Write frame data + * @param frame Frame + + * [AUTO-TRANSLATED:b7c92013] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame buffers + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + + /** + * 重置所有track + * Reset all tracks + + + * [AUTO-TRANSLATED:f203fa3e] + */ + void resetTracks() override ; + +private: + void onRtp(RtpPacket::Ptr in, bool is_key); + void trySyncTrack(); + +private: + bool _live = true; + bool _track_existed[2] = { false, false }; + + uint8_t _index {0}; + uint64_t _ntp_stamp_start; + std::string _sdp; + + struct TrackInfo { + Stamp stamp; + uint32_t rtp_stamp { 0 }; + uint64_t ntp_stamp { 0 }; + RtpCodec::Ptr encoder; + }; + + std::unordered_map _tracks; + RtpRing::RingType::Ptr _rtpRing; + RtpRing::RingType::Ptr _rtpInterceptor; +}; + + +} /* namespace mediakit */ + +#endif //ZLMEDIAKIT_RTSPMUXER_H diff --git a/MediaServer/Rtsp/RtspPlayer.cpp b/MediaServer/Rtsp/RtspPlayer.cpp new file mode 100644 index 0000000..6a2cea5 --- /dev/null +++ b/MediaServer/Rtsp/RtspPlayer.cpp @@ -0,0 +1,875 @@ +/* + * 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 "RtspPlayer.h" +#include "Common/config.h" +#include "Rtcp/Rtcp.h" +#include "Rtcp/RtcpContext.h" +#include "RtspDemuxer.h" +#include "RtspMediaSource.h" +#include "RtspPlayerImp.h" +#include "Util/MD5.h" +#include "Util/base64.h" +#include +#include +#include +#include + +using namespace toolkit; +using namespace std; + +namespace mediakit { + +enum PlayType { type_play = 0, type_pause, type_seek, type_speed }; +enum class BeatType : uint32_t { both = 0, rtcp, cmd }; + +RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) + : TcpClient(poller) {} + +RtspPlayer::~RtspPlayer(void) { + DebugL; +} + +void RtspPlayer::sendTeardown() { + if (alive()) { + if (!_control_url.empty()) { + sendRtspRequest("TEARDOWN", _control_url); + } + shutdown(SockException(Err_shutdown, "teardown")); + } +} + +void RtspPlayer::teardown() { + sendTeardown(); + _md5_nonce.clear(); + _realm.clear(); + _sdp_track.clear(); + _session_id.clear(); + _content_base.clear(); + RtpReceiver::clear(); + _rtcp_context.clear(); + + CLEAR_ARR(_rtp_sock); + CLEAR_ARR(_rtcp_sock); + + _play_check_timer.reset(); + _rtp_check_timer.reset(); + _cseq_send = 1; + _on_response = nullptr; +} + +void RtspPlayer::play(const string &strUrl) { + RtspUrl url; + try { + url.parse(strUrl); + } catch (std::exception &ex) { + onPlayResult_l(SockException(Err_other, StrPrinter << "illegal rtsp url:" << ex.what()), false); + return; + } + + teardown(); + + if (url._user.size()) { + (*this)[Client::kRtspUser] = url._user; + } + if (url._passwd.size()) { + (*this)[Client::kRtspPwd] = url._passwd; + (*this)[Client::kRtspPwdIsMD5] = false; + } + + _play_url = url._url; + _rtp_type = (Rtsp::eRtpType)(int)(*this)[Client::kRtpType]; + _beat_type = (*this)[Client::kRtspBeatType].as(); + _beat_interval_ms = (*this)[Client::kBeatIntervalMS].as(); + _speed = (*this)[Client::kRtspSpeed].as(); + DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _rtp_type; + + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + float playTimeOutSec = (*this)[Client::kTimeoutMS].as() / 1000.0f; + _play_check_timer.reset(new Timer( + playTimeOutSec, + [weakSelf]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->onPlayResult_l(SockException(Err_timeout, "play rtsp timeout"), false); + return false; + }, + getPoller())); + + if (!(*this)[Client::kNetAdapter].empty()) { + setNetAdapter((*this)[Client::kNetAdapter]); + } + startConnect(url._host, url._port, playTimeOutSec); +} + +void RtspPlayer::onConnect(const SockException &err) { + if (err.getErrCode() != Err_success) { + onPlayResult_l(err, false); + return; + } + sendOptions(); +} + +void RtspPlayer::onRecv(const Buffer::Ptr &buf) { + if (_benchmark_mode && !_play_check_timer) { + // 在性能测试模式下,如果rtsp握手完毕后,不再解析rtp包 [AUTO-TRANSLATED:747b5399] + // In performance test mode, if the RTSP handshake is complete, no RTP packets will be parsed + _rtp_recv_ticker.resetTime(); + return; + } + try { + input(buf->data(), buf->size()); + } catch (exception &e) { + SockException ex(Err_other, e.what()); + onPlayResult_l(ex, !_play_check_timer); + } +} + +void RtspPlayer::onError(const SockException &ex) { + // 定时器_pPlayTimer为空后表明握手结束了 [AUTO-TRANSLATED:06a369c2] + // After the timer _pPlayTimer is empty, it indicates that the handshake is complete + onPlayResult_l(ex, !_play_check_timer); +} + +// from live555 +bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) { + if (!_realm.empty()) { + // 已经认证过了 [AUTO-TRANSLATED:f2db5f9c] + // It has been authenticated + return false; + } + + char *realm = new char[paramsStr.size()]; + char *nonce = new char[paramsStr.size()]; + char *stale = new char[paramsStr.size()]; + onceToken token(nullptr, [&]() { + delete[] realm; + delete[] nonce; + delete[] stale; + }); + + if (sscanf(paramsStr.data(), "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\", stale=%[a-zA-Z]", realm, nonce, stale) == 3) { + _realm = (const char *)realm; + _md5_nonce = (const char *)nonce; + return true; + } + if (sscanf(paramsStr.data(), "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) { + _realm = (const char *)realm; + _md5_nonce = (const char *)nonce; + return true; + } + if (sscanf(paramsStr.data(), "Basic realm=\"%[^\"]\"", realm) == 1) { + _realm = (const char *)realm; + return true; + } + return false; +} + +bool RtspPlayer::handleResponse(const string &cmd, const Parser &parser) { + string authInfo = parser["WWW-Authenticate"]; + // 发送DESCRIBE命令后的回复 [AUTO-TRANSLATED:39629cf0] + // The response after sending the DESCRIBE command + if ((parser.status() == "401") && handleAuthenticationFailure(authInfo)) { + sendOptions(); + return false; + } + if (parser.status() == "302" || parser.status() == "301") { + auto newUrl = parser["Location"]; + if (newUrl.empty()) { + throw std::runtime_error("未找到Location字段(跳转url)"); + } + play(newUrl); + return false; + } + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << cmd << ":" << parser.status() << " " << parser.statusStr() << endl); + } + return true; +} + +void RtspPlayer::handleResDESCRIBE(const Parser &parser) { + if (!handleResponse("DESCRIBE", parser)) { + return; + } + _content_base = parser["Content-Base"]; + if (_content_base.empty()) { + _content_base = _play_url; + } + if (_content_base.back() == '/') { + _content_base.pop_back(); + } + + // 解析sdp [AUTO-TRANSLATED:ed3f07fe] + // Parse SDP + SdpParser sdpParser(parser.content()); + + _control_url = sdpParser.getControlUrl(_content_base); + + string sdp; + auto play_track = (TrackType)((int)(*this)[Client::kPlayTrack] - 1); + if (play_track != TrackInvalid) { + auto track = sdpParser.getTrack(play_track); + _sdp_track.emplace_back(track); + auto title_track = sdpParser.getTrack(TrackTitle); + sdp = (title_track ? title_track->toString() : "") + track->toString(); + } else { + _sdp_track = sdpParser.getAvailableTrack(); + sdp = sdpParser.toString(); + } + + if (_sdp_track.empty()) { + throw std::runtime_error("无有效的Sdp Track"); + } + if (!onCheckSDP(sdp)) { + throw std::runtime_error("onCheckSDP faied"); + } + _rtcp_context.clear(); + for (auto &track : _sdp_track) { + if (track->_pt != 0xff) { + setPayloadType(_rtcp_context.size(), track->_pt); + } + _rtcp_context.emplace_back(std::make_shared()); + } + sendSetup(0); +} + +// 有必要的情况下创建udp端口 [AUTO-TRANSLATED:651202fc] +// Create UDP port if necessary +void RtspPlayer::createUdpSockIfNecessary(int track_idx) { + auto &rtpSockRef = _rtp_sock[track_idx]; + auto &rtcpSockRef = _rtcp_sock[track_idx]; + if (!rtpSockRef || !rtcpSockRef) { + std::pair pr = std::make_pair(createSocket(), createSocket()); + makeSockPair(pr, get_local_ip()); + rtpSockRef = pr.first; + rtcpSockRef = pr.second; + } +} + +// 发送SETUP命令 [AUTO-TRANSLATED:68a7ca33] +// Send SETUP command +void RtspPlayer::sendSetup(unsigned int track_idx) { + _on_response = std::bind(&RtspPlayer::handleResSETUP, this, placeholders::_1, track_idx); + auto &track = _sdp_track[track_idx]; + auto control_url = track->getControlUrl(_content_base); + switch (_rtp_type) { + case Rtsp::RTP_TCP: { + sendRtspRequest( + "SETUP", control_url, + { "Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1 << ";mode=play" }); + } break; + case Rtsp::RTP_MULTICAST: { + sendRtspRequest("SETUP", control_url, { "Transport", "RTP/AVP;multicast;mode=play" }); + } break; + case Rtsp::RTP_UDP: { + createUdpSockIfNecessary(track_idx); + sendRtspRequest( + "SETUP", control_url, + { "Transport", + StrPrinter << "RTP/AVP;unicast;client_port=" << _rtp_sock[track_idx]->get_local_port() << "-" << _rtcp_sock[track_idx]->get_local_port() + << ";mode=play" }); + } break; + default: break; + } +} + +void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "SETUP:" << parser.status() << " " << parser.statusStr() << endl); + } + if (track_idx == 0) { + _session_id = parser["Session"]; + _session_id.append(";"); + _session_id = findSubString(_session_id.data(), nullptr, ";"); + } + + auto strTransport = parser["Transport"]; + if (strTransport.find("TCP") != string::npos || strTransport.find("interleaved") != string::npos) { + _rtp_type = Rtsp::RTP_TCP; + } else if (strTransport.find("multicast") != string::npos) { + _rtp_type = Rtsp::RTP_MULTICAST; + } else { + _rtp_type = Rtsp::RTP_UDP; + } + auto transport_map = Parser::parseArgs(strTransport, ";", "="); + RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); + string ssrc = transport_map["ssrc"]; + if (!ssrc.empty()) { + sscanf(ssrc.data(), "%x", &_sdp_track[track_idx]->_ssrc); + } else { + _sdp_track[track_idx]->_ssrc = 0; + } + + if (_rtp_type == Rtsp::RTP_TCP) { + int interleaved_rtp, interleaved_rtcp; + sscanf(transport_map["interleaved"].data(), "%d-%d", &interleaved_rtp, &interleaved_rtcp); + _sdp_track[track_idx]->_interleaved = interleaved_rtp; + } else { + auto port_str = transport_map[(_rtp_type == Rtsp::RTP_MULTICAST ? "port" : "server_port")]; + int rtp_port, rtcp_port; + sscanf(port_str.data(), "%d-%d", &rtp_port, &rtcp_port); + auto &pRtpSockRef = _rtp_sock[track_idx]; + auto &pRtcpSockRef = _rtcp_sock[track_idx]; + + if (_rtp_type == Rtsp::RTP_MULTICAST) { + // udp组播 [AUTO-TRANSLATED:ccc90d1f] + // UDP multicast + auto multiAddr = transport_map["destination"]; + pRtpSockRef = createSocket(); + // 目前组播仅支持ipv4 [AUTO-TRANSLATED:8215bfd2] + // Currently, multicast only supports IPv4 + if (!pRtpSockRef->bindUdpSock(rtp_port, "0.0.0.0")) { + pRtpSockRef.reset(); + throw std::runtime_error("open udp sock err"); + } + auto fd = pRtpSockRef->rawFD(); + if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(), get_local_ip().data())) { + SockUtil::joinMultiAddr(fd, multiAddr.data(), get_local_ip().data()); + } + + // 设置rtcp发送端口 [AUTO-TRANSLATED:f39b07bd] + // Set RTCP send port + pRtcpSockRef = createSocket(); + // 目前组播仅支持ipv4 [AUTO-TRANSLATED:8215bfd2] + // Currently, multicast only supports IPv4 + if (!pRtcpSockRef->bindUdpSock(0, "0.0.0.0")) { + // 分配端口失败 [AUTO-TRANSLATED:59ecd25d] + // Port allocation failed + throw runtime_error("open udp socket failed"); + } + + // 设置发送地址和发送端口 [AUTO-TRANSLATED:67e1cb6e] + // Set send address and send port + auto dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); + pRtcpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); + } else { + createUdpSockIfNecessary(track_idx); + // udp单播 [AUTO-TRANSLATED:7d16a875] + // UDP unicast + auto dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtp_port); + pRtpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); + // 发送rtp打洞包 [AUTO-TRANSLATED:9a79d94f] + // Send RTP hole punching packet + pRtpSockRef->send("\xce\xfa\xed\xfe", 4); + + dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); + // 设置rtcp发送目标,为后续发送rtcp做准备 [AUTO-TRANSLATED:70929b8e] + // Set RTCP send target, prepare for subsequent RTCP sending + pRtcpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); + } + + auto peer_ip = get_peer_ip(); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + // 设置rtp over udp接收回调处理函数 [AUTO-TRANSLATED:6e74b593] + // Set RTP over UDP receive callback handler + pRtpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + if (SockUtil::inet_ntoa(addr) != peer_ip) { + WarnL << "收到其他地址的rtp数据:" << SockUtil::inet_ntoa(addr); + return; + } + strongSelf->handleOneRtp( + track_idx, strongSelf->_sdp_track[track_idx]->_type, strongSelf->_sdp_track[track_idx]->_samplerate, (uint8_t *)buf->data(), buf->size()); + }); + + if (pRtcpSockRef) { + // 设置rtcp over udp接收回调处理函数 [AUTO-TRANSLATED:eed55b8e] + // Set RTCP over UDP receive callback handler + pRtcpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + if (SockUtil::inet_ntoa(addr) != peer_ip) { + WarnL << "收到其他地址的rtcp数据:" << SockUtil::inet_ntoa(addr); + return; + } + strongSelf->onRtcpPacket(track_idx, strongSelf->_sdp_track[track_idx], (uint8_t *)buf->data(), buf->size()); + }); + } + } + + if (track_idx < _sdp_track.size() - 1) { + // 需要继续发送SETUP命令 [AUTO-TRANSLATED:d7ea1a7a] + // Need to continue sending SETUP command + sendSetup(track_idx + 1); + return; + } + // 所有setup命令发送完毕 [AUTO-TRANSLATED:be499080] + // All SETUP commands have been sent + // 发送play命令 [AUTO-TRANSLATED:47a826d1] + // Send PLAY command + if (_speed==0.0f) { + sendPause(type_play, 0); + } else { + sendPause(type_speed, 0); + } + +} + +void RtspPlayer::sendDescribe() { + // 发送DESCRIBE命令后处理函数:handleResDESCRIBE [AUTO-TRANSLATED:3c2b0ffe] + // Handle the response to the DESCRIBE command: handleResDESCRIBE + _on_response = std::bind(&RtspPlayer::handleResDESCRIBE, this, placeholders::_1); + sendRtspRequest("DESCRIBE", _play_url, { "Accept", "application/sdp" }); +} + +void RtspPlayer::sendOptions() { + _on_response = [this](const Parser &parser) { + if (!handleResponse("OPTIONS", parser)) { + return; + } + // 获取服务器支持的命令 [AUTO-TRANSLATED:8a6a12f1] + // Get the commands supported by the server + _supported_cmd.clear(); + auto public_val = split(parser["Public"], ","); + for (auto &cmd : public_val) { + trim(cmd); + _supported_cmd.emplace(cmd); + } + // 发送Describe请求,获取sdp [AUTO-TRANSLATED:f2e291d1] + // Send Describe request to get SDP + sendDescribe(); + }; + sendRtspRequest("OPTIONS", _play_url); +} + +void RtspPlayer::sendKeepAlive() { + _on_response = [](const Parser &parser) {}; + if (_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()) { + // 支持GET_PARAMETER,用此命令保活 [AUTO-TRANSLATED:b45cd737] + // Support GET_PARAMETER, use this command to keep alive + sendRtspRequest("GET_PARAMETER", _control_url); + } else { + // 不支持GET_PARAMETER,用OPTIONS命令保活 [AUTO-TRANSLATED:3391350c] + // Do not support GET_PARAMETER, use OPTIONS command to keep alive + sendRtspRequest("OPTIONS", _play_url); + } +} + +void RtspPlayer::sendPause(int type, uint32_t seekMS) { + _on_response = std::bind(&RtspPlayer::handleResPAUSE, this, placeholders::_1, type); + // 开启或暂停rtsp [AUTO-TRANSLATED:8ba5b594] + // Start or pause RTSP + switch (type) { + case type_pause: sendRtspRequest("PAUSE", _control_url, {}); break; + case type_play: + // sendRtspRequest("PLAY", _content_base); + // break; + case type_seek: + sendRtspRequest("PLAY", _control_url, { "Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-" }); + break; + case type_speed: + speed(_speed); + break; + default: + WarnL << "unknown type : " << type; + _on_response = nullptr; + break; + } +} + +void RtspPlayer::pause(bool bPause) { + sendPause(bPause ? type_pause : type_seek, getProgressMilliSecond()); +} + +void RtspPlayer::speed(float speed) { + sendRtspRequest("PLAY", _control_url, { "Scale", StrPrinter << speed }); +} + +void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { + if (parser.status() != "200") { + switch (type) { + case type_pause: WarnL << "Pause failed:" << parser.status() << " " << parser.statusStr(); break; + case type_play: + WarnL << "Play failed:" << parser.status() << " " << parser.statusStr(); + onPlayResult_l(SockException(Err_other, StrPrinter << "rtsp play failed:" << parser.status() << " " << parser.statusStr()), !_play_check_timer); + break; + case type_seek: WarnL << "Seek failed:" << parser.status() << " " << parser.statusStr(); break; + } + return; + } + + if (type == type_pause) { + // 暂停成功! [AUTO-TRANSLATED:782cea77] + // Pause successfully! + _rtp_check_timer.reset(); + return; + } + + // play或seek成功 [AUTO-TRANSLATED:ba7b0da3] + // Play or seek successfully + uint32_t iSeekTo = 0; + // 修正时间轴 [AUTO-TRANSLATED:5ab341f9] + // Correct the timeline + auto strRange = parser["Range"]; + if (strRange.size()) { + auto strStart = findSubString(strRange.data(), "npt=", "-"); + if (strStart == "now") { + strStart = "0"; + } + iSeekTo = (uint32_t)(1000 * atof(strStart.data())); + DebugL << "seekTo(ms):" << iSeekTo; + } + + onPlayResult_l(SockException(Err_success, type == type_seek ? "resume rtsp success" : "rtsp play success"), !_play_check_timer); +} + +void RtspPlayer::onWholeRtspPacket(Parser &parser) { + if (!start_with(parser.method(), "RTSP")) { + // 不是rtsp回复,忽略 [AUTO-TRANSLATED:1dca8f64] + // Not an RTSP response, ignore + WarnL << "Not rtsp response: " << parser.method(); + return; + } + try { + decltype(_on_response) func; + _on_response.swap(func); + if (func) { + func(parser); + } + parser.clear(); + } catch (std::exception &err) { + // 定时器_pPlayTimer为空后表明握手结束了 [AUTO-TRANSLATED:06a369c2] + // _pPlayTimer is empty after handshake ends + onPlayResult_l(SockException(Err_other, err.what()), !_play_check_timer); + } +} + +void RtspPlayer::onRtpPacket(const char *data, size_t len) { + int trackIdx = -1; + uint8_t interleaved = data[1]; + if (interleaved % 2 == 0) { + trackIdx = getTrackIndexByInterleaved(interleaved); + if (trackIdx == -1) { + return; + } + handleOneRtp( + trackIdx, _sdp_track[trackIdx]->_type, _sdp_track[trackIdx]->_samplerate, (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, + len - RtpPacket::kRtpTcpHeaderSize); + } else { + trackIdx = getTrackIndexByInterleaved(interleaved - 1); + if (trackIdx == -1) { + return; + } + onRtcpPacket(trackIdx, _sdp_track[trackIdx], (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); + } +} + +// 此处预留rtcp处理函数 [AUTO-TRANSLATED:30c3afa8] +// Reserved for RTCP processing function +void RtspPlayer::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len) { + auto rtcp_arr = RtcpHeader::loadFromBytes((char *)data, len); + for (auto &rtcp : rtcp_arr) { + _rtcp_context[track_idx]->onRtcp(rtcp); + if ((RtcpType)rtcp->pt == RtcpType::RTCP_SR) { + auto sr = (RtcpSR *)(rtcp); + // 设置rtp时间戳与ntp时间戳的对应关系 [AUTO-TRANSLATED:e92f4749] + // Set the correspondence between RTP timestamp and NTP timestamp + setNtpStamp(track_idx, sr->rtpts, sr->getNtpUnixStampMS()); + } + } +} + +void RtspPlayer::onRtpSorted(RtpPacket::Ptr rtppt, int trackidx) { + _stamp[trackidx] = rtppt->getStampMS(); + _rtp_recv_ticker.resetTime(); + onRecvRTP(std::move(rtppt), _sdp_track[trackidx]); +} + +float RtspPlayer::getPacketLossRate(TrackType type) const { + size_t lost = 0, expected = 0; + try { + auto track_idx = getTrackIndexByTrackType(type); + if (_rtcp_context.empty()) { + return 0; + } + auto ctx = _rtcp_context[track_idx]; + lost = ctx->getLost(); + expected = ctx->getExpectedPackets(); + } catch (...) { + for (auto &ctx : _rtcp_context) { + lost += ctx->getLost(); + expected += ctx->getExpectedPackets(); + } + } + if (!expected) { + return 0; + } + return (float)(double(lost) / double(expected)); +} + +uint32_t RtspPlayer::getProgressMilliSecond() const { + return MAX(_stamp[0], _stamp[1]); +} + +void RtspPlayer::seekToMilliSecond(uint32_t ms) { + sendPause(type_seek, ms); +} + +void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list &header) { + string key; + StrCaseMap header_map; + int i = 0; + for (auto &val : header) { + if (++i % 2 == 0) { + header_map.emplace(key, val); + } else { + key = val; + } + } + + sendRtspRequest(cmd, url, header_map); +} + +void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const StrCaseMap &header_const) { + auto header = header_const; + header.emplace("CSeq", StrPrinter << _cseq_send++); + header.emplace("User-Agent", kServerName); + + if (!_session_id.empty()) { + header.emplace("Session", _session_id); + } + + if (!_realm.empty() && !(*this)[Client::kRtspUser].empty()) { + if (!_md5_nonce.empty()) { + // MD5认证 [AUTO-TRANSLATED:0640fa6a] + // MD5 authentication + /* + response计算方法如下: + RTSP客户端应该使用username + password并计算response如下: + (1)当password为MD5编码,则 + response = md5( password:nonce:md5(public_method:url) ); + (2)当password为ANSI字符串,则 + response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); + /* + The response calculation method is as follows: + The RTSP client should use username + password and calculate the response as follows: + (1) When password is MD5 encoded, then + response = md5( password:nonce:md5(public_method:url) ); + (2) When password is ANSI string, then + response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); + + * [AUTO-TRANSLATED:7858b67d] + */ + string encrypted_pwd = (*this)[Client::kRtspPwd]; + if (!(*this)[Client::kRtspPwdIsMD5].as()) { + encrypted_pwd = MD5((*this)[Client::kRtspUser] + ":" + _realm + ":" + encrypted_pwd).hexdigest(); + } + auto response = MD5(encrypted_pwd + ":" + _md5_nonce + ":" + MD5(cmd + ":" + url).hexdigest()).hexdigest(); + _StrPrinter printer; + printer << "Digest "; + printer << "username=\"" << (*this)[Client::kRtspUser] << "\", "; + printer << "realm=\"" << _realm << "\", "; + printer << "nonce=\"" << _md5_nonce << "\", "; + printer << "uri=\"" << url << "\", "; + printer << "response=\"" << response << "\""; + header.emplace("Authorization", printer); + } else if (!(*this)[Client::kRtspPwdIsMD5].as()) { + // base64认证 [AUTO-TRANSLATED:06d26447] + // Base64 authentication + auto authStrBase64 = encodeBase64((*this)[Client::kRtspUser] + ":" + (*this)[Client::kRtspPwd]); + header.emplace("Authorization", StrPrinter << "Basic " << authStrBase64); + } + } + + _StrPrinter printer; + printer << cmd << " " << url << " RTSP/1.0\r\n"; + + TraceL << cmd << " "<< url; + for (auto &pr : header) { + printer << pr.first << ": " << pr.second << "\r\n"; + } + printer << "\r\n"; + SockSender::send(std::move(printer)); +} + +void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx) { + auto &rtcp_ctx = _rtcp_context[track_idx]; + rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); + + auto &ticker = _rtcp_send_ticker[track_idx]; + if (ticker.elapsedTime() < _beat_interval_ms) { + // 心跳时间未到 [AUTO-TRANSLATED:265d4e62] + // Heartbeat time not reached + return; + } + + // 有些rtsp服务器需要rtcp保活,有些需要发送信令保活; rtcp与rtsp信令轮流心跳,该特性用于兼容issue:#642 [AUTO-TRANSLATED:a36070c5] + // Some RTSP servers require RTCP keep-alive, some require sending signaling keep-alive; RTCP and RTSP signaling alternate heartbeat, this feature is used to be compatible with issue:#642 + auto &rtcp_flag = _send_rtcp[track_idx]; + ticker.resetTime(); + + switch ((BeatType)_beat_type) { + case BeatType::cmd: rtcp_flag = false; break; + case BeatType::rtcp: rtcp_flag = true; break; + case BeatType::both: + default: rtcp_flag = !rtcp_flag; break; + } + + // 发送信令保活 [AUTO-TRANSLATED:0ef0747e] + // Send signaling keep-alive + if (!rtcp_flag) { + if (track_idx == 0) { + // 两个track无需同时触发发送信令保活 [AUTO-TRANSLATED:7dde4aec] + // Two tracks do not need to trigger sending signaling keep-alive at the same time + sendKeepAlive(); + } + return; + } + + // 发送rtcp [AUTO-TRANSLATED:5c7aad87] + // Send RTCP + static auto send_rtcp = [](RtspPlayer *thiz, int index, Buffer::Ptr ptr) { + if (thiz->_rtp_type == Rtsp::RTP_TCP) { + auto &track = thiz->_sdp_track[index]; + thiz->send(makeRtpOverTcpPrefix((uint16_t)(ptr->size()), track->_interleaved + 1)); + thiz->send(std::move(ptr)); + } else { + thiz->_rtcp_sock[index]->send(std::move(ptr)); + } + }; + + auto ssrc = rtp->getSSRC(); + auto rtcp = rtcp_ctx->createRtcpRR(ssrc + 1, ssrc); + auto rtcp_sdes = RtcpSdes::create({ kServerName }); + rtcp_sdes->chunks.type = (uint8_t)SdesType::RTCP_SDES_CNAME; + rtcp_sdes->chunks.ssrc = htonl(ssrc); + send_rtcp(this, track_idx, std::move(rtcp)); + send_rtcp(this, track_idx, RtcpHeader::toBuffer(rtcp_sdes)); +} + +void RtspPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { + if (ex.getErrCode() == Err_shutdown) { + // 主动shutdown的,不触发回调 [AUTO-TRANSLATED:21f9c396] + // Active shutdown, do not trigger callback + return; + } + + WarnL << ex.getErrCode() << " " << ex.what(); + if (!handshake_done) { + // 开始播放阶段 [AUTO-TRANSLATED:7ef385fc] + // Start playback stage + _play_check_timer.reset(); + onPlayResult(ex); + // 是否为性能测试模式 [AUTO-TRANSLATED:871a0e65] + // Whether it is performance test mode + _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + } else if (ex) { + // 播放成功后异常断开回调 [AUTO-TRANSLATED:3fe28e4f] + // Callback for abnormal disconnection after successful playback + onShutdown(ex); + } else { + // 恢复播放 [AUTO-TRANSLATED:7a99afd6] + // Resume playback + onResume(); + } + + if (!ex) { + // 播放成功,恢复rtp接收超时定时器 [AUTO-TRANSLATED:0ebefcb5] + // Playback successful, restore RTP receive timeout timer + _rtp_recv_ticker.resetTime(); + auto timeoutMS = (*this)[Client::kMediaTimeoutMS].as(); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + auto lam = [weakSelf, timeoutMS]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + if (strongSelf->_rtp_recv_ticker.elapsedTime() > timeoutMS) { + // 接收rtp媒体数据包超时 [AUTO-TRANSLATED:601b8c0c] + // Receive RTP media data packet timeout + strongSelf->onPlayResult_l(SockException(Err_timeout, "receive rtp timeout"), true); + return false; + } + return true; + }; + // 创建rtp数据接收超时检测定时器 [AUTO-TRANSLATED:edbffc19] + // Create RTP data receive timeout detection timer + _rtp_check_timer = std::make_shared(timeoutMS / 2000.0f, lam, getPoller()); + } else { + sendTeardown(); + } +} + +int RtspPlayer::getTrackIndexByInterleaved(int interleaved) const { + for (size_t i = 0; i < _sdp_track.size(); ++i) { + if (_sdp_track[i]->_interleaved == interleaved) { + return i; + } + } + if (_sdp_track.size() == 1) { + return 0; + } + WarnL << "no such track with interleaved:" << interleaved; + return -1; +} + +int RtspPlayer::getTrackIndexByTrackType(TrackType track_type) const { + for (size_t i = 0; i < _sdp_track.size(); ++i) { + if (_sdp_track[i]->_type == track_type) { + return i; + } + } + if (_sdp_track.size() == 1) { + return 0; + } + throw SockException(Err_other, StrPrinter << "no such track with type:" << getTrackString(track_type)); +} + +/////////////////////////////////////////////////// +// RtspPlayerImp +float RtspPlayerImp::getDuration() const { + return _demuxer ? _demuxer->getDuration() : 0; +} + +void RtspPlayerImp::onPlayResult(const toolkit::SockException &ex) { + if (!(*this)[Client::kWaitTrackReady].as() || ex) { + Super::onPlayResult(ex); + return; + } +} + +void RtspPlayerImp::addTrackCompleted() { + if ((*this)[Client::kWaitTrackReady].as()) { + Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); + } +} + +std::vector RtspPlayerImp::getTracks(bool ready /*= true*/) const { + return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); +} + +bool RtspPlayerImp::onCheckSDP(const std::string &sdp) { + _rtsp_media_src = std::dynamic_pointer_cast(_media_src); + if (_rtsp_media_src) { + _rtsp_media_src->setSdp(sdp); + } + _demuxer = std::make_shared(); + _demuxer->setTrackListener(this, (*this)[Client::kWaitTrackReady].as()); + _demuxer->loadSdp(sdp); + return true; +} + +void RtspPlayerImp::onRecvRTP(RtpPacket::Ptr rtp, const SdpTrack::Ptr &track) { + // rtp解复用时可以判断是否为关键帧起始位置 [AUTO-TRANSLATED:fb7d9b6e] + // When demultiplexing RTP, it can be determined whether it is the starting position of the key frame + auto key_pos = _demuxer->inputRtp(rtp); + if (_rtsp_media_src) { + _rtsp_media_src->onWrite(std::move(rtp), key_pos); + } +} + +} /* namespace mediakit */ diff --git a/MediaServer/Rtsp/RtspPlayer.h b/MediaServer/Rtsp/RtspPlayer.h new file mode 100644 index 0000000..58e2cfa --- /dev/null +++ b/MediaServer/Rtsp/RtspPlayer.h @@ -0,0 +1,200 @@ +/* + * 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_RTSPPLAYER_RTSPPLAYER_H_TXT_ +#define SRC_RTSPPLAYER_RTSPPLAYER_H_TXT_ + +#include +#include +#include "Util/TimeTicker.h" +#include "Poller/Timer.h" +#include "Network/Socket.h" +#include "Player/PlayerBase.h" +#include "Network/TcpClient.h" +#include "RtspSplitter.h" +#include "RtpReceiver.h" +#include "Rtcp/RtcpContext.h" + +namespace mediakit { + +// 实现了rtsp播放器协议部分的功能,及数据接收功能 [AUTO-TRANSLATED:c1ed5c0f] +// Implemented the rtsp player protocol part functionality, and data receiving functionality +class RtspPlayer : public PlayerBase, public toolkit::TcpClient, public RtspSplitter, public RtpReceiver { +public: + using Ptr = std::shared_ptr; + + RtspPlayer(const toolkit::EventPoller::Ptr &poller); + ~RtspPlayer() override; + + void play(const std::string &strUrl) override; + void pause(bool pause) override; + void speed(float speed) override; + void teardown() override; + float getPacketLossRate(TrackType type) const override; + +protected: + // 派生类回调函数 [AUTO-TRANSLATED:61e20903] + // Derived class callback function + virtual bool onCheckSDP(const std::string &sdp) = 0; + virtual void onRecvRTP(RtpPacket::Ptr rtp, const SdpTrack::Ptr &track) = 0; + uint32_t getProgressMilliSecond() const; + void seekToMilliSecond(uint32_t ms); + + /** + * 收到完整的rtsp包回调,包括sdp等content数据 + * @param parser rtsp包 + * Callback for receiving a complete rtsp packet, including sdp and other content data + * @param parser rtsp packet + + * [AUTO-TRANSLATED:4d3c2056] + */ + void onWholeRtspPacket(Parser &parser) override ; + + /** + * 收到rtp包回调 + * @param data + * @param len + * Callback for receiving rtp packet + * @param data + * @param len + + * [AUTO-TRANSLATED:c8f7c9bb] + */ + void onRtpPacket(const char *data,size_t len) override ; + + /** + * rtp数据包排序后输出 + * @param rtp rtp数据包 + * @param track_idx track索引 + * Output rtp data packets after sorting + * @param rtp rtp data packet + * @param track_idx track index + + * [AUTO-TRANSLATED:8f9ca364] + */ + void onRtpSorted(RtpPacket::Ptr rtp, int track_idx) override; + + /** + * 解析出rtp但还未排序 + * @param rtp rtp数据包 + * @param track_index track索引 + * Parse out rtp but not yet sorted + * @param rtp rtp data packet + * @param track_index track index + + * [AUTO-TRANSLATED:c1636911] + */ + void onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_index) override; + + /** + * 收到RTCP包回调 + * @param track_idx track索引 + * @param track sdp相关信息 + * @param data rtcp内容 + * @param len rtcp内容长度 + * Callback for receiving RTCP packet + * @param track_idx track index + * @param track sdp related information + * @param data rtcp content + * @param len rtcp content length + + * [AUTO-TRANSLATED:1a2cfa4f] + */ + virtual void onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len); + + /////////////TcpClient override///////////// + void onConnect(const toolkit::SockException &err) override; + void onRecv(const toolkit::Buffer::Ptr &buf) override; + void onError(const toolkit::SockException &ex) override; + +private: + void onPlayResult_l(const toolkit::SockException &ex , bool handshake_done); + + int getTrackIndexByInterleaved(int interleaved) const; + int getTrackIndexByTrackType(TrackType track_type) const; + + void handleResSETUP(const Parser &parser, unsigned int track_idx); + void handleResDESCRIBE(const Parser &parser); + bool handleAuthenticationFailure(const std::string &wwwAuthenticateParamsStr); + void handleResPAUSE(const Parser &parser, int type); + bool handleResponse(const std::string &cmd, const Parser &parser); + + void sendOptions(); + void sendSetup(unsigned int track_idx); + void sendPause(int type , uint32_t ms); + void sendDescribe(); + void sendTeardown(); + void sendKeepAlive(); + void sendRtspRequest(const std::string &cmd, const std::string &url ,const StrCaseMap &header = StrCaseMap()); + void sendRtspRequest(const std::string &cmd, const std::string &url ,const std::initializer_list &header); + void createUdpSockIfNecessary(int track_idx); + +private: + // 是否为性能测试模式 [AUTO-TRANSLATED:1fde8234] + // Whether it is performance test mode + bool _benchmark_mode = false; + // 轮流发送rtcp与GET_PARAMETER保活 [AUTO-TRANSLATED:5b6f9c37] + // Send rtcp and GET_PARAMETER keep-alive in turn + bool _send_rtcp[2] = {true, true}; + + // 心跳类型 [AUTO-TRANSLATED:c22abb05] + // Heartbeat type + uint32_t _beat_type = 0; + // 心跳保护间隔 [AUTO-TRANSLATED:de16d9c9] + // Heartbeat protection interval + uint32_t _beat_interval_ms = 0; + + std::string _play_url; + // rtsp开始倍速 [AUTO-TRANSLATED:9ab84508] + // Rtsp start speed + float _speed= 0.0f; + std::vector _sdp_track; + std::function _on_response; + // RTP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:77c186bb] + // RTP port, trackid idx is the array subscript + toolkit::Socket::Ptr _rtp_sock[2]; + // RTCP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:446a7861] + // RTCP port, trackid idx is the array subscript + toolkit::Socket::Ptr _rtcp_sock[2]; + + // rtsp鉴权相关 [AUTO-TRANSLATED:947dc6a3] + // Rtsp authentication related + std::string _md5_nonce; + std::string _realm; + //rtsp info + std::string _session_id; + uint32_t _cseq_send = 1; + std::string _content_base; + std::string _control_url; + Rtsp::eRtpType _rtp_type = Rtsp::RTP_TCP; + + // 当前rtp时间戳 [AUTO-TRANSLATED:410f2691] + // Current rtp timestamp + uint32_t _stamp[2] = {0, 0}; + + // 超时功能实现 [AUTO-TRANSLATED:1d603b3a] + // Timeout function implementation + toolkit::Ticker _rtp_recv_ticker; + std::shared_ptr _play_check_timer; + std::shared_ptr _rtp_check_timer; + // 服务器支持的命令 [AUTO-TRANSLATED:f7f589bf] + // Server supported commands + std::set _supported_cmd; + ////////// rtcp //////////////// + // rtcp发送时间,trackid idx 为数组下标 [AUTO-TRANSLATED:bf3248b1] + // Rtcp send time, trackid idx is the array subscript + toolkit::Ticker _rtcp_send_ticker[2]; + // 统计rtp并发送rtcp [AUTO-TRANSLATED:0ac2b665] + // Statistics rtp and send rtcp + std::vector _rtcp_context; +}; + +} /* namespace mediakit */ +#endif /* SRC_RTSPPLAYER_RTSPPLAYER_H_TXT_ */ diff --git a/MediaServer/Rtsp/RtspPlayerImp.h b/MediaServer/Rtsp/RtspPlayerImp.h new file mode 100644 index 0000000..0bb7b47 --- /dev/null +++ b/MediaServer/Rtsp/RtspPlayerImp.h @@ -0,0 +1,82 @@ +/* + * 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_RTP_RTPPARSERTESTER_H_ +#define SRC_RTP_RTPPARSERTESTER_H_ + +#include +#include +#include +#include "RtspPlayer.h" +#include "RtspDemuxer.h" +#include "RtspMediaSource.h" + +namespace mediakit { + +class RtspPlayerImp : public PlayerImp ,private TrackListener { +public: + using Ptr = std::shared_ptr; + using Super = PlayerImp; + + RtspPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {} + + ~RtspPlayerImp() override { + DebugL; + } + + float getProgress() const override { + if (getDuration() > 0) { + return getProgressMilliSecond() / (getDuration() * 1000); + } + return PlayerBase::getProgress(); + } + + uint32_t getProgressPos() const override { + if (getDuration() > 0) { + return getProgressMilliSecond(); + } + return PlayerBase::getProgressPos(); + } + + void seekTo(float fProgress) override { + fProgress = MAX(float(0), MIN(fProgress, float(1.0))); + seekToMilliSecond((uint32_t) (fProgress * getDuration() * 1000)); + } + + void seekTo(uint32_t seekPos) override { + uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000; + seekToMilliSecond(pos); + } + + float getDuration() const override; + + std::vector getTracks(bool ready = true) const override; + +private: + // 派生类回调函数 [AUTO-TRANSLATED:61e20903] + // Derived class callback function + bool onCheckSDP(const std::string &sdp) override; + + void onRecvRTP(RtpPacket::Ptr rtp, const SdpTrack::Ptr &track) override; + + void onPlayResult(const toolkit::SockException &ex) override; + + bool addTrack(const Track::Ptr &track) override { return true; } + + void addTrackCompleted() override; + +private: + RtspDemuxer::Ptr _demuxer; + RtspMediaSource::Ptr _rtsp_media_src; +}; + +} /* namespace mediakit */ + +#endif /* SRC_RTP_RTPPARSERTESTER_H_ */ diff --git a/MediaServer/Rtsp/RtspPusher.cpp b/MediaServer/Rtsp/RtspPusher.cpp new file mode 100644 index 0000000..7b64436 --- /dev/null +++ b/MediaServer/Rtsp/RtspPusher.cpp @@ -0,0 +1,599 @@ +/* + * 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 "Util/MD5.h" +#include "Util/base64.h" +#include "RtspPusher.h" +#include "RtspSession.h" +#include "Rtcp/RtcpContext.h" +#include "Common/config.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +RtspPusher::RtspPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src) : TcpClient(poller) { + _push_src = src; +} + +RtspPusher::~RtspPusher() { + teardown(); + DebugL; +} + +void RtspPusher::sendTeardown(){ + if (alive()) { + if (!_content_base.empty()) { + sendRtspRequest("TEARDOWN", _content_base); + } + shutdown(SockException(Err_shutdown, "teardown")); + } +} + +void RtspPusher::teardown() { + sendTeardown(); + reset(); + CLEAR_ARR(_rtp_sock); + CLEAR_ARR(_rtcp_sock); + _nonce.clear(); + _realm.clear(); + _track_vec.clear(); + _session_id.clear(); + _content_base.clear(); + _cseq = 1; + _publish_timer.reset(); + _beat_timer.reset(); + _rtsp_reader.reset(); + _track_vec.clear(); + _on_res_func = nullptr; +} + +void RtspPusher::publish(const string &url_str) { + RtspUrl url; + try { + url.parse(url_str); + } catch (std::exception &ex) { + onPublishResult_l(SockException(Err_other, StrPrinter << "illegal rtsp url:" << ex.what()), false); + return; + } + + teardown(); + + if (url._user.size()) { + (*this)[Client::kRtspUser] = url._user; + } + if (url._passwd.size()) { + (*this)[Client::kRtspPwd] = url._passwd; + (*this)[Client::kRtspPwdIsMD5] = false; + } + + _url = url_str; + _rtp_type = (Rtsp::eRtpType) (int) (*this)[Client::kRtpType]; + DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " + << (url._passwd.size() ? url._passwd : "null") << " " << _rtp_type; + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + float publish_timeout_sec = (*this)[Client::kTimeoutMS].as() / 1000.0f; + _publish_timer.reset(new Timer(publish_timeout_sec, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onPublishResult_l(SockException(Err_timeout, "publish rtsp timeout"), false); + return false; + }, getPoller())); + + if (!(*this)[Client::kNetAdapter].empty()) { + setNetAdapter((*this)[Client::kNetAdapter]); + } + + startConnect(url._host, url._port, publish_timeout_sec); +} + +void RtspPusher::onPublishResult_l(const SockException &ex, bool handshake_done) { + DebugL << ex.what(); + if (ex.getErrCode() == Err_shutdown) { + // 主动shutdown的,不触发回调 [AUTO-TRANSLATED:bd97b1c1] + // Actively shutdown, do not trigger callback + return; + } + if (!handshake_done) { + // 播放结果回调 [AUTO-TRANSLATED:a5714269] + // Playback result callback + _publish_timer.reset(); + onPublishResult(ex); + } else { + // 播放成功后异常断开回调 [AUTO-TRANSLATED:b5c5fa80] + // Callback for abnormal disconnection after playback success + onShutdown(ex); + } + + if (ex) { + sendTeardown(); + } +} + +void RtspPusher::onError(const SockException &ex) { + // 定时器_pPublishTimer为空后表明握手结束了 [AUTO-TRANSLATED:630ec31e] + // The timer _pPublishTimer is empty, indicating that the handshake is over + onPublishResult_l(ex, !_publish_timer); +} + +void RtspPusher::onConnect(const SockException &err) { + if (err) { + onPublishResult_l(err, false); + return; + } + sendAnnounce(); +} + +void RtspPusher::onRecv(const Buffer::Ptr &buf){ + try { + input(buf->data(), buf->size()); + } catch (exception &e) { + SockException ex(Err_other, e.what()); + // 定时器_pPublishTimer为空后表明握手结束了 [AUTO-TRANSLATED:630ec31e] + // The timer _pPublishTimer is empty, indicating that the handshake is over + onPublishResult_l(ex, !_publish_timer); + } +} + +void RtspPusher::onWholeRtspPacket(Parser &parser) { + decltype(_on_res_func) func; + _on_res_func.swap(func); + if (func) { + func(parser); + } + parser.clear(); +} + +void RtspPusher::onRtpPacket(const char *data, size_t len) { + int trackIdx = -1; + uint8_t interleaved = data[1]; + if (interleaved % 2 != 0) { + trackIdx = getTrackIndexByInterleaved(interleaved - 1); + onRtcpPacket(trackIdx, _track_vec[trackIdx], (uint8_t *) data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); + } +} + +void RtspPusher::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len){ + auto rtcp_arr = RtcpHeader::loadFromBytes((char *) data, len); + for (auto &rtcp : rtcp_arr) { + _rtcp_context[track_idx]->onRtcp(rtcp); + } +} + +void RtspPusher::sendAnnounce() { + auto src = _push_src.lock(); + if (!src) { + throw std::runtime_error("the media source was released"); + } + // 解析sdp [AUTO-TRANSLATED:a2d549e2] + // Parse sdp + _sdp_parser.load(src->getSdp()); + _track_vec = _sdp_parser.getAvailableTrack(); + if (_track_vec.empty()) { + throw std::runtime_error("无有效的Sdp Track"); + } + _rtcp_context.clear(); + for (auto &track : _track_vec) { + _rtcp_context.emplace_back(std::make_shared()); + } + _on_res_func = std::bind(&RtspPusher::handleResAnnounce, this, placeholders::_1); + sendRtspRequest("ANNOUNCE", _url, {}, src->getSdp()); +} + +void RtspPusher::handleResAnnounce(const Parser &parser) { + string authInfo = parser["WWW-Authenticate"]; + // 发送DESCRIBE命令后的回复 [AUTO-TRANSLATED:924afd2e] + // Reply after sending DESCRIBE command + if ((parser.status() == "401") && handleAuthenticationFailure(authInfo)) { + sendAnnounce(); + return; + } + if (parser.status() == "302") { + auto newUrl = parser["Location"]; + if (newUrl.empty()) { + throw std::runtime_error("未找到Location字段(跳转url)"); + } + publish(newUrl); + return; + } + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "ANNOUNCE:" << parser.status() << " " << parser.statusStr()); + } + _content_base = parser["Content-Base"]; + + if (_content_base.empty()) { + _content_base = _url; + } + if (_content_base.back() == '/') { + _content_base.pop_back(); + } + + _session_id = parser["Session"]; + + sendSetup(0); +} + +bool RtspPusher::handleAuthenticationFailure(const string ¶ms_str) { + if (!_realm.empty()) { + // 已经认证过了 [AUTO-TRANSLATED:3c8ce1d6] + // Already authenticated + return false; + } + + char *realm = new char[params_str.size()]; + char *nonce = new char[params_str.size()]; + char *stale = new char[params_str.size()]; + onceToken token(nullptr, [&]() { + delete[] realm; + delete[] nonce; + delete[] stale; + }); + + if (sscanf(params_str.data(), "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\", stale=%[a-zA-Z]", realm, nonce, stale) == 3) { + _realm = (const char *) realm; + _nonce = (const char *) nonce; + return true; + } + if (sscanf(params_str.data(), "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) { + _realm = (const char *) realm; + _nonce = (const char *) nonce; + return true; + } + if (sscanf(params_str.data(), "Basic realm=\"%[^\"]\"", realm) == 1) { + _realm = (const char *) realm; + return true; + } + return false; +} + +// 有必要的情况下创建udp端口 [AUTO-TRANSLATED:b59b7389] +// Create UDP port if necessary +void RtspPusher::createUdpSockIfNecessary(int track_idx){ + auto &rtpSockRef = _rtp_sock[track_idx]; + auto &rtcpSockRef = _rtcp_sock[track_idx]; + if (!rtpSockRef || !rtcpSockRef) { + std::pair pr = std::make_pair(createSocket(), createSocket()); + makeSockPair(pr, get_local_ip()); + rtpSockRef = pr.first; + rtcpSockRef = pr.second; + } +} + +void RtspPusher::sendSetup(unsigned int track_idx) { + _on_res_func = std::bind(&RtspPusher::handleResSetup, this, placeholders::_1, track_idx); + auto &track = _track_vec[track_idx]; + auto control_url = track->getControlUrl(_content_base); + switch (_rtp_type) { + case Rtsp::RTP_TCP: { + sendRtspRequest("SETUP", control_url, {"Transport", + StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 + << "-" << track->_type * 2 + 1 << ";mode=record"}); + } + break; + case Rtsp::RTP_UDP: { + createUdpSockIfNecessary(track_idx); + int port = _rtp_sock[track_idx]->get_local_port(); + sendRtspRequest("SETUP", control_url, + {"Transport", StrPrinter << "RTP/AVP;unicast;client_port=" << port << "-" << port + 1 << ";mode=record"}); + } + break; + default: + break; + } +} + +void RtspPusher::handleResSetup(const Parser &parser, unsigned int track_idx) { + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "SETUP:" << parser.status() << " " << parser.statusStr() << endl); + } + if (track_idx == 0) { + _session_id = parser["Session"]; + _session_id.append(";"); + _session_id = findSubString(_session_id.data(), nullptr, ";"); + } + + auto transport = parser["Transport"]; + if (transport.find("TCP") != string::npos || transport.find("interleaved") != string::npos) { + _rtp_type = Rtsp::RTP_TCP; + string interleaved = findSubString(findSubString((transport + ";").data(), "interleaved=", ";").data(), NULL, "-"); + _track_vec[track_idx]->_interleaved = atoi(interleaved.data()); + } else if (transport.find("multicast") != string::npos) { + throw std::runtime_error("SETUP rtsp pusher can not support multicast!"); + } else { + _rtp_type = Rtsp::RTP_UDP; + createUdpSockIfNecessary(track_idx); + const char *strPos = "server_port="; + auto port_str = findSubString((transport + ";").data(), strPos, ";"); + uint16_t rtp_port = atoi(findSubString(port_str.data(), NULL, "-").data()); + uint16_t rtcp_port = atoi(findSubString(port_str.data(), "-", NULL).data()); + auto &rtp_sock = _rtp_sock[track_idx]; + auto &rtcp_sock = _rtcp_sock[track_idx]; + + auto rtpto = SockUtil::make_sockaddr(get_peer_ip().data(), rtp_port); + // 设置rtp发送目标,为后续发送rtp做准备 [AUTO-TRANSLATED:5ae9bd72] + // Set RTP sending target, prepare for subsequent RTP sending + rtp_sock->bindPeerAddr((struct sockaddr *) &(rtpto)); + + // 设置rtcp发送目标,为后续发送rtcp做准备 [AUTO-TRANSLATED:a487732d] + // Set RTCP sending target, prepare for subsequent RTCP sending + auto rtcpto = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); + rtcp_sock->bindPeerAddr((struct sockaddr *)&(rtcpto)); + + auto peer_ip = get_peer_ip(); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + if(rtcp_sock) { + // 设置rtcp over udp接收回调处理函数 [AUTO-TRANSLATED:59963785] + // Set RTCP over UDP receive callback handler + rtcp_sock->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr , int addr_len) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + if (SockUtil::inet_ntoa(addr) != peer_ip) { + WarnL << "收到其他地址的rtcp数据:" << SockUtil::inet_ntoa(addr); + return; + } + strongSelf->onRtcpPacket(track_idx, strongSelf->_track_vec[track_idx], (uint8_t *) buf->data(), buf->size()); + }); + } + } + + RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); + + if (track_idx < _track_vec.size() - 1) { + // 需要继续发送SETUP命令 [AUTO-TRANSLATED:fddda4c6] + // Need to continue sending SETUP command + sendSetup(track_idx + 1); + return; + } + + sendRecord(); +} + +void RtspPusher::sendOptions() { + _on_res_func = [](const Parser &parser) {}; + sendRtspRequest("OPTIONS", _content_base); +} + +void RtspPusher::updateRtcpContext(const RtpPacket::Ptr &rtp){ + int track_index = getTrackIndexByTrackType(rtp->type); + auto &ticker = _rtcp_send_ticker[track_index]; + auto &rtcp_ctx = _rtcp_context[track_index]; + rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); + if (!rtp->ntp_stamp && !rtp->getStamp()) { + // 忽略时间戳都为0的rtp [AUTO-TRANSLATED:6b793565] + // Ignore RTP with all timestamps being 0 + return; + } + //send rtcp every 5 second + if (ticker.elapsedTime() > 5 * 1000) { + ticker.resetTime(); + static auto send_rtcp = [](RtspPusher *thiz, int index, Buffer::Ptr ptr) { + if (thiz->_rtp_type == Rtsp::RTP_TCP) { + auto &track = thiz->_track_vec[index]; + thiz->send(makeRtpOverTcpPrefix((uint16_t) (ptr->size()), track->_interleaved + 1)); + thiz->send(std::move(ptr)); + } else { + thiz->_rtcp_sock[index]->send(std::move(ptr)); + } + }; + + auto ssrc = rtp->getSSRC(); + auto rtcp = rtcp_ctx->createRtcpSR(ssrc + 1); + auto rtcp_sdes = RtcpSdes::create({kServerName}); + rtcp_sdes->chunks.type = (uint8_t) SdesType::RTCP_SDES_CNAME; + rtcp_sdes->chunks.ssrc = htonl(ssrc); + send_rtcp(this, track_index, std::move(rtcp)); + send_rtcp(this, track_index, RtcpHeader::toBuffer(rtcp_sdes)); + } +} + +void RtspPusher::sendRtpPacket(const RtspMediaSource::RingDataType &pkt) { + switch (_rtp_type) { + case Rtsp::RTP_TCP: { + size_t i = 0; + auto size = pkt->size(); + setSendFlushFlag(false); + pkt->for_each([&](const RtpPacket::Ptr &rtp) { + updateRtcpContext(rtp); + if (++i == size) { + setSendFlushFlag(true); + } + send(rtp); + }); + break; + } + + case Rtsp::RTP_UDP: { + size_t i = 0; + auto size = pkt->size(); + pkt->for_each([&](const RtpPacket::Ptr &rtp) { + updateRtcpContext(rtp); + int iTrackIndex = getTrackIndexByTrackType(rtp->type); + auto &pSock = _rtp_sock[iTrackIndex]; + if (!pSock) { + shutdown(SockException(Err_shutdown, "udp sock not opened yet")); + return; + } + + pSock->send(std::make_shared(rtp, RtpPacket::kRtpTcpHeaderSize), nullptr, 0, ++i == size); + }); + break; + } + default : break; + } +} + +int RtspPusher::getTrackIndexByInterleaved(int interleaved) const { + for (size_t i = 0; i < _track_vec.size(); ++i) { + if (_track_vec[i]->_interleaved == interleaved) { + return i; + } + } + if (_track_vec.size() == 1) { + return 0; + } + throw SockException(Err_shutdown, StrPrinter << "no such track with interleaved:" << interleaved); +} + +int RtspPusher::getTrackIndexByTrackType(TrackType type) const { + for (size_t i = 0; i < _track_vec.size(); ++i) { + if (type == _track_vec[i]->_type) { + return i; + } + } + if (_track_vec.size() == 1) { + return 0; + } + throw SockException(Err_shutdown, StrPrinter << "no such track with type:" << getTrackString(type)); +} + +void RtspPusher::sendRecord() { + _on_res_func = [this](const Parser &parser) { + auto src = _push_src.lock(); + if (!src) { + throw std::runtime_error("the media source was released"); + } + + src->pause(false); + _rtsp_reader = src->getRing()->attach(getPoller()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _rtsp_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->sendRtpPacket(pkt); + }); + _rtsp_reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->onPublishResult_l(SockException(Err_other, "媒体源被释放"), !strong_self->_publish_timer); + } + }); + if (_rtp_type != Rtsp::RTP_TCP) { + // ///////////////////////心跳///////////////////////////////// [AUTO-TRANSLATED:4e72777b] + // ///////////////////////Heartbeat///////////////////////////////// + _beat_timer.reset(new Timer((*this)[Client::kBeatIntervalMS].as() / 1000.0f, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->sendOptions(); + return true; + }, getPoller())); + } + onPublishResult_l(SockException(Err_success, "success"), false); + // 提升发送性能 [AUTO-TRANSLATED:90630751] + // Improve sending performance + setSocketFlags(); + }; + sendRtspRequest("RECORD", _content_base, {"Range", "npt=0.000-"}); +} + +void RtspPusher::setSocketFlags(){ + GET_CONFIG(int, merge_write_ms, General::kMergeWriteMS); + if (merge_write_ms > 0) { + // 提高发送性能 [AUTO-TRANSLATED:de96ec30] + // Improve sending performance + setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); + SockUtil::setNoDelay(getSock()->rawFD(), false); + } +} + +void RtspPusher::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list &header,const string &sdp ) { + string key; + StrCaseMap header_map; + int i = 0; + for (auto &val : header) { + if (++i % 2 == 0) { + header_map.emplace(key, val); + } else { + key = val; + } + } + sendRtspRequest(cmd, url, header_map, sdp); +} +void RtspPusher::sendRtspRequest(const string &cmd, const string &url,const StrCaseMap &header_const,const string &sdp ) { + auto header = header_const; + header.emplace("CSeq", StrPrinter << _cseq++); + header.emplace("User-Agent", kServerName); + + if (!_session_id.empty()) { + header.emplace("Session", _session_id); + } + + if (!_realm.empty() && !(*this)[Client::kRtspUser].empty()) { + if (!_nonce.empty()) { + // MD5认证 [AUTO-TRANSLATED:57936f0b] + // MD5 authentication + /* + response计算方法如下: + RTSP客户端应该使用username + password并计算response如下: + (1)当password为MD5编码,则 + response = md5( password:nonce:md5(public_method:url) ); + (2)当password为ANSI字符串,则 + response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); + /* + The response calculation method is as follows: + The RTSP client should use username + password and calculate the response as follows: + (1) When password is MD5 encoded, then + response = md5( password:nonce:md5(public_method:url) ); + (2) When password is ANSI string, then + response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); + + * [AUTO-TRANSLATED:7858b67d] + */ + string encrypted_pwd = (*this)[Client::kRtspPwd]; + if (!(*this)[Client::kRtspPwdIsMD5].as()) { + encrypted_pwd = MD5((*this)[Client::kRtspUser] + ":" + _realm + ":" + encrypted_pwd).hexdigest(); + } + auto response = MD5(encrypted_pwd + ":" + _nonce + ":" + MD5(cmd + ":" + url).hexdigest()).hexdigest(); + _StrPrinter printer; + printer << "Digest "; + printer << "username=\"" << (*this)[Client::kRtspUser] << "\", "; + printer << "realm=\"" << _realm << "\", "; + printer << "nonce=\"" << _nonce << "\", "; + printer << "uri=\"" << url << "\", "; + printer << "response=\"" << response << "\""; + header.emplace("Authorization", printer); + } else if (!(*this)[Client::kRtspPwdIsMD5].as()) { + // base64认证 [AUTO-TRANSLATED:06d26447] + // base64 authentication + auto authStrBase64 = encodeBase64((*this)[Client::kRtspUser] + ":" + (*this)[Client::kRtspPwd]); + header.emplace("Authorization", StrPrinter << "Basic " << authStrBase64); + } + } + + if (!sdp.empty()) { + header.emplace("Content-Length", StrPrinter << sdp.size()); + header.emplace("Content-Type", "application/sdp"); + } + + _StrPrinter printer; + printer << cmd << " " << url << " RTSP/1.0\r\n"; + for (auto &pr : header) { + printer << pr.first << ": " << pr.second << "\r\n"; + } + + printer << "\r\n"; + + if (!sdp.empty()) { + printer << sdp; + } + SockSender::send(std::move(printer)); +} + + +} /* namespace mediakit */ diff --git a/MediaServer/Rtsp/RtspPusher.h b/MediaServer/Rtsp/RtspPusher.h new file mode 100644 index 0000000..eb929f3 --- /dev/null +++ b/MediaServer/Rtsp/RtspPusher.h @@ -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 ZLMEDIAKIT_RTSPPUSHER_H +#define ZLMEDIAKIT_RTSPPUSHER_H + +#include +#include +#include "RtspMediaSource.h" +#include "Poller/Timer.h" +#include "Network/Socket.h" +#include "Network/TcpClient.h" +#include "RtspSplitter.h" +#include "Pusher/PusherBase.h" +#include "Rtcp/RtcpContext.h" + +namespace mediakit { + +class RtspPusher : public toolkit::TcpClient, public RtspSplitter, public PusherBase { +public: + using Ptr = std::shared_ptr; + RtspPusher(const toolkit::EventPoller::Ptr &poller,const RtspMediaSource::Ptr &src); + ~RtspPusher() override; + void publish(const std::string &url) override; + void teardown() override; + +protected: + //for Tcpclient override + void onRecv(const toolkit::Buffer::Ptr &buf) override; + void onConnect(const toolkit::SockException &err) override; + void onError(const toolkit::SockException &ex) override; + + //RtspSplitter override + void onWholeRtspPacket(Parser &parser) override ; + void onRtpPacket(const char *data,size_t len) override; + + virtual void onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len); + +private: + void onPublishResult_l(const toolkit::SockException &ex, bool handshake_done); + + void sendAnnounce(); + void sendSetup(unsigned int track_idx); + void sendRecord(); + void sendOptions(); + void sendTeardown(); + + void handleResAnnounce(const Parser &parser); + void handleResSetup(const Parser &parser, unsigned int track_idx); + bool handleAuthenticationFailure(const std::string ¶ms_str); + + int getTrackIndexByInterleaved(int interleaved) const; + int getTrackIndexByTrackType(TrackType type) const; + + void sendRtpPacket(const RtspMediaSource::RingDataType & pkt) ; + void sendRtspRequest(const std::string &cmd, const std::string &url ,const StrCaseMap &header = StrCaseMap(),const std::string &sdp = "" ); + void sendRtspRequest(const std::string &cmd, const std::string &url ,const std::initializer_list &header,const std::string &sdp = ""); + + void createUdpSockIfNecessary(int track_idx); + void setSocketFlags(); + void updateRtcpContext(const RtpPacket::Ptr &pkt); + +private: + unsigned int _cseq = 1; + Rtsp::eRtpType _rtp_type = Rtsp::RTP_TCP; + + // rtsp鉴权相关 [AUTO-TRANSLATED:947dc6a3] + // RTSP authentication related + std::string _nonce; + std::string _realm; + std::string _url; + std::string _session_id; + std::string _content_base; + SdpParser _sdp_parser; + std::vector _track_vec; + // RTP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:77c186bb] + // RTP port, trackid idx is the array index + toolkit::Socket::Ptr _rtp_sock[2]; + // RTCP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:446a7861] + // RTCP port, trackid idx is the array index + toolkit::Socket::Ptr _rtcp_sock[2]; + // 超时功能实现 [AUTO-TRANSLATED:1d603b3a] + // Timeout function implementation + toolkit::Timer::Ptr _publish_timer; + // 心跳定时器 [AUTO-TRANSLATED:536ec800] + // Heartbeat timer + toolkit::Timer::Ptr _beat_timer; + std::weak_ptr _push_src; + RtspMediaSource::RingType::RingReader::Ptr _rtsp_reader; + std::function _on_res_func; + ////////// rtcp //////////////// + // rtcp发送时间,trackid idx 为数组下标 [AUTO-TRANSLATED:bf3248b1] + // RTCP send time, trackid idx is the array index + toolkit::Ticker _rtcp_send_ticker[2]; + // 统计rtp并发送rtcp [AUTO-TRANSLATED:0ac2b665] + // Statistics RTP and send RTCP + std::vector _rtcp_context; +}; + +using RtspPusherImp = PusherImp; + +} /* namespace mediakit */ +#endif //ZLMEDIAKIT_RTSPPUSHER_H diff --git a/MediaServer/Rtsp/RtspSession.cpp b/MediaServer/Rtsp/RtspSession.cpp new file mode 100644 index 0000000..ef02ff5 --- /dev/null +++ b/MediaServer/Rtsp/RtspSession.cpp @@ -0,0 +1,1282 @@ +/* + * 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 +#include +#include "Common/config.h" +#include "UDPServer.h" +#include "RtspSession.h" +#include "Util/MD5.h" +#include "Util/base64.h" +#include "RtpMultiCaster.h" +#include "Rtcp/RtcpContext.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +/** + * rtsp协议有多种方式传输rtp数据包,目前已支持包括以下4种 + * 1: rtp over udp ,这种方式是rtp通过单独的udp端口传输 + * 2: rtp over udp_multicast,这种方式是rtp通过共享udp组播端口传输 + * 3: rtp over tcp,这种方式是通过rtsp信令tcp通道完成传输 + * 4: rtp over http,下面着重讲解:rtp over http + * + * rtp over http 是把rtsp协议伪装成http协议以达到穿透防火墙的目的, + * 此时播放器会发送两次http请求至rtsp服务器,第一次是http get请求, + * 第二次是http post请求。 + * + * 这两次请求通过http请求头中的x-sessioncookie键完成绑定 + * + * 第一次http get请求用于接收rtp、rtcp和rtsp回复,后续该链接不再发送其他请求 + * 第二次http post请求用于发送rtsp请求,rtsp握手结束后可能会断开连接,此时我们还要维持rtp发送 + * 需要指出的是http post请求中的content负载就是base64编码后的rtsp请求包, + * 播放器会把rtsp请求伪装成http content负载发送至rtsp服务器,然后rtsp服务器又把回复发送给第一次http get请求的tcp链接 + * 这样,对防火墙而言,本次rtsp会话就是两次http请求,防火墙就会放行数据 + * + * zlmediakit在处理rtsp over http的请求时,会把http poster中的content数据base64解码后转发给http getter处理 + */ + + +//rtsp over http 情况下get请求实例,在请求实例用于接收rtp数据包 +static unordered_map > g_mapGetter; +//对g_mapGetter上锁保护 +static recursive_mutex g_mtxGetter; + +RtspSession::RtspSession(const Socket::Ptr &sock) : Session(sock) { + GET_CONFIG(uint32_t,keep_alive_sec,Rtsp::kKeepAliveSecond); + sock->setSendTimeOutSecond(keep_alive_sec); +} + +void RtspSession::onError(const SockException &err) { + bool is_player = !_push_src_ownership; + uint64_t duration = _alive_ticker.createdTime() / 1000; + WarnP(this) << (is_player ? "RTSP播放器(" : "RTSP推流器(") + << _media_info.shortUrl() + << ")断开:" << err.what() + << ",耗时(s):" << duration; + + if (_rtp_type == Rtsp::RTP_MULTICAST) { + //取消UDP端口监听 + UDPServer::Instance().stopListenPeer(get_peer_ip().data(), this); + } + + if (_http_x_sessioncookie.size() != 0) { + //移除http getter的弱引用记录 + lock_guard lock(g_mtxGetter); + g_mapGetter.erase(_http_x_sessioncookie); + } + + //流量统计事件广播 + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + if (_bytes_usage >= iFlowThreshold * 1024) { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, is_player, *this); + } + + //如果是主动关闭的,那么不延迟注销 + if (_push_src && _continue_push_ms && err.getErrCode() != Err_shutdown) { + //取消所有权 + _push_src_ownership = nullptr; + //延时10秒注销流 + auto push_src = std::move(_push_src); + getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; }); + } +} + +void RtspSession::onManager() { + GET_CONFIG(uint32_t, handshake_sec, Rtsp::kHandshakeSecond); + GET_CONFIG(uint32_t, keep_alive_sec, Rtsp::kKeepAliveSecond); + + if (_alive_ticker.createdTime() > handshake_sec * 1000) { + if (_sessionid.size() == 0) { + shutdown(SockException(Err_timeout,"illegal connection")); + return; + } + } + + if (_push_src && _alive_ticker.elapsedTime() > keep_alive_sec * 1000) { + //推流超时 + shutdown(SockException(Err_timeout, "pusher session timeout")); + return; + } + + if (!_push_src && _rtp_type == Rtsp::RTP_UDP && _alive_ticker.elapsedTime() > keep_alive_sec * 4000) { + //rtp over udp播放器超时 + shutdown(SockException(Err_timeout, "rtp over udp player timeout")); + } +} + +void RtspSession::onRecv(const Buffer::Ptr &buf) { + _alive_ticker.resetTime(); + _bytes_usage += buf->size(); + if (_on_recv) { + //http poster的请求数据转发给http getter处理 + _on_recv(buf); + } else { + input(buf->data(), buf->size()); + } +} + +void RtspSession::onWholeRtspPacket(Parser &parser) { + string method = parser.method(); //提取出请求命令字 + _cseq = atoi(parser["CSeq"].data()); + if (_content_base.empty() && method != "GET" && method != "POST" ) { + RtspUrl rtsp; + rtsp.parse(parser.url()); + _content_base = rtsp._url; + _media_info.parse(parser.fullUrl()); + _media_info.schema = RTSP_SCHEMA; + } + + using rtsp_request_handler = void (RtspSession::*)(const Parser &parser); + static unordered_map s_cmd_functions; + static onceToken token([]() { + s_cmd_functions.emplace("OPTIONS", &RtspSession::handleReq_Options); + s_cmd_functions.emplace("DESCRIBE", &RtspSession::handleReq_Describe); + s_cmd_functions.emplace("ANNOUNCE", &RtspSession::handleReq_ANNOUNCE); + s_cmd_functions.emplace("RECORD", &RtspSession::handleReq_RECORD); + s_cmd_functions.emplace("SETUP", &RtspSession::handleReq_Setup); + s_cmd_functions.emplace("PLAY", &RtspSession::handleReq_Play); + s_cmd_functions.emplace("PAUSE", &RtspSession::handleReq_Pause); + s_cmd_functions.emplace("TEARDOWN", &RtspSession::handleReq_Teardown); + s_cmd_functions.emplace("GET", &RtspSession::handleReq_Get); + s_cmd_functions.emplace("POST", &RtspSession::handleReq_Post); + s_cmd_functions.emplace("SET_PARAMETER", &RtspSession::handleReq_SET_PARAMETER); + s_cmd_functions.emplace("GET_PARAMETER", &RtspSession::handleReq_SET_PARAMETER); + }); + + auto it = s_cmd_functions.find(method); + if (it == s_cmd_functions.end()) { + sendRtspResponse("403 Forbidden"); + throw SockException(Err_shutdown, StrPrinter << "403 Forbidden:" << method); + } + + (this->*(it->second))(parser); + parser.clear(); +} + +void RtspSession::onRtpPacket(const char *data, size_t len) { + uint8_t interleaved = data[1]; + if (interleaved % 2 == 0) { + auto track_idx = getTrackIndexByInterleaved(interleaved); + handleOneRtp(track_idx, _sdp_track[track_idx]->_type, _sdp_track[track_idx]->_samplerate, (uint8_t *) data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); + } else { + auto track_idx = getTrackIndexByInterleaved(interleaved - 1); + onRtcpPacket(track_idx, _sdp_track[track_idx], data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); + } +} + +void RtspSession::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, const char *data, size_t len){ + auto rtcp_arr = RtcpHeader::loadFromBytes((char *) data, len); + for (auto &rtcp : rtcp_arr) { + _rtcp_context[track_idx]->onRtcp(rtcp); + if ((RtcpType) rtcp->pt == RtcpType::RTCP_SR) { + auto sr = (RtcpSR *) (rtcp); + //设置rtp时间戳与ntp时间戳的对应关系 + setNtpStamp(track_idx, sr->rtpts, sr->getNtpUnixStampMS()); + } + } +} + +ssize_t RtspSession::getContentLength(Parser &parser) { + if(parser.method() == "POST"){ + //http post请求的content数据部分是base64编码后的rtsp请求信令包 + return remainDataSize(); + } + return RtspSplitter::getContentLength(parser); +} + +void RtspSession::handleReq_Options(const Parser &parser) { + //支持这些命令 + sendRtspResponse("200 OK",{"Public" , "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, ANNOUNCE, RECORD, SET_PARAMETER, GET_PARAMETER"}); +} + +void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { + auto full_url = parser.fullUrl(); + _content_base = full_url; + if (end_with(full_url, ".sdp")) { + //去除.sdp后缀,防止EasyDarwin推流器强制添加.sdp后缀 + full_url = full_url.substr(0, full_url.length() - 4); + _media_info.parse(full_url); + } + + if (_media_info.app.empty() || _media_info.stream.empty()) { + //推流rtsp url必须最少两级(rtsp://host/app/stream_id),不允许莫名其妙的推流url + static constexpr auto err = "rtsp推流url非法,最少确保两级rtsp url"; + sendRtspResponse("403 Forbidden", {"Content-Type", "text/plain"}, err); + throw SockException(Err_shutdown, StrPrinter << err << ":" << full_url); + } + + auto onRes = [this, parser, full_url](const string &err, const ProtocolOption &option) { + if (!err.empty()) { + sendRtspResponse("401 Unauthorized", { "Content-Type", "text/plain" }, err); + shutdown(SockException(Err_shutdown, StrPrinter << "401 Unauthorized:" << err)); + return; + } + + assert(!_push_src); + auto src = MediaSource::find(RTSP_SCHEMA, _media_info.vhost, _media_info.app, _media_info.stream); + auto push_failed = (bool)src; + + while (src) { + //尝试断连后继续推流 + auto rtsp_src = dynamic_pointer_cast(src); + if (!rtsp_src) { + //源不是rtsp推流产生的 + break; + } + auto ownership = rtsp_src->getOwnership(); + if (!ownership) { + //获取推流源所有权失败 + break; + } + _push_src = std::move(rtsp_src); + _push_src_ownership = std::move(ownership); + push_failed = false; + break; + } + + if (push_failed) { + sendRtspResponse("406 Not Acceptable", { "Content-Type", "text/plain" }, "Already publishing."); + string err = StrPrinter << "ANNOUNCE: Already publishing:" << _media_info.shortUrl() << endl; + throw SockException(Err_shutdown, err); + } + + SdpParser sdpParser(parser.content()); + _sessionid = makeRandStr(12); + _sdp_track = sdpParser.getAvailableTrack(); + if (_sdp_track.empty()) { + // sdp无效 + static constexpr auto err = "sdp中无有效track"; + sendRtspResponse("403 Forbidden", { "Content-Type", "text/plain" }, err); + shutdown(SockException(Err_shutdown, StrPrinter << err << ":" << full_url)); + return; + } + _rtcp_context.clear(); + for (auto &track : _sdp_track) { + _rtcp_context.emplace_back(std::make_shared()); + } + + if (!_push_src) { + _push_src = std::make_shared(_media_info); + //获取所有权 + _push_src_ownership = _push_src->getOwnership(); + _push_src->setProtocolOption(option); + _push_src->setSdp(parser.content()); + } + + _push_src->setListener(static_pointer_cast(shared_from_this())); + _continue_push_ms = option.continue_push_ms; + sendRtspResponse("200 OK"); + }; + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + Broadcast::PublishAuthInvoker invoker = [weak_self, onRes](const string &err, const ProtocolOption &option) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->async([weak_self, onRes, err, option]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + onRes(err, option); + }); + }; + + //rtsp推流需要鉴权 + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtsp_push, _media_info, invoker, *this); + if (!flag) { + //该事件无人监听,默认不鉴权 + onRes("", ProtocolOption()); + } +} + +void RtspSession::handleReq_RECORD(const Parser &parser){ + if (_sdp_track.empty() || parser["Session"] != _sessionid) { + send_SessionNotFound(); + throw SockException(Err_shutdown, _sdp_track.empty() ? "can not find any available track when record" : "session not found when record"); + } + + _StrPrinter rtp_info; + for (auto &track : _sdp_track) { + if (track->_inited == false) { + //还有track没有setup + shutdown(SockException(Err_shutdown, "track not setuped")); + return; + } + rtp_info << "url=" << track->getControlUrl(_content_base) << ","; + } + rtp_info.pop_back(); + sendRtspResponse("200 OK", {"RTP-Info", rtp_info}); + if (_rtp_type == Rtsp::RTP_TCP) { + //如果是rtsp推流服务器,并且是TCP推流,设置socket flags,,这样能提升接收性能 + setSocketFlags(); + } +} + +void RtspSession::emitOnPlay(){ + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + //url鉴权回调 + auto onRes = [weak_self](const string &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (!err.empty()) { + //播放url鉴权失败 + strong_self->sendRtspResponse("401 Unauthorized", {"Content-Type", "text/plain"}, err); + strong_self->shutdown(SockException(Err_shutdown, StrPrinter << "401 Unauthorized:" << err)); + return; + } + strong_self->onAuthSuccess(); + }; + + Broadcast::AuthInvoker invoker = [weak_self, onRes](const string &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->async([onRes, err, weak_self]() { + onRes(err); + }); + }; + + //广播通用播放url鉴权事件 + auto flag = _emit_on_play ? false : NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); + if (!flag) { + //该事件无人监听,默认不鉴权 + onRes(""); + } + //已经鉴权过了 + _emit_on_play = true; +} + +void RtspSession::handleReq_Describe(const Parser &parser) { + //该请求中的认证信息 + auto authorization = parser["Authorization"]; + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + //rtsp专属鉴权是否开启事件回调 + onGetRealm invoker = [weak_self, authorization](const string &realm) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + //本对象已经销毁 + return; + } + //切换到自己的线程然后执行 + strong_self->async([weak_self, realm, authorization]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + //本对象已经销毁 + return; + } + if (realm.empty()) { + //无需rtsp专属认证, 那么继续url通用鉴权认证(on_play) + strong_self->emitOnPlay(); + return; + } + //该流需要rtsp专属认证,开启rtsp专属认证后,将不再触发url通用鉴权认证(on_play) + strong_self->_rtsp_realm = realm; + strong_self->onAuthUser(realm, authorization); + }); + }; + + if(_rtsp_realm.empty()){ + //广播是否需要rtsp专属认证事件 + if (!NOTICE_EMIT(BroadcastOnGetRtspRealmArgs, Broadcast::kBroadcastOnGetRtspRealm, _media_info, invoker, *this)) { + //无人监听此事件,说明无需认证 + invoker(""); + } + }else{ + invoker(_rtsp_realm); + } +} + +void RtspSession::onAuthSuccess() { + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + MediaSource::findAsync(_media_info, weak_self.lock(), [weak_self](const MediaSource::Ptr &src){ + auto strong_self = weak_self.lock(); + if(!strong_self){ + return; + } + auto rtsp_src = dynamic_pointer_cast(src); + if (!rtsp_src) { + //未找到相应的MediaSource + string err = StrPrinter << "no such stream:" << strong_self->_media_info.shortUrl(); + strong_self->send_StreamNotFound(); + strong_self->shutdown(SockException(Err_shutdown,err)); + return; + } + //找到了相应的rtsp流 + strong_self->_sdp_track = SdpParser(rtsp_src->getSdp()).getAvailableTrack(); + if (strong_self->_sdp_track.empty()) { + //该流无效 + WarnL << "sdp中无有效track,该流无效:" << rtsp_src->getSdp(); + strong_self->send_StreamNotFound(); + strong_self->shutdown(SockException(Err_shutdown,"can not find any available track in sdp")); + return; + } + strong_self->_rtcp_context.clear(); + for (auto &track : strong_self->_sdp_track) { + strong_self->_rtcp_context.emplace_back(std::make_shared()); + } + strong_self->_sessionid = makeRandStr(12); + strong_self->_play_src = rtsp_src; + for(auto &track : strong_self->_sdp_track){ + track->_ssrc = rtsp_src->getSsrc(track->_type); + track->_seq = rtsp_src->getSeqence(track->_type); + track->_time_stamp = rtsp_src->getTimeStamp(track->_type); + } + + strong_self->sendRtspResponse("200 OK", + {"Content-Base", strong_self->_content_base + "/", + "x-Accept-Retransmit","our-retransmit", + "x-Accept-Dynamic-Rate","1" + },rtsp_src->getSdp()); + }); +} + +void RtspSession::onAuthFailed(const string &realm,const string &why,bool close) { + GET_CONFIG(bool, authBasic, Rtsp::kAuthBasic); + if (!authBasic) { + // 我们需要客户端优先以md5方式认证 + _auth_nonce = makeRandStr(32); + sendRtspResponse("401 Unauthorized", { "WWW-Authenticate", StrPrinter << "Digest realm=\"" << realm << "\",nonce=\"" << _auth_nonce << "\"" }); + } else { + // 当然我们也支持base64认证,但是我们不建议这样做 + sendRtspResponse("401 Unauthorized", { "WWW-Authenticate", StrPrinter << "Basic realm=\"" << realm << "\"" }); + } + if (close) { + shutdown(SockException(Err_shutdown, StrPrinter << "401 Unauthorized:" << why)); + } +} + +void RtspSession::onAuthBasic(const string &realm, const string &auth_base64) { + //base64认证 + auto user_passwd = decodeBase64(auth_base64); + auto user_pwd_vec = split(user_passwd, ":"); + if (user_pwd_vec.size() < 2) { + // 认证信息格式不合法,回复401 Unauthorized + onAuthFailed(realm, "can not find user and passwd when basic64 auth"); + return; + } + auto user = user_pwd_vec[0]; + auto pwd = user_pwd_vec[1]; + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + onAuth invoker = [pwd, realm, weak_self](bool encrypted, const string &good_pwd) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + //本对象已经销毁 + return; + } + //切换到自己的线程执行 + strong_self->async([weak_self, good_pwd, pwd, realm]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + //本对象已经销毁 + return; + } + //base64忽略encrypted参数,上层必须传入明文密码 + if (pwd == good_pwd) { + //提供的密码且匹配正确 + strong_self->onAuthSuccess(); + return; + } + //密码错误 + strong_self->onAuthFailed(realm, StrPrinter << "password mismatch when base64 auth:" << pwd << " != " << good_pwd); + }); + }; + + //此时必须提供明文密码 + if (!NOTICE_EMIT(BroadcastOnRtspAuthArgs, Broadcast::kBroadcastOnRtspAuth, _media_info, realm, user, true, invoker, *this)) { + //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 + WarnP(this) << "请监听kBroadcastOnRtspAuth事件!"; + //但是我们还是忽略认证以便完成播放 + //我们输入的密码是明文 + invoker(false, pwd); + } +} + +void RtspSession::onAuthDigest(const string &realm,const string &auth_md5){ + DebugP(this) << auth_md5; + auto mapTmp = Parser::parseArgs(auth_md5, ",", "="); + decltype(mapTmp) map; + for(auto &pr : mapTmp){ + map[trim(string(pr.first)," \"")] = trim(pr.second," \""); + } + //check realm + if(realm != map["realm"]){ + onAuthFailed(realm,StrPrinter << "realm not mached:" << realm << " != " << map["realm"]); + return ; + } + //check nonce + auto nonce = map["nonce"]; + if(_auth_nonce != nonce){ + onAuthFailed(realm,StrPrinter << "nonce not mached:" << nonce << " != " << _auth_nonce); + return ; + } + //check username and uri + auto username = map["username"]; + auto uri = map["uri"]; + auto response = map["response"]; + if(username.empty() || uri.empty() || response.empty()){ + onAuthFailed(realm,StrPrinter << "username/uri/response empty:" << username << "," << uri << "," << response); + return ; + } + + auto realInvoker = [this,realm,nonce,uri,username,response](bool ignoreAuth,bool encrypted,const string &good_pwd){ + if(ignoreAuth){ + //忽略认证 + TraceP(this) << "auth ignored"; + onAuthSuccess(); + return; + } + /* + response计算方法如下: + RTSP客户端应该使用username + password并计算response如下: + (1)当password为MD5编码,则 + response = md5( password:nonce:md5(public_method:url) ); + (2)当password为ANSI字符串,则 + response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); + */ + auto encrypted_pwd = good_pwd; + if(!encrypted){ + //提供的是明文密码 + encrypted_pwd = MD5(username+ ":" + realm + ":" + good_pwd).hexdigest(); + } + + auto good_response = MD5( encrypted_pwd + ":" + nonce + ":" + MD5(string("DESCRIBE") + ":" + uri).hexdigest()).hexdigest(); + if(strcasecmp(good_response.data(),response.data()) == 0){ + //认证成功!md5不区分大小写 + onAuthSuccess(); + }else{ + //认证失败! + onAuthFailed(realm, StrPrinter << "password mismatch when md5 auth:" << good_response << " != " << response ); + } + }; + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + onAuth invoker = [realInvoker,weak_self](bool encrypted,const string &good_pwd){ + auto strong_self = weak_self.lock(); + if(!strong_self){ + return; + } + //切换到自己的线程确保realInvoker执行时,this指针有效 + strong_self->async([realInvoker,weak_self,encrypted,good_pwd](){ + auto strong_self = weak_self.lock(); + if(!strong_self){ + return; + } + realInvoker(false,encrypted,good_pwd); + }); + }; + + //此时可以提供明文或md5加密的密码 + if(!NOTICE_EMIT(BroadcastOnRtspAuthArgs, Broadcast::kBroadcastOnRtspAuth, _media_info, realm, username, false, invoker, *this)){ + //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 + WarnP(this) << "请监听kBroadcastOnRtspAuth事件!"; + //但是我们还是忽略认证以便完成播放 + realInvoker(true,true,""); + } +} + +void RtspSession::onAuthUser(const string &realm,const string &authorization){ + if(authorization.empty()){ + onAuthFailed(realm,"", false); + return; + } + //请求中包含认证信息 + auto authType = findSubString(authorization.data(), NULL, " "); + auto authStr = findSubString(authorization.data(), " ", NULL); + if(authType.empty() || authStr.empty()){ + //认证信息格式不合法,回复401 Unauthorized + onAuthFailed(realm,"can not find auth type or auth string"); + return; + } + if(authType == "Basic"){ + //base64认证,需要明文密码 + onAuthBasic(realm,authStr); + }else if(authType == "Digest"){ + //md5认证 + onAuthDigest(realm,authStr); + }else{ + //其他认证方式?不支持! + onAuthFailed(realm,StrPrinter << "unsupported auth type:" << authType); + } +} + +void RtspSession::send_StreamNotFound() { + sendRtspResponse("404 Stream Not Found",{"Connection","Close"}); +} + +void RtspSession::send_UnsupportedTransport() { + sendRtspResponse("461 Unsupported Transport",{"Connection","Close"}); +} + +void RtspSession::send_SessionNotFound() { + sendRtspResponse("454 Session Not Found",{"Connection","Close"}); +} + +void RtspSession::handleReq_Setup(const Parser &parser) { + //处理setup命令,该函数可能进入多次 + int trackIdx = getTrackIndexByControlUrl(parser.fullUrl()); + SdpTrack::Ptr &trackRef = _sdp_track[trackIdx]; + if (trackRef->_inited) { + //已经初始化过该Track + throw SockException(Err_shutdown, "can not setup one track twice"); + } + + static auto getRtpTypeStr = [](const int type) { + switch (type) + { + case Rtsp::RTP_TCP: + return "TCP"; + case Rtsp::RTP_UDP: + return "UDP"; + case Rtsp::RTP_MULTICAST: + return "MULTICAST"; + default: + return "Invalid"; + } + }; + + if (_rtp_type == Rtsp::RTP_Invalid) { + auto &strTransport = parser["Transport"]; + auto rtpType = Rtsp::RTP_Invalid; + if (strTransport.find("TCP") != string::npos) { + rtpType = Rtsp::RTP_TCP; + } else if (strTransport.find("multicast") != string::npos) { + rtpType = Rtsp::RTP_MULTICAST; + } else { + rtpType = Rtsp::RTP_UDP; + } + //检查RTP传输类型限制 + GET_CONFIG(int, transport, Rtsp::kRtpTransportType); + if (transport != Rtsp::RTP_Invalid && transport != rtpType) { + WarnL << "rtsp client setup transport " << getRtpTypeStr(rtpType) << " but config force transport " << getRtpTypeStr(transport); + //配置限定RTSP传输方式,但是客户端握手方式不一致,返回461 + sendRtspResponse("461 Unsupported transport"); + return; + } + _rtp_type = rtpType; + } + + trackRef->_inited = true; //现在初始化 + + //允许接收rtp、rtcp包 + RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); + + switch (_rtp_type) { + case Rtsp::RTP_TCP: { + if (_push_src) { + // rtsp推流时,interleaved由推流者决定 + auto key_values = Parser::parseArgs(parser["Transport"], ";", "="); + int interleaved_rtp = -1, interleaved_rtcp = -1; + if (2 == sscanf(key_values["interleaved"].data(), "%d-%d", &interleaved_rtp, &interleaved_rtcp)) { + trackRef->_interleaved = interleaved_rtp; + } else { + throw SockException(Err_shutdown, "can not find interleaved when setup of rtp over tcp"); + } + } else { + // rtsp播放时,由于数据共享分发,所以interleaved必须由服务器决定 + trackRef->_interleaved = 2 * trackRef->_type; + } + sendRtspResponse("200 OK", + {"Transport", StrPrinter << "RTP/AVP/TCP;unicast;" + << "interleaved=" << (int) trackRef->_interleaved << "-" + << (int) trackRef->_interleaved + 1 << ";" + << "ssrc=" << printSSRC(trackRef->_ssrc), + "x-Transport-Options", "late-tolerance=1.400000", + "x-Dynamic-Rate", "1" + }); + } + break; + + case Rtsp::RTP_UDP: { + std::pair pr = std::make_pair(createSocket(),createSocket()); + try { + makeSockPair(pr, get_local_ip()); + } catch (std::exception &ex) { + //分配端口失败 + send_NotAcceptable(); + throw SockException(Err_shutdown, ex.what()); + } + + _rtp_socks[trackIdx] = pr.first; + _rtcp_socks[trackIdx] = pr.second; + + //设置客户端内网端口信息 + string strClientPort = findSubString(parser["Transport"].data(), "client_port=", NULL); + uint16_t ui16RtpPort = atoi(findSubString(strClientPort.data(), NULL, "-").data()); + uint16_t ui16RtcpPort = atoi(findSubString(strClientPort.data(), "-", NULL).data()); + + auto peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtpPort); + //设置rtp发送目标地址 + pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); + + //设置rtcp发送目标地址 + peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtcpPort); + pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); + + //尝试获取客户端nat映射地址 + startListenPeerUdpData(trackIdx); + //InfoP(this) << "分配端口:" << srv_port; + + sendRtspResponse("200 OK", + {"Transport", StrPrinter << "RTP/AVP/UDP;unicast;" + << "client_port=" << strClientPort << ";" + << "server_port=" << pr.first->get_local_port() << "-" + << pr.second->get_local_port() << ";" + << "ssrc=" << printSSRC(trackRef->_ssrc) + }); + } + break; + case Rtsp::RTP_MULTICAST: { + if(!_multicaster){ + _multicaster = RtpMultiCaster::get(*this, get_local_ip(), _media_info, _multicast_ip, _multicast_video_port, _multicast_audio_port); + if (!_multicaster) { + send_NotAcceptable(); + throw SockException(Err_shutdown, "can not get a available udp multicast socket"); + } + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _multicaster->setDetachCB(this, [weak_self]() { + auto strong_self = weak_self.lock(); + if(!strong_self) { + return; + } + strong_self->safeShutdown(SockException(Err_shutdown,"ring buffer detached")); + }); + } + int iSrvPort = _multicaster->getMultiCasterPort(trackRef->_type); + //我们用trackIdx区分rtp和rtcp包 + //由于组播udp端口是共享的,而rtcp端口为组播udp端口+1,所以rtcp端口需要改成共享端口 + auto pSockRtcp = UDPServer::Instance().getSock(*this, get_local_ip().data(), 2 * trackIdx + 1, iSrvPort + 1); + if (!pSockRtcp) { + //分配端口失败 + send_NotAcceptable(); + throw SockException(Err_shutdown, "open shared rtcp socket failed"); + } + startListenPeerUdpData(trackIdx); + GET_CONFIG(uint32_t,udpTTL,MultiCast::kUdpTTL); + + sendRtspResponse("200 OK", + {"Transport", StrPrinter << "RTP/AVP;multicast;" + << "destination=" << _multicaster->getMultiCasterIP() << ";" + << "source=" << get_local_ip() << ";" + << "port=" << iSrvPort << "-" << pSockRtcp->get_local_port() << ";" + << "ttl=" << udpTTL << ";" + << "ssrc=" << printSSRC(trackRef->_ssrc) + }); + } + break; + default: + break; + } +} + +void RtspSession::handleReq_Play(const Parser &parser) { + if (_sdp_track.empty() || parser["Session"] != _sessionid) { + send_SessionNotFound(); + throw SockException(Err_shutdown, _sdp_track.empty() ? "can not find any available track when play" : "session not found when play"); + } + auto play_src = _play_src.lock(); + if(!play_src){ + send_StreamNotFound(); + shutdown(SockException(Err_shutdown,"rtsp stream released")); + return; + } + + bool use_gop = true; + auto &strScale = parser["Scale"]; + auto &strRange = parser["Range"]; + StrCaseMap res_header; + if (!strScale.empty()) { + //这是设置播放速度 + res_header.emplace("Scale", strScale); + auto speed = atof(strScale.data()); + play_src->speed(speed); + InfoP(this) << "rtsp set play speed:" << speed; + } + + if (!strRange.empty()) { + //这是seek操作 + res_header.emplace("Range", strRange); + auto strStart = findSubString(strRange.data(), "npt=", "-"); + if (strStart == "now") { + strStart = "0"; + } + auto iStartTime = 1000 * (float) atof(strStart.data()); + use_gop = !play_src->seekTo((uint32_t) iStartTime); + InfoP(this) << "rtsp seekTo(ms):" << iStartTime; + } + + vector inited_tracks; + _StrPrinter rtp_info; + for (auto &track : _sdp_track) { + if (track->_inited == false) { + //为支持播放器播放单一track, 不校验没有发setup的track + continue; + } + inited_tracks.emplace_back(track->_type); + track->_ssrc = play_src->getSsrc(track->_type); + track->_seq = play_src->getSeqence(track->_type); + track->_time_stamp = play_src->getTimeStamp(track->_type); + + rtp_info << "url=" << track->getControlUrl(_content_base) << ";" + << "seq=" << track->_seq << ";" + << "rtptime=" << (int64_t)(track->_time_stamp) * (int64_t)(track->_samplerate/ 1000) << ","; + } + + rtp_info.pop_back(); + + res_header.emplace("RTP-Info", rtp_info); + //已存在Range时不覆盖 + res_header.emplace("Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << play_src->getTimeStamp(TrackInvalid) / 1000.0); + sendRtspResponse("200 OK", res_header); + + //设置播放track + if (inited_tracks.size() == 1) { + _target_play_track = inited_tracks[0]; + InfoP(this) << "指定播放track:" << _target_play_track; + } + + //在回复rtsp信令后再恢复播放 + play_src->pause(false); + + setSocketFlags(); + + if (!_play_reader && _rtp_type != Rtsp::RTP_MULTICAST) { + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _play_reader = play_src->getRing()->attach(getPoller(), use_gop); + _play_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); + _play_reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->shutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); + }); + _play_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pack) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->sendRtpPacket(pack); + }); + } +} + +void RtspSession::handleReq_Pause(const Parser &parser) { + if (parser["Session"] != _sessionid) { + send_SessionNotFound(); + throw SockException(Err_shutdown, "session not found when pause"); + } + + sendRtspResponse("200 OK"); + auto play_src = _play_src.lock(); + if (play_src) { + play_src->pause(true); + } +} + +void RtspSession::handleReq_Teardown(const Parser &parser) { + _push_src = nullptr; + //此时回复可能触发broken pipe事件,从而直接触发onError回调;所以需要先把_push_src置空,防止触发断流续推功能 + sendRtspResponse("200 OK"); + throw SockException(Err_shutdown,"recv teardown request"); +} + +void RtspSession::handleReq_Get(const Parser &parser) { + _http_x_sessioncookie = parser["x-sessioncookie"]; + sendRtspResponse("200 OK", + {"Cache-Control","no-store", + "Pragma","no-store", + "Content-Type","application/x-rtsp-tunnelled", + },"","HTTP/1.0"); + + //注册http getter,以便http poster绑定 + lock_guard lock(g_mtxGetter); + g_mapGetter[_http_x_sessioncookie] = static_pointer_cast(shared_from_this()); +} + +void RtspSession::handleReq_Post(const Parser &parser) { + lock_guard lock(g_mtxGetter); + string sessioncookie = parser["x-sessioncookie"]; + //Poster 找到 Getter + auto it = g_mapGetter.find(sessioncookie); + if (it == g_mapGetter.end()) { + throw SockException(Err_shutdown,"can not find http getter by x-sessioncookie"); + } + + //Poster 找到Getter的SOCK + auto httpGetterWeak = it->second; + //移除http getter的弱引用记录 + g_mapGetter.erase(sessioncookie); + + //http poster收到请求后转发给http getter处理 + _on_recv = [this,httpGetterWeak](const Buffer::Ptr &buf){ + auto httpGetterStrong = httpGetterWeak.lock(); + if(!httpGetterStrong){ + shutdown(SockException(Err_shutdown,"http getter released")); + return; + } + + //切换到http getter的线程 + httpGetterStrong->async([buf,httpGetterWeak](){ + auto httpGetterStrong = httpGetterWeak.lock(); + if(!httpGetterStrong){ + return; + } + httpGetterStrong->onRecv(std::make_shared(decodeBase64(string(buf->data(), buf->size())))); + }); + }; + + if(!parser.content().empty()){ + //http poster后面的粘包 + _on_recv(std::make_shared(parser.content())); + } + + sendRtspResponse("200 OK", + {"Cache-Control","no-store", + "Pragma","no-store", + "Content-Type","application/x-rtsp-tunnelled", + },"","HTTP/1.0"); +} + +void RtspSession::handleReq_SET_PARAMETER(const Parser &parser) { + //TraceP(this) <onWrite(std::move(rtp), false); + } else { + WarnL << "Not a rtsp push!"; + } +} + +void RtspSession::onRcvPeerUdpData(int interleaved, const Buffer::Ptr &buf, const struct sockaddr_storage &addr) { + //这是rtcp心跳包,说明播放器还存活 + _alive_ticker.resetTime(); + + if (interleaved % 2 == 0) { + if (_push_src) { + //这是rtsp推流上来的rtp包 + auto &ref = _sdp_track[interleaved / 2]; + handleOneRtp(interleaved / 2, ref->_type, ref->_samplerate, (uint8_t *) buf->data(), buf->size()); + } else if (!_udp_connected_flags.count(interleaved)) { + //这是rtsp播放器的rtp打洞包 + _udp_connected_flags.emplace(interleaved); + if (_rtp_socks[interleaved / 2]) { + _rtp_socks[interleaved / 2]->bindPeerAddr((struct sockaddr *)&addr); + } + } + } else { + //rtcp包 + if (!_udp_connected_flags.count(interleaved)) { + _udp_connected_flags.emplace(interleaved); + if (_rtcp_socks[(interleaved - 1) / 2]) { + _rtcp_socks[(interleaved - 1) / 2]->bindPeerAddr((struct sockaddr *)&addr); + } + } + onRtcpPacket((interleaved - 1) / 2, _sdp_track[(interleaved - 1) / 2], buf->data(), buf->size()); + } +} + +void RtspSession::startListenPeerUdpData(int track_idx) { + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + auto peer_ip = get_peer_ip(); + auto onUdpData = [weak_self,peer_ip](const Buffer::Ptr &buf, struct sockaddr *peer_addr, int interleaved){ + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + + if (SockUtil::inet_ntoa(peer_addr) != peer_ip) { + WarnP(strong_self.get()) << ((interleaved % 2 == 0) ? "收到其他地址的rtp数据:" : "收到其他地址的rtcp数据:") + << SockUtil::inet_ntoa(peer_addr); + return true; + } + + struct sockaddr_storage addr = *((struct sockaddr_storage *)peer_addr); + strong_self->async([weak_self, buf, addr, interleaved]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + try { + strong_self->onRcvPeerUdpData(interleaved, buf, addr); + } catch (SockException &ex) { + strong_self->shutdown(ex); + } catch (std::exception &ex) { + strong_self->shutdown(SockException(Err_other, ex.what())); + } + }); + return true; + }; + + switch (_rtp_type){ + case Rtsp::RTP_MULTICAST:{ + //组播使用的共享rtcp端口 + UDPServer::Instance().listenPeer(get_peer_ip().data(), this, + [onUdpData]( int interleaved, const Buffer::Ptr &buf, struct sockaddr *peer_addr) { + return onUdpData(buf, peer_addr, interleaved); + }); + } + break; + case Rtsp::RTP_UDP:{ + auto setEvent = [&](Socket::Ptr &sock,int interleaved){ + if(!sock){ + WarnP(this) << "udp端口为空:" << interleaved; + return; + } + sock->setOnRead([onUdpData,interleaved](const Buffer::Ptr &pBuf, struct sockaddr *pPeerAddr , int addr_len){ + onUdpData(pBuf, pPeerAddr, interleaved); + }); + }; + setEvent(_rtp_socks[track_idx], 2 * track_idx ); + setEvent(_rtcp_socks[track_idx], 2 * track_idx + 1 ); + } + break; + + default: + break; + } + +} + +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; +} + +bool RtspSession::sendRtspResponse(const string &res_code, const StrCaseMap &header_const, const string &sdp, const char *protocol){ + auto header = header_const; + header.emplace("CSeq",StrPrinter << _cseq); + if(!_sessionid.empty()){ + header.emplace("Session", _sessionid); + } + + header.emplace("Server",kServerName); + header.emplace("Date",dateStr()); + + if(!sdp.empty()){ + header.emplace("Content-Length",StrPrinter << sdp.size()); + header.emplace("Content-Type","application/sdp"); + } + + _StrPrinter printer; + printer << protocol << " " << res_code << "\r\n"; + for (auto &pr : header){ + printer << pr.first << ": " << pr.second << "\r\n"; + } + + printer << "\r\n"; + + if(!sdp.empty()){ + printer << sdp; + } +// DebugP(this) << printer; + return send(std::make_shared(std::move(printer))) > 0 ; +} + +ssize_t RtspSession::send(Buffer::Ptr pkt){ +// if(!_enableSendRtp){ +// DebugP(this) << pkt->data(); +// } + _bytes_usage += pkt->size(); + return Session::send(std::move(pkt)); +} + +bool RtspSession::sendRtspResponse(const string &res_code, const std::initializer_list &header, const string &sdp, const char *protocol) { + string key; + StrCaseMap header_map; + int i = 0; + for(auto &val : header){ + if(++i % 2 == 0){ + header_map.emplace(key,val); + }else{ + key = val; + } + } + return sendRtspResponse(res_code,header_map,sdp,protocol); +} + +int RtspSession::getTrackIndexByTrackType(TrackType type) { + for (size_t i = 0; i < _sdp_track.size(); ++i) { + if (type == _sdp_track[i]->_type) { + return i; + } + } + if (_sdp_track.size() == 1) { + return 0; + } + throw SockException(Err_shutdown, StrPrinter << "no such track with type:" << getTrackString(type)); +} + +int RtspSession::getTrackIndexByControlUrl(const string &control_url) { + for (size_t i = 0; i < _sdp_track.size(); ++i) { + if (control_url == _sdp_track[i]->getControlUrl(_content_base)) { + return i; + } + } + if (_sdp_track.size() == 1) { + return 0; + } + throw SockException(Err_shutdown, StrPrinter << "no such track with control url:" << control_url); +} + +int RtspSession::getTrackIndexByInterleaved(int interleaved) { + for (size_t i = 0; i < _sdp_track.size(); ++i) { + if (_sdp_track[i]->_interleaved == interleaved) { + return i; + } + } + if (_sdp_track.size() == 1) { + return 0; + } + throw SockException(Err_shutdown, StrPrinter << "no such track with interleaved:" << interleaved); +} + +bool RtspSession::close(MediaSource &sender) { + //此回调在其他线程触发 + string err = StrPrinter << "close media: " << sender.getUrl(); + safeShutdown(SockException(Err_shutdown,err)); + return true; +} + +int RtspSession::totalReaderCount(MediaSource &sender) { + return _push_src ? _push_src->totalReaderCount() : sender.readerCount(); +} + +MediaOriginType RtspSession::getOriginType(MediaSource &sender) const{ + return MediaOriginType::rtsp_push; +} + +string RtspSession::getOriginUrl(MediaSource &sender) const { + return _media_info.full_url; +} + +std::shared_ptr RtspSession::getOriginSock(MediaSource &sender) const { + return const_cast(this)->shared_from_this(); +} + +toolkit::EventPoller::Ptr RtspSession::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + +void RtspSession::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_index){ + updateRtcpContext(rtp); +} + +void RtspSession::updateRtcpContext(const RtpPacket::Ptr &rtp){ + int track_index = getTrackIndexByTrackType(rtp->type); + auto &rtcp_ctx = _rtcp_context[track_index]; + rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); + if (!rtp->ntp_stamp && !rtp->getStamp()) { + // 忽略时间戳都为0的rtp + return; + } + + auto &ticker = _rtcp_send_tickers[track_index]; + //send rtcp every 5 second + if (ticker.elapsedTime() > 5 * 1000 || (_send_sr_rtcp[track_index] && !_push_src)) { + //确保在发送rtp前,先发送一次sender report rtcp(用于播放器同步音视频) + ticker.resetTime(); + _send_sr_rtcp[track_index] = false; + + static auto send_rtcp = [](RtspSession *thiz, int index, Buffer::Ptr ptr) { + if (thiz->_rtp_type == Rtsp::RTP_TCP) { + auto &track = thiz->_sdp_track[index]; + thiz->send(makeRtpOverTcpPrefix((uint16_t)(ptr->size()), track->_interleaved + 1)); + thiz->send(std::move(ptr)); + } else { + thiz->_rtcp_socks[index]->send(std::move(ptr)); + } + }; + + auto ssrc = rtp->getSSRC(); + auto rtcp = _push_src ? rtcp_ctx->createRtcpRR(ssrc + 1, ssrc) : rtcp_ctx->createRtcpSR(ssrc); + auto rtcp_sdes = RtcpSdes::create({kServerName}); + rtcp_sdes->chunks.type = (uint8_t)SdesType::RTCP_SDES_CNAME; + rtcp_sdes->chunks.ssrc = htonl(ssrc); + send_rtcp(this, track_index, std::move(rtcp)); + send_rtcp(this, track_index, RtcpHeader::toBuffer(rtcp_sdes)); + } +} + +void RtspSession::sendRtpPacket(const RtspMediaSource::RingDataType &pkt) { + switch (_rtp_type) { + case Rtsp::RTP_TCP: { + setSendFlushFlag(false); + pkt->for_each([&](const RtpPacket::Ptr &rtp) { + if (_target_play_track == TrackInvalid || _target_play_track == rtp->type) { + updateRtcpContext(rtp); + send(rtp); + } + }); + flushAll(); + setSendFlushFlag(true); + } + break; + case Rtsp::RTP_UDP: { + //下标0表示视频,1表示音频 + Socket::Ptr rtp_socks[2]; + rtp_socks[TrackVideo] = _rtp_socks[getTrackIndexByTrackType(TrackVideo)]; + rtp_socks[TrackAudio] = _rtp_socks[getTrackIndexByTrackType(TrackAudio)]; + pkt->for_each([&](const RtpPacket::Ptr &rtp) { + if (_target_play_track == TrackInvalid || _target_play_track == rtp->type) { + updateRtcpContext(rtp); + auto &sock = rtp_socks[rtp->type]; + if (!sock) { + shutdown(SockException(Err_shutdown, "udp sock not opened yet")); + return; + } + _bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize; + sock->send(std::make_shared(rtp, RtpPacket::kRtpTcpHeaderSize), nullptr, 0, false); + } + }); + for (auto &sock : rtp_socks) { + if (sock) { + sock->flushAll(); + } + } + } + break; + default: + break; + } +} + +void RtspSession::setSocketFlags(){ + GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS); + if(mergeWriteMS > 0) { + //推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 + SockUtil::setNoDelay(getSock()->rawFD(), false); + //播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 + setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); + } +} + +} +/* namespace mediakit */ diff --git a/MediaServer/Rtsp/RtspSession.h b/MediaServer/Rtsp/RtspSession.h new file mode 100644 index 0000000..f985fd3 --- /dev/null +++ b/MediaServer/Rtsp/RtspSession.h @@ -0,0 +1,284 @@ +/* + * 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 SESSION_RTSPSESSION_H_ +#define SESSION_RTSPSESSION_H_ + +#include +#include +#include +#include "Network/Session.h" +#include "RtspSplitter.h" +#include "RtpReceiver.h" +#include "Rtcp/RtcpContext.h" +#include "RtspMediaSource.h" +#include "RtspMediaSourceImp.h" +#include "RtpMultiCaster.h" + +namespace mediakit { + +using BufferRtp = toolkit::BufferOffset; +class RtspSession : public toolkit::Session, public RtspSplitter, public RtpReceiver, public MediaSourceEvent { +public: + using Ptr = std::shared_ptr; + using onGetRealm = std::function; + // encrypted为true是则表明是md5加密的密码,否则是明文密码 [AUTO-TRANSLATED:cad96e51] + // `encrypted` being `true` indicates an MD5 encrypted password, otherwise it is a plain text password + // 在请求明文密码时如果提供md5密码者则会导致认证失败 [AUTO-TRANSLATED:8a38bff8] + // When requesting a plain text password, providing an MD5 password will result in authentication failure + using onAuth = std::function; + + RtspSession(const toolkit::Socket::Ptr &sock); + ////Session override//// + void onRecv(const toolkit::Buffer::Ptr &buf) override; + void onError(const toolkit::SockException &err) override; + void onManager() override; + +protected: + /////RtspSplitter override///// + // 收到完整的rtsp包回调,包括sdp等content数据 [AUTO-TRANSLATED:efbe20df] + // Callback for receiving a complete RTSP packet, including SDP and other content data + void onWholeRtspPacket(Parser &parser) override; + // 收到rtp包回调 [AUTO-TRANSLATED:119f1cca] + // Callback for receiving an RTP packet + void onRtpPacket(const char *data, size_t len) override; + // 从rtsp头中获取Content长度 [AUTO-TRANSLATED:0e6f033e] + // Get the Content length from the RTSP header + ssize_t getContentLength(Parser &parser) override; + + ////RtpReceiver override//// + void onRtpSorted(RtpPacket::Ptr rtp, int track_idx) override; + void onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_index) override; + + ///////MediaSourceEvent override/////// + // 关闭 [AUTO-TRANSLATED:92392f02] + // Close + bool close(MediaSource &sender) override; + // 播放总人数 [AUTO-TRANSLATED:c42a3161] + // Total number of players + int totalReaderCount(MediaSource &sender) override; + // 获取媒体源类型 [AUTO-TRANSLATED:34290a69] + // Get the media source type + MediaOriginType getOriginType(MediaSource &sender) const override; + // 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795] + // Get the media source URL or file path + std::string getOriginUrl(MediaSource &sender) const override; + // 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910] + // Get the media source client related information + std::shared_ptr getOriginSock(MediaSource &sender) const override; + // 由于支持断连续推,存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40] + // Due to support for continuous pushing, there is a possibility of OwnerPoller changes + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + + /////Session override//// + ssize_t send(toolkit::Buffer::Ptr pkt) override; + // 收到RTCP包回调 [AUTO-TRANSLATED:249f4807] + // Callback for receiving an RTCP packet + virtual void onRtcpPacket(int track_idx, SdpTrack::Ptr &track, const char *data, size_t len); + + // 回复客户端 [AUTO-TRANSLATED:8108ebea] + // Reply to the client + virtual bool sendRtspResponse(const std::string &res_code, const StrCaseMap &header = StrCaseMap(), const std::string &sdp = "", const char *protocol = "RTSP/1.0"); + +protected: + // url解析后保存的相关信息 [AUTO-TRANSLATED:1c26e4e3] + // Information related to the URL after parsing + MediaInfo _media_info; + + ////////RTP over udp_multicast//////// + // 共享的rtp组播对象 [AUTO-TRANSLATED:d4a5cfdd] + // Shared RTP multicast object + RtpMultiCaster::Ptr _multicaster; + + // Session号 [AUTO-TRANSLATED:4552ec74] + // Session number + std::string _sessionid; + + uint32_t _multicast_ip = 0; + uint16_t _multicast_video_port = 0; + uint16_t _multicast_audio_port = 0; + +private: + // 处理options方法,获取服务器能力 [AUTO-TRANSLATED:a51f6d7c] + // Handle the OPTIONS method, get server capabilities + void handleReq_Options(const Parser &parser); + // 处理describe方法,请求服务器rtsp sdp信息 [AUTO-TRANSLATED:ed2c8fcb] + // Handle the DESCRIBE method, request server RTSP SDP information + void handleReq_Describe(const Parser &parser); + // 处理ANNOUNCE方法,请求推流,附带sdp [AUTO-TRANSLATED:aa4b4517] + // Handle the ANNOUNCE method, request streaming, with SDP attached + void handleReq_ANNOUNCE(const Parser &parser); + // 处理record方法,开始推流 [AUTO-TRANSLATED:885cf8a9] + // Handle the RECORD method, start streaming + void handleReq_RECORD(const Parser &parser); + // 处理setup方法,播放和推流协商rtp传输方式用 [AUTO-TRANSLATED:cbe5dcfc] + // Handle the SETUP method, used for negotiating RTP transport methods for playback and streaming + void handleReq_Setup(const Parser &parser); + // 处理play方法,开始或恢复播放 [AUTO-TRANSLATED:f15d151d] + // Handle the PLAY method, start or resume playback + void handleReq_Play(const Parser &parser); + // 处理pause方法,暂停播放 [AUTO-TRANSLATED:0c3b8f79] + // Handle the PAUSE method, pause playback + void handleReq_Pause(const Parser &parser); + // 处理teardown方法,结束播放 [AUTO-TRANSLATED:64d82572] + // Handle the TEARDOWN method, end playback + void handleReq_Teardown(const Parser &parser); + // 处理Get方法,rtp over http才用到 [AUTO-TRANSLATED:c7c51eb6] + // Handle the GET method, only used for RTP over HTTP + void handleReq_Get(const Parser &parser); + // 处理Post方法,rtp over http才用到 [AUTO-TRANSLATED:228bdbbe] + // Handle the POST method, only used for RTP over HTTP + void handleReq_Post(const Parser &parser); + // 处理SET_PARAMETER、GET_PARAMETER方法,一般用于心跳 [AUTO-TRANSLATED:b9e333e1] + // Handle the SET_PARAMETER, GET_PARAMETER methods, generally used for heartbeats + void handleReq_SET_PARAMETER(const Parser &parser); + // rtsp资源未找到 [AUTO-TRANSLATED:9b779890] + // RTSP resource not found + void send_StreamNotFound(); + // 不支持的传输模式 [AUTO-TRANSLATED:ef90414c] + // Unsupported transport mode + void send_UnsupportedTransport(); + // 会话id错误 [AUTO-TRANSLATED:7cf632d3] + // Session ID error + void send_SessionNotFound(); + // 一般rtsp服务器打开端口失败时触发 [AUTO-TRANSLATED:82ecb043] + // Triggered when the general RTSP server fails to open the port + void send_NotAcceptable(); + // 获取track下标 [AUTO-TRANSLATED:36d0b2c2] + // Get the track index + int getTrackIndexByTrackType(TrackType type); + int getTrackIndexByControlUrl(const std::string &control_url); + int getTrackIndexByInterleaved(int interleaved); + // 一般用于接收udp打洞包,也用于rtsp推流 [AUTO-TRANSLATED:0b55c12f] + // Generally used to receive UDP hole punching packets, also used for RTSP pushing + void onRcvPeerUdpData(int interleaved, const toolkit::Buffer::Ptr &buf, const struct sockaddr_storage &addr); + // 配合onRcvPeerUdpData使用 [AUTO-TRANSLATED:811d2d1a] + // Used in conjunction with onRcvPeerUdpData + void startListenPeerUdpData(int track_idx); + // //rtsp专有认证相关//// [AUTO-TRANSLATED:0f021bb5] + // // RTSP specific authentication related //// + // 认证成功 [AUTO-TRANSLATED:e1bafff3] + // Authentication successful + void onAuthSuccess(); + // 认证失败 [AUTO-TRANSLATED:a188326a] + // Authentication failed + void onAuthFailed(const std::string &realm, const std::string &why, bool close = true); + // 开始走rtsp专有认证流程 [AUTO-TRANSLATED:2d773497] + // Start the RTSP specific authentication process + void onAuthUser(const std::string &realm, const std::string &authorization); + // 校验base64方式的认证加密 [AUTO-TRANSLATED:bde8662f] + // Verify base64 authentication encryption + void onAuthBasic(const std::string &realm, const std::string &auth_base64); + // 校验md5方式的认证加密 [AUTO-TRANSLATED:0cc37fa7] + // Verify MD5 authentication encryption + void onAuthDigest(const std::string &realm, const std::string &auth_md5); + // 触发url鉴权事件 [AUTO-TRANSLATED:776dc4b5] + // Trigger URL authentication event + void emitOnPlay(); + // 发送rtp给客户端 [AUTO-TRANSLATED:18602be0] + // Send RTP to the client + void sendRtpPacket(const RtspMediaSource::RingDataType &pkt); + // 触发rtcp发送 [AUTO-TRANSLATED:4fbe7706] + // Trigger RTCP sending + void updateRtcpContext(const RtpPacket::Ptr &rtp); + // 回复客户端 [AUTO-TRANSLATED:8108ebea] + // Reply to the client + bool sendRtspResponse(const std::string &res_code, const std::initializer_list &header, const std::string &sdp = "", const char *protocol = "RTSP/1.0"); + + // 设置socket标志 [AUTO-TRANSLATED:4086e686] + // Set socket flag + void setSocketFlags(); + +private: + // 是否已经触发on_play事件 [AUTO-TRANSLATED:49c937ce] + // Whether the on_play event has been triggered + bool _emit_on_play = false; + bool _send_sr_rtcp[2] = {true, true}; + // 断连续推延时 [AUTO-TRANSLATED:13ad578a] + // Delay in continuous pushing + uint32_t _continue_push_ms = 0; + // 推流或拉流客户端采用的rtp传输方式 [AUTO-TRANSLATED:27411079] + // RTP transport method used by the pushing or pulling client + Rtsp::eRtpType _rtp_type = Rtsp::RTP_Invalid; + // 收到的seq,回复时一致 [AUTO-TRANSLATED:64544fb4] + // Received seq, consistent when replying + int _cseq = 0; + // 消耗的总流量 [AUTO-TRANSLATED:45ad2785] + // Total traffic consumed + uint64_t _bytes_usage = 0; + //ContentBase + std::string _content_base; + // 记录是否需要rtsp专属鉴权,防止重复触发事件 [AUTO-TRANSLATED:9cff90b9] + // Record whether RTSP specific authentication is required to prevent duplicate event triggering + std::string _rtsp_realm; + // 登录认证 [AUTO-TRANSLATED:43fdb875] + // Login authentication + std::string _auth_nonce; + // 用于判断客户端是否超时 [AUTO-TRANSLATED:86cb328a] + // Used to determine if the client has timed out + toolkit::Ticker _alive_ticker; + + // rtsp推流相关绑定的源 [AUTO-TRANSLATED:a25078d9] + // Source bound to RTSP pushing + RtspMediaSourceImp::Ptr _push_src; + // 推流器所有权 [AUTO-TRANSLATED:e47b4bcb] + // Pusher ownership + std::shared_ptr _push_src_ownership; + // rtsp播放器绑定的直播源 [AUTO-TRANSLATED:a7e130b5] + // Live source bound to the RTSP player + std::weak_ptr _play_src; + // 直播源读取器 [AUTO-TRANSLATED:e1edc193] + // Live source reader + RtspMediaSource::RingType::RingReader::Ptr _play_reader; + // sdp里面有效的track,包含音频或视频 [AUTO-TRANSLATED:64e2fcdf] + // Valid track in SDP, including audio or video + std::vector _sdp_track; + // 播放器setup指定的播放track,默认为TrackInvalid表示不指定即音视频都推 [AUTO-TRANSLATED:c7a0df5e] + // Track specified by the player setup, default is TrackInvalid, which means no specification, both audio and video are pushed + TrackType _target_play_track = TrackInvalid; + + ////////RTP over udp//////// + // RTP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:77c186bb] + // RTP port, trackid idx is the array index + toolkit::Socket::Ptr _rtp_socks[2]; + // RTCP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:446a7861] + // RTCP port, trackid idx is the array index + toolkit::Socket::Ptr _rtcp_socks[2]; + // 标记是否收到播放的udp打洞包,收到播放的udp打洞包后才能知道其外网udp端口号 [AUTO-TRANSLATED:ad039c25] + // Flag whether the UDP hole punching packet for playback has been received. The external UDP port number can only be known after receiving the UDP hole punching packet for playback. + std::unordered_set _udp_connected_flags; + ////////RTSP over HTTP //////// + // quicktime 请求rtsp会产生两次tcp连接, [AUTO-TRANSLATED:3f72e181] + // QuickTime requests for RTSP will generate two TCP connections, + // 一次发送 get 一次发送post,需要通过x-sessioncookie关联起来 [AUTO-TRANSLATED:f29a653f] + // one for sending GET and one for sending POST. They need to be associated through x-sessioncookie. + std::string _http_x_sessioncookie; + std::function _on_recv; + ////////// rtcp //////////////// + // rtcp发送时间,trackid idx 为数组下标 [AUTO-TRANSLATED:bf3248b1] + // RTCP send time, trackid idx is the array index + toolkit::Ticker _rtcp_send_tickers[2]; + // 统计rtp并发送rtcp [AUTO-TRANSLATED:0ac2b665] + // Count RTP and send RTCP + std::vector _rtcp_context; +}; + +/** + * 支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 + * RTSP server supporting SSL encryption, which can be used for devices such as Amazon Echo Show to access. + + + * [AUTO-TRANSLATED:7d1eed83] + */ +using RtspSessionWithSSL = toolkit::SessionWithSSL; + +} /* namespace mediakit */ + +#endif /* SESSION_RTSPSESSION_H_ */ diff --git a/MediaServer/Rtsp/RtspSplitter.cpp b/MediaServer/Rtsp/RtspSplitter.cpp new file mode 100644 index 0000000..50bfbb9 --- /dev/null +++ b/MediaServer/Rtsp/RtspSplitter.cpp @@ -0,0 +1,120 @@ +/* + * 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 +#include "RtspSplitter.h" +#include "Util/util.h" +#include "Util/logger.h" +#include "Common/macros.h" +#include "Rtsp/RtpReceiver.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +const char *RtspSplitter::onSearchPacketTail(const char *data, size_t len) { + auto ret = onSearchPacketTail_l(data, len); + if(ret){ + return ret; + } + + if (len > 256 * 1024) { + // rtp大于256KB [AUTO-TRANSLATED:d8d8a481] + // rtp is greater than 256KB + ret = (char *) memchr(data, '$', len); + if (!ret) { + WarnL << "rtp缓存溢出:" << hexdump(data, 1024); + reset(); + } + } + return ret; +} + +const char *RtspSplitter::onSearchPacketTail_l(const char *data, size_t len) { + if(!_enableRecvRtp || data[0] != '$'){ + // 这是rtsp包 [AUTO-TRANSLATED:cdc2211d] + // This is an rtsp packet + _isRtpPacket = false; + return HttpRequestSplitter::onSearchPacketTail(data, len); + } + // 这是rtp包 [AUTO-TRANSLATED:627d4881] + // This is an rtp packet + if(len < 4){ + // 数据不够 [AUTO-TRANSLATED:72802244] + // Not enough data + return nullptr; + } + uint16_t length = (((uint8_t *)data)[2] << 8) | ((uint8_t *)data)[3]; + if(len < (size_t)(length + 4)){ + // 数据不够 [AUTO-TRANSLATED:72802244] + // Not enough data + return nullptr; + } + // 返回rtp包末尾 [AUTO-TRANSLATED:2546d5ce] + // Return the end of the rtp packet + _isRtpPacket = true; + return data + 4 + length; +} + +ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len) { + if (_isRtpPacket) { + try { + onRtpPacket(data, len); + } catch (RtpTrack::BadRtpException &ex) { + WarnL << ex.what(); + } + return 0; + } + if (len == 4 && !memcmp(data, "\r\n\r\n", 4)) { + return 0; + } + try { + _parser.parse(data, len); + } catch (toolkit::AssertFailedException &ex){ + if (!_enableRecvRtp) { + // 还在握手中,直接中断握手 [AUTO-TRANSLATED:19dfdf7a] + // Still in handshake, interrupt handshake directly + throw; + } + // 握手已经结束,如果rtsp server存在发送缓存溢出的bug,那么rtsp信令可能跟rtp混在一起 [AUTO-TRANSLATED:56c28270] + // Handshake has ended, if rtsp server has a send buffer overflow bug, then rtsp signaling may be mixed with rtp + // 这种情况下,rtsp信令解析异常不中断链接,只丢弃这个包 [AUTO-TRANSLATED:93cd60b4] + // In this case, rtsp signaling parsing exception does not interrupt the connection, just discard this packet + WarnL << ex.what(); + return 0; + } + auto ret = getContentLength(_parser); + if (ret == 0) { + onWholeRtspPacket(_parser); + _parser.clear(); + } + return ret; +} + +void RtspSplitter::onRecvContent(const char *data, size_t len) { + _parser.setContent(string(data,len)); + onWholeRtspPacket(_parser); + _parser.clear(); +} + +void RtspSplitter::enableRecvRtp(bool enable) { + _enableRecvRtp = enable; +} + +ssize_t RtspSplitter::getContentLength(Parser &parser) { + return atoi(parser["Content-Length"].data()); +} + + +}//namespace mediakit + + + diff --git a/MediaServer/Rtsp/RtspSplitter.h b/MediaServer/Rtsp/RtspSplitter.h new file mode 100644 index 0000000..7e3a06d --- /dev/null +++ b/MediaServer/Rtsp/RtspSplitter.h @@ -0,0 +1,82 @@ +/* + * 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_RTSPSPLITTER_H +#define ZLMEDIAKIT_RTSPSPLITTER_H + +#include "Common/Parser.h" +#include "Http/HttpRequestSplitter.h" + +namespace mediakit{ + +class RtspSplitter : public HttpRequestSplitter{ +public: + /** + * 是否允许接收rtp包 + * @param enable + * Whether to allow receiving rtp packets + * @param enable + + * [AUTO-TRANSLATED:8de8e1ee] + */ + void enableRecvRtp(bool enable); +protected: + /** + * 收到完整的rtsp包回调,包括sdp等content数据 + * @param parser rtsp包 + * Callback for receiving a complete rtsp packet, including sdp and other content data + * @param parser rtsp packet + + * [AUTO-TRANSLATED:4d3c2056] + */ + virtual void onWholeRtspPacket(Parser &parser) = 0; + + /** + * 收到rtp包回调 + * @param data + * @param len + * Callback for receiving rtp packets + * @param data + * @param len + + * [AUTO-TRANSLATED:c8f7c9bb] + */ + virtual void onRtpPacket(const char *data,size_t len) = 0; + + /** + * 从rtsp头中获取Content长度 + * @param parser + * @return + * Get the Content length from the rtsp header + * @param parser + * @return + + + * [AUTO-TRANSLATED:f0bc1fb8] + */ + virtual ssize_t getContentLength(Parser &parser); + +protected: + const char *onSearchPacketTail(const char *data,size_t len) override ; + const char *onSearchPacketTail_l(const char *data,size_t len) ; + ssize_t onRecvHeader(const char *data,size_t len) override; + void onRecvContent(const char *data,size_t len) override; + +private: + bool _enableRecvRtp = false; + bool _isRtpPacket = false; + Parser _parser; +}; + +}//namespace mediakit + + + +#endif //ZLMEDIAKIT_RTSPSPLITTER_H diff --git a/MediaServer/Rtsp/UDPServer.cpp b/MediaServer/Rtsp/UDPServer.cpp new file mode 100644 index 0000000..1ab42e6 --- /dev/null +++ b/MediaServer/Rtsp/UDPServer.cpp @@ -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. + */ + +#include "UDPServer.h" +#include "Util/TimeTicker.h" +#include "Util/onceToken.h" + +using namespace toolkit; +using namespace std; + +namespace mediakit { + +INSTANCE_IMP(UDPServer); + +UDPServer::UDPServer() { +} + +UDPServer::~UDPServer() { + InfoL; +} + +Socket::Ptr UDPServer::getSock(SocketHelper &helper, const char* local_ip, int interleaved, uint16_t local_port) { + lock_guard lck(_mtx_udp_sock); + string key = StrPrinter << local_ip << ":" << interleaved << endl; + auto it = _udp_sock_map.find(key); + if (it == _udp_sock_map.end()) { + Socket::Ptr sock = helper.createSocket(); + if (!sock->bindUdpSock(local_port, local_ip)) { + // 分配失败 [AUTO-TRANSLATED:a6c6a6e6] + // Allocation failed + return nullptr; + } + + sock->setOnErr(bind(&UDPServer::onErr, this, key, placeholders::_1)); + sock->setOnRead(bind(&UDPServer::onRecv, this, interleaved, placeholders::_1, placeholders::_2)); + _udp_sock_map[key] = sock; + DebugL << local_ip << " " << sock->get_local_port() << " " << interleaved; + return sock; + } + return it->second; +} + +void UDPServer::listenPeer(const char* peer_ip, void* obj, const onRecvData &cb) { + lock_guard lck(_mtx_on_recv); + auto &ref = _on_recv_map[peer_ip]; + ref.emplace(obj, cb); +} + +void UDPServer::stopListenPeer(const char* peer_ip, void* obj) { + lock_guard lck(_mtx_on_recv); + auto it0 = _on_recv_map.find(peer_ip); + if (it0 == _on_recv_map.end()) { + return; + } + auto &ref = it0->second; + auto it1 = ref.find(obj); + if (it1 != ref.end()) { + ref.erase(it1); + } + if (ref.size() == 0) { + _on_recv_map.erase(it0); + } +} + +void UDPServer::onErr(const string &key, const SockException &err) { + WarnL << err.what(); + lock_guard lck(_mtx_udp_sock); + _udp_sock_map.erase(key); +} + +void UDPServer::onRecv(int interleaved, const Buffer::Ptr &buf, struct sockaddr* peer_addr) { + string peer_ip = SockUtil::inet_ntoa(peer_addr); + lock_guard lck(_mtx_on_recv); + auto it0 = _on_recv_map.find(peer_ip); + if (it0 == _on_recv_map.end()) { + return; + } + auto &ref = it0->second; + for (auto it1 = ref.begin(); it1 != ref.end();) { + auto &func = it1->second; + if (!func(interleaved, buf, peer_addr)) { + it1 = ref.erase(it1); + } else { + ++it1; + } + } + if (ref.size() == 0) { + _on_recv_map.erase(it0); + } +} + +} /* namespace mediakit */ + + diff --git a/MediaServer/Rtsp/UDPServer.h b/MediaServer/Rtsp/UDPServer.h new file mode 100644 index 0000000..90f735d --- /dev/null +++ b/MediaServer/Rtsp/UDPServer.h @@ -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 RTSP_UDPSERVER_H_ +#define RTSP_UDPSERVER_H_ + +#include +#include +#include +#include +#include "Network/Socket.h" + +namespace mediakit { + +class UDPServer : public std::enable_shared_from_this { +public: + using onRecvData = std::function ; + ~UDPServer(); + static UDPServer &Instance(); + toolkit::Socket::Ptr getSock(toolkit::SocketHelper &helper, const char *local_ip, int interleaved, uint16_t local_port = 0); + void listenPeer(const char *peer_ip, void *obj, const onRecvData &cb); + void stopListenPeer(const char *peer_ip, void *obj); + +private: + UDPServer(); + void onRecv(int interleaved, const toolkit::Buffer::Ptr &buf, struct sockaddr *peer_addr); + void onErr(const std::string &strKey, const toolkit::SockException &err); + +private: + std::mutex _mtx_udp_sock; + std::mutex _mtx_on_recv; + std::unordered_map _udp_sock_map; + std::unordered_map > _on_recv_map; +}; + +} /* namespace mediakit */ + +#endif /* RTSP_UDPSERVER_H_ */ diff --git a/MediaServer/TS/TSMediaSource.h b/MediaServer/TS/TSMediaSource.h new file mode 100644 index 0000000..16c9e17 --- /dev/null +++ b/MediaServer/TS/TSMediaSource.h @@ -0,0 +1,150 @@ +/* + * 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_TSMEDIASOURCE_H +#define ZLMEDIAKIT_TSMEDIASOURCE_H + +#include "Common/MediaSource.h" +#include "Common/PacketCache.h" +#include "Util/RingBuffer.h" + +#define TS_GOP_SIZE 512 + +namespace mediakit { + +// TS直播数据包 [AUTO-TRANSLATED:02fb2e8e] +// TS Live Data Packet +class TSPacket : public toolkit::BufferOffset{ +public: + using Ptr = std::shared_ptr; + + template + TSPacket(ARGS && ...args) : BufferOffset(std::forward(args)...) {}; + +public: + uint64_t time_stamp = 0; +}; + +// TS直播源 [AUTO-TRANSLATED:0d25ead6] +// TS Live Source +class TSMediaSource final : public MediaSource, public toolkit::RingDelegate, private PacketCache{ +public: + using Ptr = std::shared_ptr; + using RingDataType = std::shared_ptr >; + using RingType = toolkit::RingBuffer; + + TSMediaSource(const MediaTuple& tuple, int ring_size = TS_GOP_SIZE): MediaSource(TS_SCHEMA, tuple), _ring_size(ring_size) {} + + ~TSMediaSource() 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 &info_list)> &cb, + const std::function &on_change) override { + _ring->getInfoList(cb, on_change); + } + + /** + * 获取播放器个数 + * Get the number of players + + * [AUTO-TRANSLATED:a451c846] + */ + int readerCount() override { + return _ring ? _ring->readerCount() : 0; + } + + /** + * 输入TS包 + * @param packet TS包 + * @param key 是否为关键帧第一个包 + * Input TS packet + * @param packet TS packet + * @param key Whether it is the first packet of the key frame + + * [AUTO-TRANSLATED:cd773549] + */ + void onWrite(TSPacket::Ptr packet, bool key) override { + _speed[TrackVideo] += packet->size(); + if (!_ring) { + createRing(); + } + if (key) { + _have_video = true; + } + auto stamp = packet->time_stamp; + PacketCache::inputPacket(stamp, true, std::move(packet), key); + } + + /** + * 情况GOP缓存 + * Clear GOP cache + + * [AUTO-TRANSLATED:d863f8c9] + */ + void clearCache() override { + PacketCache::clearCache(); + _ring->clearCache(); + } + +private: + void createRing(){ + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _ring = std::make_shared(_ring_size, [weak_self](int size) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onReaderChanged(size); + }); + // 注册媒体源 [AUTO-TRANSLATED:b87b5ac4] + // Register media source + 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 > 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; + RingType::Ptr _ring; +}; + + +}//namespace mediakit +#endif //ZLMEDIAKIT_TSMEDIASOURCE_H diff --git a/MediaServer/TS/TSMediaSourceMuxer.h b/MediaServer/TS/TSMediaSourceMuxer.h new file mode 100644 index 0000000..fab961d --- /dev/null +++ b/MediaServer/TS/TSMediaSourceMuxer.h @@ -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_TSMEDIASOURCEMUXER_H +#define ZLMEDIAKIT_TSMEDIASOURCEMUXER_H + +#include "TSMediaSource.h" +#include "Record/MPEG.h" + +namespace mediakit { + +class TSMediaSourceMuxer final : public MpegMuxer, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + TSMediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) : MpegMuxer(false) { + _option = option; + _media_src = std::make_shared(tuple); + } + + ~TSMediaSourceMuxer() override { + try { + MpegMuxer::flush(); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + }; + + void setListener(const std::weak_ptr &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.ts_demand ? size : true; + if (!size && _option.ts_demand) { + _clear_cache = true; + } + MediaSourceEventInterceptor::onReaderChanged(sender, size); + } + + bool inputFrame(const Frame::Ptr &frame) override { + if (_clear_cache && _option.ts_demand) { + _clear_cache = false; + _media_src->clearCache(); + } + if (_enabled || !_option.ts_demand) { + return MpegMuxer::inputFrame(frame); + } + return false; + } + + bool isEnabled() { + // 缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 [AUTO-TRANSLATED:7cfd4d49] + // Allow the inputFrame function to be triggered even when the cache is not yet cleared, so that the cache can be cleared in time. + return _option.ts_demand ? (_clear_cache ? true : _enabled) : true; + } + +protected: + void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { + if (!buffer) { + return; + } + auto packet = std::make_shared(std::move(buffer)); + packet->time_stamp = timestamp; + _media_src->onWrite(std::move(packet), key_pos); + } + +private: + bool _enabled = true; + bool _clear_cache = false; + ProtocolOption _option; + TSMediaSource::Ptr _media_src; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_TSMEDIASOURCEMUXER_H diff --git a/MediaServer/ext-codec/AAC.cpp b/MediaServer/ext-codec/AAC.cpp new file mode 100644 index 0000000..d17ea49 --- /dev/null +++ b/MediaServer/ext-codec/AAC.cpp @@ -0,0 +1,453 @@ +/* + * 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 "AAC.h" +#include "AACRtp.h" +#include "AACRtmp.h" +#include "Common/Parser.h" +#include "Extension/Factory.h" +#ifdef ENABLE_MP4 +#include "mpeg4-aac.h" +#endif + +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +#ifndef ENABLE_MP4 +unsigned const samplingFrequencyTable[16] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0 }; + +class AdtsHeader { +public: + unsigned int syncword = 0; // 12 bslbf 同步字The bit string ‘1111 1111 1111’,说明一个ADTS帧的开始 + unsigned int id; // 1 bslbf MPEG 标示符, 设置为1 + unsigned int layer; // 2 uimsbf Indicates which layer is used. Set to ‘00’ + unsigned int protection_absent; // 1 bslbf 表示是否误码校验 + unsigned int profile; // 2 uimsbf 表示使用哪个级别的AAC,如01 Low Complexity(LC)--- AACLC + unsigned int sf_index; // 4 uimsbf 表示使用的采样率下标 + unsigned int private_bit; // 1 bslbf + unsigned int channel_configuration; // 3 uimsbf 表示声道数 + unsigned int original; // 1 bslbf + unsigned int home; // 1 bslbf + // 下面的为改变的参数即每一帧都不同 [AUTO-TRANSLATED:481aa349] + // The following are the parameters that change in each frame + unsigned int copyright_identification_bit; // 1 bslbf + unsigned int copyright_identification_start; // 1 bslbf + unsigned int aac_frame_length; // 13 bslbf 一个ADTS帧的长度包括ADTS头和raw data block + unsigned int adts_buffer_fullness; // 11 bslbf 0x7FF 说明是码率可变的码流 + // no_raw_data_blocks_in_frame 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧. [AUTO-TRANSLATED:3e975531] + // no_raw_data_blocks_in_frame indicates that there are number_of_raw_data_blocks_in_frame + 1 AAC raw frames in the ADTS frame. + // 所以说number_of_raw_data_blocks_in_frame == 0 [AUTO-TRANSLATED:1b8e9697] + // So number_of_raw_data_blocks_in_frame == 0 + // 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据) [AUTO-TRANSLATED:4a09d783] + // means that there is one AAC data block in the ADTS frame, not that there is none. (An AAC raw frame contains 1024 samples and related data over a period of time) + unsigned int no_raw_data_blocks_in_frame; // 2 uimsfb +}; + +static void dumpAdtsHeader(const AdtsHeader &hed, uint8_t *out) { + out[0] = (hed.syncword >> 4 & 0xFF); // 8bit + out[1] = (hed.syncword << 4 & 0xF0); // 4 bit + out[1] |= (hed.id << 3 & 0x08); // 1 bit + out[1] |= (hed.layer << 1 & 0x06); // 2bit + out[1] |= (hed.protection_absent & 0x01); // 1 bit + out[2] = (hed.profile << 6 & 0xC0); // 2 bit + out[2] |= (hed.sf_index << 2 & 0x3C); // 4bit + out[2] |= (hed.private_bit << 1 & 0x02); // 1 bit + out[2] |= (hed.channel_configuration >> 2 & 0x03); // 1 bit + out[3] = (hed.channel_configuration << 6 & 0xC0); // 2 bit + out[3] |= (hed.original << 5 & 0x20); // 1 bit + out[3] |= (hed.home << 4 & 0x10); // 1 bit + out[3] |= (hed.copyright_identification_bit << 3 & 0x08); // 1 bit + out[3] |= (hed.copyright_identification_start << 2 & 0x04); // 1 bit + out[3] |= (hed.aac_frame_length >> 11 & 0x03); // 2 bit + out[4] = (hed.aac_frame_length >> 3 & 0xFF); // 8 bit + out[5] = (hed.aac_frame_length << 5 & 0xE0); // 3 bit + out[5] |= (hed.adts_buffer_fullness >> 6 & 0x1F); // 5 bit + out[6] = (hed.adts_buffer_fullness << 2 & 0xFC); // 6 bit + out[6] |= (hed.no_raw_data_blocks_in_frame & 0x03); // 2 bit +} + +static bool parseAacConfig(const string &config, AdtsHeader &adts) { + if (config.size() < 2) { + return false; + } + uint8_t cfg1 = config[0]; + uint8_t cfg2 = config[1]; + + int audioObjectType; + int sampling_frequency_index; + int channel_configuration; + + audioObjectType = cfg1 >> 3; + sampling_frequency_index = ((cfg1 & 0x07) << 1) | (cfg2 >> 7); + channel_configuration = (cfg2 & 0x7F) >> 3; + + adts.syncword = 0x0FFF; + adts.id = 0; + adts.layer = 0; + adts.protection_absent = 1; + adts.profile = audioObjectType - 1; + adts.sf_index = sampling_frequency_index; + adts.private_bit = 0; + adts.channel_configuration = channel_configuration; + adts.original = 0; + adts.home = 0; + adts.copyright_identification_bit = 0; + adts.copyright_identification_start = 0; + adts.aac_frame_length = 7; + adts.adts_buffer_fullness = 2047; + adts.no_raw_data_blocks_in_frame = 0; + return true; +} +#endif// ENABLE_MP4 + +int getAacFrameLength(const uint8_t *data, size_t bytes) { + uint16_t len; + if (bytes < 7) return -1; + if (0xFF != data[0] || 0xF0 != (data[1] & 0xF0)) { + return -1; + } + len = ((uint16_t) (data[3] & 0x03) << 11) | ((uint16_t) data[4] << 3) | ((uint16_t) (data[5] >> 5) & 0x07); + return len; +} + +string makeAacConfig(const uint8_t *hex, size_t length){ +#ifndef ENABLE_MP4 + if (!(hex[0] == 0xFF && (hex[1] & 0xF0) == 0xF0)) { + return ""; + } + // Get and check the 'profile': + unsigned char profile = (hex[2] & 0xC0) >> 6; // 2 bits + if (profile == 3) { + return ""; + } + + // Get and check the 'sampling_frequency_index': + unsigned char sampling_frequency_index = (hex[2] & 0x3C) >> 2; // 4 bits + if (samplingFrequencyTable[sampling_frequency_index] == 0) { + return ""; + } + + // Get and check the 'channel_configuration': + unsigned char channel_configuration = ((hex[2] & 0x01) << 2) | ((hex[3] & 0xC0) >> 6); // 3 bits + unsigned char audioSpecificConfig[2]; + unsigned char const audioObjectType = profile + 1; + audioSpecificConfig[0] = (audioObjectType << 3) | (sampling_frequency_index >> 1); + audioSpecificConfig[1] = (sampling_frequency_index << 7) | (channel_configuration << 3); + return string((char *)audioSpecificConfig,2); +#else + struct mpeg4_aac_t aac; + memset(&aac, 0, sizeof(aac)); + if (mpeg4_aac_adts_load(hex, length, &aac) > 0) { + char buf[32] = {0}; + int len = mpeg4_aac_audio_specific_config_save(&aac, (uint8_t *) buf, sizeof(buf)); + if (len > 0) { + return string(buf, len); + } + } + WarnL << "生成aac config失败, adts header:" << hexdump(hex, length); + return ""; +#endif +} + +int dumpAacConfig(const string &config, size_t length, uint8_t *out, size_t out_size) { +#ifndef ENABLE_MP4 + AdtsHeader header; + parseAacConfig(config, header); + header.aac_frame_length = (decltype(header.aac_frame_length))(ADTS_HEADER_LEN + length); + dumpAdtsHeader(header, out); + return ADTS_HEADER_LEN; +#else + struct mpeg4_aac_t aac; + memset(&aac, 0, sizeof(aac)); + int ret = mpeg4_aac_audio_specific_config_load((uint8_t *) config.data(), config.size(), &aac); + if (ret > 0) { + ret = mpeg4_aac_adts_save(&aac, length, out, out_size); + } + if (ret < 0) { + WarnL << "生成adts头失败:" << ret << ", aac config:" << hexdump(config.data(), config.size()); + } + assert((int)out_size >= ret); + return ret; +#endif +} + +bool parseAacConfig(const string &config, int &samplerate, int &channels) { +#ifndef ENABLE_MP4 + AdtsHeader header; + if (!parseAacConfig(config, header)) { + return false; + } + samplerate = samplingFrequencyTable[header.sf_index]; + channels = header.channel_configuration; + return true; +#else + struct mpeg4_aac_t aac; + memset(&aac, 0, sizeof(aac)); + int ret = mpeg4_aac_audio_specific_config_load((uint8_t *) config.data(), config.size(), &aac); + if (ret > 0) { + samplerate = aac.sampling_frequency; + channels = aac.channels; + return true; + } + WarnL << "获取aac采样率、声道数失败:" << hexdump(config.data(), config.size()); + return false; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * aac类型SDP + * aac type SDP + + * [AUTO-TRANSLATED:c06f00b1] + */ +class AACSdp : public Sdp { +public: + /** + * 构造函数 + * @param aac_cfg aac两个字节的配置描述 + * @param payload_type rtp payload type + * @param sample_rate 音频采样率 + * @param channels 通道数 + * @param bitrate 比特率 + * Constructor + * @param aac_cfg aac two-byte configuration description + * @param payload_type rtp payload type + * @param sample_rate audio sampling rate + * @param channels number of channels + * @param bitrate bitrate + + * [AUTO-TRANSLATED:6fe1f3b2] + */ + AACSdp(const string &aac_cfg, int payload_type, int sample_rate, int channels, int bitrate) + : Sdp(sample_rate, payload_type) { + _printer << "m=audio 0 RTP/AVP " << payload_type << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + _printer << "a=rtpmap:" << payload_type << " " << getCodecName(CodecAAC) << "/" << sample_rate << "/" << channels << "\r\n"; + + string configStr; + char buf[4] = { 0 }; + for (auto &ch : aac_cfg) { + snprintf(buf, sizeof(buf), "%02X", (uint8_t)ch); + configStr.append(buf); + } + _printer << "a=fmtp:" << payload_type << " streamtype=5;profile-level-id=1;mode=AAC-hbr;" + << "sizelength=13;indexlength=3;indexdeltalength=3;config=" << configStr << "\r\n"; + } + + string getSdp() const override { return _printer; } + +private: + _StrPrinter _printer; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +AACTrack::AACTrack(const string &aac_cfg) { + if (aac_cfg.size() < 2) { + throw std::invalid_argument("adts配置必须最少2个字节"); + } + _cfg = aac_cfg; + update(); +} + +CodecId AACTrack::getCodecId() const { + return CodecAAC; +} + +bool AACTrack::ready() const { + return !_cfg.empty(); +} + +int AACTrack::getAudioSampleRate() const { + return _sampleRate; +} + +int AACTrack::getAudioSampleBit() const { + return _sampleBit; +} + +int AACTrack::getAudioChannel() const { + return _channel; +} + +static Frame::Ptr addADTSHeader(const Frame::Ptr &frame_in, const std::string &aac_config) { + auto frame = FrameImp::create(); + frame->_codec_id = CodecAAC; + // 生成adts头 [AUTO-TRANSLATED:c285b9b0] + // Generate adts header + char adts_header[32] = { 0 }; + auto size = dumpAacConfig(aac_config, frame_in->size(), (uint8_t *)adts_header, sizeof(adts_header)); + CHECK(size > 0, "Invalid adts config"); + frame->_prefix_size = size; + frame->_dts = frame_in->dts(); + frame->_buffer.assign(adts_header, size); + frame->_buffer.append(frame_in->data(), frame_in->size()); + frame->setIndex(frame_in->getIndex()); + return frame; +} + +bool AACTrack::inputFrame(const Frame::Ptr &frame) { + if (!frame->prefixSize()) { + CHECK(ready()); + return inputFrame_l(addADTSHeader(frame, _cfg)); + } + + bool ret = false; + // 有adts头,尝试分帧 [AUTO-TRANSLATED:f691c4ce] + // There is an adts header, try to frame + int64_t dts = frame->dts(); + int64_t pts = frame->pts(); + + auto ptr = frame->data(); + auto end = frame->data() + frame->size(); + while (ptr < end) { + auto frame_len = getAacFrameLength((uint8_t *)ptr, end - ptr); + if (frame_len < ADTS_HEADER_LEN) { + break; + } + if (frame_len == (int)frame->size()) { + return inputFrame_l(frame); + } + auto sub_frame = std::make_shared>(frame, (char *)ptr, frame_len, dts, pts, ADTS_HEADER_LEN); + ptr += frame_len; + if (ptr > end) { + WarnL << "invalid aac length in adts header: " << frame_len + << ", remain data size: " << end - (ptr - frame_len); + break; + } + if (inputFrame_l(sub_frame)) { + ret = true; + } + dts += 1024 * 1000 / getAudioSampleRate(); + pts += 1024 * 1000 / getAudioSampleRate(); + } + return ret; +} + +bool AACTrack::inputFrame_l(const Frame::Ptr &frame) { + if (_cfg.empty() && frame->prefixSize()) { + // 未获取到aac_cfg信息,根据7个字节的adts头生成aac config [AUTO-TRANSLATED:1b80f562] + // Unable to get aac_cfg information, generate aac config based on the 7-byte adts header + _cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize()); + update(); + } + + if (frame->size() > frame->prefixSize()) { + // 除adts头外,有实际负载 [AUTO-TRANSLATED:5b7c088e] + // There is an actual payload besides the adts header + return AudioTrack::inputFrame(frame); + } + return false; +} + +toolkit::Buffer::Ptr AACTrack::getExtraData() const { + CHECK(ready()); + return std::make_shared(_cfg); +} + +void AACTrack::setExtraData(const uint8_t *data, size_t size) { + CHECK(size >= 2); + _cfg.assign((char *)data, size); + update(); +} + +bool AACTrack::update() { + return parseAacConfig(_cfg, _sampleRate, _channel); +} + +Track::Ptr AACTrack::clone() const { + return std::make_shared(*this); +} + +Sdp::Ptr AACTrack::getSdp(uint8_t payload_type) const { + if (!ready()) { + WarnL << getCodecName() << " Track未准备好"; + return nullptr; + } + return std::make_shared(getExtraData()->toString(), payload_type, getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); +} + +namespace { + +CodecId getCodec() { + return CodecAAC; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + string aac_cfg_str = findSubString(track->_fmtp.data(), "config=", ";"); + if (aac_cfg_str.empty()) { + aac_cfg_str = findSubString(track->_fmtp.data(), "config=", nullptr); + } + if (aac_cfg_str.empty()) { + // 如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track [AUTO-TRANSLATED:995bc20d] + // If aac config information cannot be obtained from sdp, then it cannot be obtained from rtp either, so ignore this Track + return nullptr; + } + string aac_cfg; + for (size_t i = 0; i < aac_cfg_str.size() / 2; ++i) { + unsigned int cfg; + sscanf(aac_cfg_str.substr(i * 2, 2).data(), "%02X", &cfg); + cfg &= 0x00FF; + aac_cfg.push_back((char)cfg); + } + return std::make_shared(aac_cfg); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +size_t aacPrefixSize(const char *data, size_t bytes) { + uint8_t *ptr = (uint8_t *)data; + size_t prefix = 0; + if (!(bytes > ADTS_HEADER_LEN && ptr[0] == 0xFF && (ptr[1] & 0xF0) == 0xF0)) { + return 0; + } + return ADTS_HEADER_LEN; +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared(CodecAAC, (char *)data, bytes, dts, pts, aacPrefixSize(data, bytes)); +} + +} // namespace + +CodecPlugin aac_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +} // namespace mediakit \ No newline at end of file diff --git a/MediaServer/ext-codec/AAC.h b/MediaServer/ext-codec/AAC.h new file mode 100644 index 0000000..66fe007 --- /dev/null +++ b/MediaServer/ext-codec/AAC.h @@ -0,0 +1,64 @@ +/* + * 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_AAC_H +#define ZLMEDIAKIT_AAC_H + +#include "Extension/Frame.h" +#include "Extension/Track.h" +#define ADTS_HEADER_LEN 7 + +namespace mediakit{ + +/** + * aac音频通道 + * AAC audio channel + + * [AUTO-TRANSLATED:0d58b638] + */ +class AACTrack : public AudioTrack { +public: + using Ptr = std::shared_ptr; + + AACTrack() = default; + + /** + * 通过aac extra data 构造对象 + * Construct object through AAC extra data + + + * [AUTO-TRANSLATED:1fa035c8] + */ + AACTrack(const std::string &aac_cfg); + + bool ready() const override; + CodecId getCodecId() const override; + int getAudioChannel() const override; + int getAudioSampleRate() const override; + int getAudioSampleBit() const override; + bool inputFrame(const Frame::Ptr &frame) override; + toolkit::Buffer::Ptr getExtraData() const override; + void setExtraData(const uint8_t *data, size_t size) override; + bool update() override; + +private: + Sdp::Ptr getSdp(uint8_t payload_type) const override; + Track::Ptr clone() const override; + bool inputFrame_l(const Frame::Ptr &frame); + +private: + std::string _cfg; + int _channel = 0; + int _sampleRate = 0; + int _sampleBit = 16; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_AAC_H \ No newline at end of file diff --git a/MediaServer/ext-codec/AACRtmp.cpp b/MediaServer/ext-codec/AACRtmp.cpp new file mode 100644 index 0000000..72c96d5 --- /dev/null +++ b/MediaServer/ext-codec/AACRtmp.cpp @@ -0,0 +1,66 @@ +/* + * 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 "AACRtmp.h" +#include "Rtmp/Rtmp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { + CHECK_RET(pkt->size() > 2); + if (pkt->isConfigFrame()) { + getTrack()->setExtraData((uint8_t *)pkt->data() + 2, pkt->size() - 2); + return; + } + RtmpCodec::inputFrame(std::make_shared(CodecAAC, pkt->buffer.data() + 2, pkt->buffer.size() - 2, pkt->time_stamp)); +} + +///////////////////////////////////////////////////////////////////////////////////// + +bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw); + // aac data + pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = frame->dts(); + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); + return true; +} + +void AACRtmpEncoder::makeConfigPacket() { + _audio_flv_flags = getAudioRtmpFlags(getTrack()); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header); + + // aac config + auto extra_data = getTrack()->getExtraData(); + CHECK(extra_data); + pkt->buffer.append(extra_data->data(), extra_data->size()); + + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); +} + +}//namespace mediakit \ No newline at end of file diff --git a/MediaServer/ext-codec/AACRtmp.h b/MediaServer/ext-codec/AACRtmp.h new file mode 100644 index 0000000..5497a7d --- /dev/null +++ b/MediaServer/ext-codec/AACRtmp.h @@ -0,0 +1,91 @@ +/* + * 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_AACRTMPCODEC_H +#define ZLMEDIAKIT_AACRTMPCODEC_H + +#include "AAC.h" +#include "Rtmp/RtmpCodec.h" +#include "Extension/Track.h" + +namespace mediakit { +/** + * aac Rtmp转adts类 + * aac Rtmp to adts class + + * [AUTO-TRANSLATED:8b262ddb] + */ +class AACRtmpDecoder : public RtmpCodec { +public: + using Ptr = std::shared_ptr; + + AACRtmpDecoder(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; +}; + +/** + * aac adts转Rtmp类 + * aac adts to Rtmp class + + * [AUTO-TRANSLATED:2d9c53dd] + */ +class AACRtmpEncoder : public RtmpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数,track可以为空,此时则在inputFrame时输入adts头 + * 如果track不为空且包含adts头相关信息, + * 那么inputFrame时可以不输入adts头 + * @param track + * Constructor, track can be empty, in which case the adts header is input when inputFrame is called + * If track is not empty and contains adts header related information, + * then the adts header can be omitted when inputFrame is called + * @param track + + * [AUTO-TRANSLATED:fcf8f765] + */ + AACRtmpEncoder(const Track::Ptr &track) : RtmpCodec(track) {} + + /** + * 输入aac 数据,可以不带adts头 + * @param frame aac数据 + * Input aac data, can be without adts header + * @param frame aac data + + * [AUTO-TRANSLATED:d9f4131a] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 生成config包 + * Generate config package + + + * [AUTO-TRANSLATED:8f851364] + */ + void makeConfigPacket() override; + +private: + uint8_t _audio_flv_flags {0}; +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_AACRTMPCODEC_H diff --git a/MediaServer/ext-codec/AACRtp.cpp b/MediaServer/ext-codec/AACRtp.cpp new file mode 100644 index 0000000..34b0483 --- /dev/null +++ b/MediaServer/ext-codec/AACRtp.cpp @@ -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. + */ + +#include "AACRtp.h" + +namespace mediakit{ + +bool AACRtpEncoder::inputFrame(const Frame::Ptr &frame) { + auto ptr = (char *)frame->data() + frame->prefixSize(); + auto size = frame->size() - frame->prefixSize(); + auto remain_size = size; + auto max_size = getRtpInfo().getMaxSize() - 4; + while (remain_size > 0) { + if (remain_size <= max_size) { + outputRtp(ptr, remain_size, size, true, frame->dts()); + break; + } + outputRtp(ptr, max_size, size, false, frame->dts()); + ptr += max_size; + remain_size -= max_size; + } + return true; +} + +void AACRtpEncoder::outputRtp(const char *data, size_t len, size_t total_len, bool mark, uint64_t stamp) { + auto rtp = getRtpInfo().makeRtp(TrackAudio, nullptr, len + 4, mark, stamp); + auto payload = rtp->data() + RtpPacket::kRtpTcpHeaderSize + RtpPacket::kRtpHeaderSize; + payload[0] = 0; + payload[1] = 16; + payload[2] = ((total_len) >> 5) & 0xFF; + payload[3] = ((total_len & 0x1F) << 3) & 0xFF; + memcpy(payload + 4, data, len); + RtpCodec::inputRtp(std::move(rtp), false); +} + +///////////////////////////////////////////////////////////////////////////////////// + +AACRtpDecoder::AACRtpDecoder() { + obtainFrame(); +} + +void AACRtpDecoder::obtainFrame() { + // 从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 [AUTO-TRANSLATED:f85fe201] + // Re-apply the object from the cache pool to prevent overwriting the object that has been written to the ring buffer + _frame = FrameImp::create(); + _frame->_codec_id = CodecAAC; +} + +bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) { + auto payload_size = rtp->getPayloadSize(); + if (payload_size <= 0) { + // 无实际负载 [AUTO-TRANSLATED:2267e6ac] + // No actual load + return false; + } + + auto stamp = rtp->getStampMS(); + // rtp数据开始部分 [AUTO-TRANSLATED:f22ebdb9] + // Start of rtp data + auto ptr = rtp->getPayload(); + // rtp数据末尾 [AUTO-TRANSLATED:ee108f2b] + // End of rtp data + auto end = ptr + payload_size; + // 首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数 [AUTO-TRANSLATED:c7175051] + // The first 2 bytes represent the number of Au-Headers, in bits, so divide by 16 to get the number of Au-Headers + auto au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4; + if (!au_header_count) { + // 问题issue: https://github.com/ZLMediaKit/ZLMediaKit/issues/1869 [AUTO-TRANSLATED:14be1ff8] + // Issue: https://github.com/ZLMediaKit/ZLMediaKit/issues/1869 + WarnL << "invalid aac rtp au_header_count"; + return false; + } + // 记录au_header起始指针 [AUTO-TRANSLATED:b9083b72] + // Record the starting pointer of au_header + auto au_header_ptr = ptr + 2; + ptr = au_header_ptr + au_header_count * 2; + + if (end < ptr) { + // 数据不够 [AUTO-TRANSLATED:830a2785] + // Not enough data + return false; + } + + if (!_last_dts) { + // 记录第一个时间戳 [AUTO-TRANSLATED:2e85b398] + // Record the first timestamp + _last_dts = stamp; + } + + // 每个audio unit时间戳增量 [AUTO-TRANSLATED:0345240c] + // Timestamp increment for each audio unit + auto dts_inc = static_cast(stamp - _last_dts) / au_header_count; + if (dts_inc < 0 || dts_inc > 100) { + // 时间戳增量异常,忽略 [AUTO-TRANSLATED:d2750ef8] + // Timestamp increment anomaly, ignore + dts_inc = 0; + } + + for (auto i = 0u; i < (size_t)au_header_count; ++i) { + // 之后的2字节是AU_HEADER,其中高13位表示一帧AAC负载的字节长度,低3位无用 [AUTO-TRANSLATED:404eb444] + // The following 2 bytes are AU_HEADER, where the high 13 bits represent the byte length of one frame of AAC payload, and the low 3 bits are useless + auto size = ((au_header_ptr[0] << 8) | au_header_ptr[1]) >> 3; + auto len = std::min(size, end - ptr); + if (len <= 0) { + break; + } + _frame->_buffer.append((char *)ptr, len); + ptr += len; + au_header_ptr += 2; + + if (_frame->size() >= (size_t)size) { + // 设置当前audio unit时间戳 [AUTO-TRANSLATED:eee18d6e] + // Set the current audio unit timestamp + _frame->_dts = _last_dts + i * dts_inc; + flushData(); + } + } + // 记录上次时间戳 [AUTO-TRANSLATED:a830d69f] + // Record the last timestamp + _last_dts = stamp; + return false; +} + +void AACRtpDecoder::flushData() { + auto ptr = reinterpret_cast(_frame->data()); + if ((ptr[0] == 0xFF && (ptr[1] & 0xF0) == 0xF0) && _frame->size() > ADTS_HEADER_LEN) { + // adts头打入了rtp包,不符合规范,兼容EasyPusher的bug [AUTO-TRANSLATED:203a5ee9] + // The adts header is inserted into the rtp packet, which is not compliant with the specification, compatible with the bug of EasyPusher + _frame->_prefix_size = ADTS_HEADER_LEN; + } + RtpCodec::inputFrame(_frame); + obtainFrame(); +} + +}//namespace mediakit \ No newline at end of file diff --git a/MediaServer/ext-codec/AACRtp.h b/MediaServer/ext-codec/AACRtp.h new file mode 100644 index 0000000..473c596 --- /dev/null +++ b/MediaServer/ext-codec/AACRtp.h @@ -0,0 +1,80 @@ +/* + * 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_AACRTPCODEC_H +#define ZLMEDIAKIT_AACRTPCODEC_H + +#include "AAC.h" +#include "Rtsp/RtpCodec.h" + +namespace mediakit { +/** + * aac rtp转adts类 + * aac rtp to adts class + + * [AUTO-TRANSLATED:8ff7580f] + */ +class AACRtpDecoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + AACRtpDecoder(); + + /** + * 输入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(); + void flushData(); + +private: + uint64_t _last_dts = 0; + FrameImp::Ptr _frame; +}; + + +/** + * aac adts转rtp类 + * aac adts to rtp class + + * [AUTO-TRANSLATED:1ed889e2] + */ +class AACRtpEncoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 输入aac 数据,必须带dats头 + * @param frame 带dats头的aac数据 + * input aac data, must have dats header + * @param frame aac data with dats header + + + * [AUTO-TRANSLATED:459bba30] + */ + bool inputFrame(const Frame::Ptr &frame) override; + +private: + void outputRtp(const char *data, size_t len, size_t total_len, bool mark, uint64_t stamp); + +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_AACRTPCODEC_H diff --git a/MediaServer/ext-codec/G711.cpp b/MediaServer/ext-codec/G711.cpp new file mode 100644 index 0000000..7a60fcd --- /dev/null +++ b/MediaServer/ext-codec/G711.cpp @@ -0,0 +1,200 @@ +/* + * 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 "G711.h" +#include "G711Rtp.h" +#include "Extension/Factory.h" +#include "Extension/CommonRtp.h" +#include "Extension/CommonRtmp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +/** + * G711类型SDP + * G711 type SDP + + * [AUTO-TRANSLATED:ea72d60a] + */ +class G711Sdp : public Sdp { +public: + /** + * G711采样率固定为8000 + * @param codecId G711A G711U + * @param payload_type rtp payload type + * @param sample_rate 音频采样率 + * @param channels 通道数 + * @param bitrate 比特率 + * G711 sampling rate is fixed at 8000 + * @param codecId G711A G711U + * @param payload_type rtp payload type + * @param sample_rate audio sampling rate + * @param channels number of channels + * @param bitrate bitrate + + * [AUTO-TRANSLATED:5ea4b771] + */ + G711Sdp(CodecId codecId, int payload_type, int sample_rate, int channels, int bitrate) + : Sdp(sample_rate, payload_type) { + _printer << "m=audio 0 RTP/AVP " << payload_type << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + _printer << "a=rtpmap:" << payload_type << " " << getCodecName(codecId) << "/" << sample_rate << "/" << channels << "\r\n"; + } + + string getSdp() const override { + return _printer; + } + +private: + _StrPrinter _printer; +}; + +Track::Ptr G711Track::clone() const { + return std::make_shared(*this); +} + +Sdp::Ptr G711Track::getSdp(uint8_t payload_type) const { + if (!ready()) { + WarnL << getCodecName() << " Track未准备好"; + return nullptr; + } + + const auto codec = getCodecId(); + const auto sample_rate = getAudioSampleRate(); + const auto audio_channel = getAudioChannel(); + const auto bitrate = getBitRate() >> 10; + if (sample_rate == 8000 && audio_channel == 1) { + // https://datatracker.ietf.org/doc/html/rfc3551#section-6 + payload_type = (codec == CodecG711U) ? Rtsp::PT_PCMU : Rtsp::PT_PCMA; + } + + return std::make_shared(codec, payload_type, sample_rate, audio_channel, bitrate); +} + + +namespace { + +CodecId getCodecA() { + return CodecG711A; +} + +CodecId getCodecU() { + return CodecG711U; +} + +Track::Ptr getTrackByCodecId_l(CodecId codec, int sample_rate, int channels, int sample_bit) { + return (sample_rate && channels && sample_bit) ? std::make_shared(codec, sample_rate, channels, sample_bit) : nullptr; +} + +Track::Ptr getTrackByCodecIdA(int sample_rate, int channels, int sample_bit) { + return getTrackByCodecId_l(CodecG711A, sample_rate, channels, sample_bit); +} + +Track::Ptr getTrackByCodecIdU(int sample_rate, int channels, int sample_bit) { + return getTrackByCodecId_l(CodecG711U, sample_rate, channels, sample_bit); +} + +Track::Ptr getTrackBySdp_l(CodecId codec, const SdpTrack::Ptr &track) { + return std::make_shared(codec, track->_samplerate, track->_channel, 16); +} + +Track::Ptr getTrackBySdpA(const SdpTrack::Ptr &track) { + return getTrackBySdp_l(CodecG711A, track); +} + +Track::Ptr getTrackBySdpU(const SdpTrack::Ptr &track) { + return getTrackBySdp_l(CodecG711U, track); +} + +RtpCodec::Ptr getRtpEncoderByCodecId_l(CodecId codec, uint8_t pt) { + if (pt == Rtsp::PT_PCMA || pt == Rtsp::PT_PCMU) { + return std::make_shared(codec, 1); + } + return std::make_shared(); +} + +RtpCodec::Ptr getRtpEncoderByCodecIdA(uint8_t pt) { + return getRtpEncoderByCodecId_l(CodecG711A, pt); +} + +RtpCodec::Ptr getRtpEncoderByCodecIdU(uint8_t pt) { + return getRtpEncoderByCodecId_l(CodecG711U, pt); +} + +RtpCodec::Ptr getRtpDecoderByCodecId_l(CodecId codec) { + return std::make_shared(codec); +} + +RtpCodec::Ptr getRtpDecoderByCodecIdA() { + return getRtpDecoderByCodecId_l(CodecG711A); +} + +RtpCodec::Ptr getRtpDecoderByCodecIdU() { + return getRtpDecoderByCodecId_l(CodecG711U); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + auto audio_track = dynamic_pointer_cast(track); + if (audio_track->getAudioSampleRate() != 8000 || audio_track->getAudioChannel() != 1 || audio_track->getAudioSampleBit() != 16) { + // rtmp对g711只支持8000/1/16规格,但是ZLMediaKit可以解析其他规格的G711 [AUTO-TRANSLATED:0ddeaafe] + // rtmp only supports 8000/1/16 specifications for g711, but ZLMediaKit can parse other specifications of G711 + WarnL << "RTMP only support G711 with 8000/1/16, now is" + << audio_track->getAudioSampleRate() << "/" + << audio_track->getAudioChannel() << "/" + << audio_track->getAudioSampleBit() + << ", ignored it"; + return nullptr; + } + return std::make_shared(track); +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +Frame::Ptr getFrameFromPtr_l(CodecId codec, const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared(codec, (char *)data, bytes, dts, pts); +} + +Frame::Ptr getFrameFromPtrA(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return getFrameFromPtr_l(CodecG711A, (char *)data, bytes, dts, pts); +} + +Frame::Ptr getFrameFromPtrU(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return getFrameFromPtr_l(CodecG711U, (char *)data, bytes, dts, pts); +} + +} // namespace + +CodecPlugin g711a_plugin = { getCodecA, + getTrackByCodecIdA, + getTrackBySdpA, + getRtpEncoderByCodecIdA, + getRtpDecoderByCodecIdA, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtrA }; + +CodecPlugin g711u_plugin = { getCodecU, + getTrackByCodecIdU, + getTrackBySdpU, + getRtpEncoderByCodecIdU, + getRtpDecoderByCodecIdU, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtrU }; + +}//namespace mediakit + + diff --git a/MediaServer/ext-codec/G711.h b/MediaServer/ext-codec/G711.h new file mode 100644 index 0000000..7b75a17 --- /dev/null +++ b/MediaServer/ext-codec/G711.h @@ -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 ZLMEDIAKIT_G711_H +#define ZLMEDIAKIT_G711_H + +#include "Extension/Frame.h" +#include "Extension/Track.h" + +namespace mediakit{ + +/** + * G711音频通道 + * G711 audio channel + + + * [AUTO-TRANSLATED:57f8bc08] + */ +class G711Track : public AudioTrackImp{ +public: + using Ptr = std::shared_ptr; + G711Track(CodecId codecId, int sample_rate, int channels, int sample_bit) : AudioTrackImp(codecId, 8000, 1, 16) {} + +private: + Sdp::Ptr getSdp(uint8_t payload_type) const override; + Track::Ptr clone() const override; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_G711_H \ No newline at end of file diff --git a/MediaServer/ext-codec/G711Rtp.cpp b/MediaServer/ext-codec/G711Rtp.cpp new file mode 100644 index 0000000..ac63056 --- /dev/null +++ b/MediaServer/ext-codec/G711Rtp.cpp @@ -0,0 +1,59 @@ +#include "G711Rtp.h" + +namespace mediakit { + +G711RtpEncoder::G711RtpEncoder(CodecId codec, uint32_t channels){ + _cache_frame = FrameImp::create(); + _cache_frame->_codec_id = codec; + _channels = channels; +} + +void G711RtpEncoder::setOpt(int opt, const toolkit::Any ¶m) { + if (opt == RTP_ENCODER_PKT_DUR_MS) { + if (param.is()) { + auto dur = param.get(); + if (dur < 20 || dur > 180) { + WarnL << "set g711 rtp encoder duration ms failed for " << dur; + return; + } + // 向上 20ms 取整 [AUTO-TRANSLATED:b8a9e39e] + // Round up to the nearest 20ms + _pkt_dur_ms = (dur + 19) / 20 * 20; + } + } +} + +bool G711RtpEncoder::inputFrame(const Frame::Ptr &frame) { + auto dur = (_cache_frame->size() - _cache_frame->prefixSize()) / (8 * _channels); + auto next_pts = _cache_frame->pts() + dur; + if (next_pts == 0) { + _cache_frame->_pts = frame->pts(); + } else { + if ((next_pts + _pkt_dur_ms) < frame->pts()) { // 有丢包超过20ms + _cache_frame->_pts = frame->pts() - dur; + } + } + _cache_frame->_buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + + auto stamp = _cache_frame->pts(); + auto ptr = _cache_frame->data() + _cache_frame->prefixSize(); + auto len = _cache_frame->size() - _cache_frame->prefixSize(); + auto remain_size = len; + size_t max_size = 160 * _channels * _pkt_dur_ms / 20; // 20 ms per 160 byte + size_t n = 0; + bool mark = false; + while (remain_size >= max_size) { + assert(remain_size >= max_size); + const size_t rtp_size = max_size; + n++; + stamp += _pkt_dur_ms; + RtpCodec::inputRtp(getRtpInfo().makeRtp(TrackAudio, ptr, rtp_size, mark, stamp), false); + ptr += rtp_size; + remain_size -= rtp_size; + } + _cache_frame->_buffer.erase(0, n * max_size); + _cache_frame->_pts += (uint64_t)_pkt_dur_ms * n; + return len > 0; +} + +} // namespace mediakit \ No newline at end of file diff --git a/MediaServer/ext-codec/G711Rtp.h b/MediaServer/ext-codec/G711Rtp.h new file mode 100644 index 0000000..e81ca9f --- /dev/null +++ b/MediaServer/ext-codec/G711Rtp.h @@ -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. + */ + +#ifndef ZLMEDIAKIT_G711RTP_H +#define ZLMEDIAKIT_G711RTP_H + +#include "Rtsp/RtpCodec.h" +#include "Extension/Frame.h" +#include "Extension/CommonRtp.h" + +namespace mediakit { + +/** + * G711 rtp编码类 + * G711 rtp encoding class + + * [AUTO-TRANSLATED:92aa6cf3] + */ +class G711RtpEncoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * @param codec 编码类型 + * @param channels 通道数 + * Constructor + * @param codec Encoding type + * @param channels Number of channels + + * [AUTO-TRANSLATED:dbbd593e] + */ + G711RtpEncoder(CodecId codec, uint32_t channels); + + /** + * 输入帧数据并编码成rtp + * Input frame data and encode it into rtp + + + * [AUTO-TRANSLATED:02bc9009] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + void setOpt(int opt, const toolkit::Any ¶m) override; + +private: + uint32_t _channels = 1; + uint32_t _pkt_dur_ms = 20; + FrameImp::Ptr _cache_frame; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_G711RTP_H diff --git a/MediaServer/ext-codec/H264.cpp b/MediaServer/ext-codec/H264.cpp new file mode 100644 index 0000000..fa0d97e --- /dev/null +++ b/MediaServer/ext-codec/H264.cpp @@ -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 "H264.h" +#include "H264Rtmp.h" +#include "H264Rtp.h" +#include "SPSParser.h" +#include "Util/logger.h" +#include "Util/base64.h" +#include "Common/Parser.h" +#include "Common/config.h" +#include "Extension/Factory.h" + +#ifdef ENABLE_MP4 +#include "mpeg4-avc.h" +#endif + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +static bool getAVCInfo(const char *sps, size_t sps_len, int &iVideoWidth, int &iVideoHeight, float &iVideoFps) { + if (sps_len < 4) { + return false; + } + T_GetBitContext tGetBitBuf; + T_SPS tH264SpsInfo; + memset(&tGetBitBuf, 0, sizeof(tGetBitBuf)); + memset(&tH264SpsInfo, 0, sizeof(tH264SpsInfo)); + tGetBitBuf.pu8Buf = (uint8_t *)sps + 1; + tGetBitBuf.iBufSize = (int)(sps_len - 1); + if (0 != h264DecSeqParameterSet((void *)&tGetBitBuf, &tH264SpsInfo)) { + return false; + } + h264GetWidthHeight(&tH264SpsInfo, &iVideoWidth, &iVideoHeight); + h264GeFramerate(&tH264SpsInfo, &iVideoFps); + // ErrorL << iVideoWidth << " " << iVideoHeight << " " << iVideoFps; + return true; +} + +bool getAVCInfo(const string &strSps, int &iVideoWidth, int &iVideoHeight, float &iVideoFps) { + return getAVCInfo(strSps.data(), strSps.size(), iVideoWidth, iVideoHeight, iVideoFps); +} + +static const char *memfind(const char *buf, ssize_t len, const char *subbuf, ssize_t sublen) { + for (auto i = 0; i < len - sublen; ++i) { + if (memcmp(buf + i, subbuf, sublen) == 0) { + return buf + i; + } + } + return NULL; +} + +void splitH264( + const char *ptr, size_t len, size_t prefix, const std::function &cb) { + auto start = ptr + prefix; + auto end = ptr + len; + size_t next_prefix; + while (true) { + auto next_start = memfind(start, end - start, "\x00\x00\x01", 3); + if (next_start) { + // 找到下一帧 [AUTO-TRANSLATED:7161f54a] + // Find the next frame + if (*(next_start - 1) == 0x00) { + // 这个是00 00 00 01开头 [AUTO-TRANSLATED:b0d79e9e] + // This starts with 00 00 00 01 + next_start -= 1; + next_prefix = 4; + } else { + // 这个是00 00 01开头 [AUTO-TRANSLATED:18ae81d8] + // This starts with 00 00 01 + next_prefix = 3; + } + // 记得加上本帧prefix长度 [AUTO-TRANSLATED:8bde5d52] + // Remember to add the prefix length of this frame + cb(start - prefix, next_start - start + prefix, prefix); + // 搜索下一帧末尾的起始位置 [AUTO-TRANSLATED:8976b719] + // Search for the starting position of the end of the next frame + start = next_start + next_prefix; + // 记录下一帧的prefix长度 [AUTO-TRANSLATED:756aee4e] + // Record the prefix length of the next frame + prefix = next_prefix; + continue; + } + // 未找到下一帧,这是最后一帧 [AUTO-TRANSLATED:58365453] + // The next frame was not found, this is the last frame + cb(start - prefix, end - start + prefix, prefix); + break; + } +} + +size_t prefixSize(const char *ptr, size_t len) { + if (len < 4) { + return 0; + } + + if (ptr[0] != 0x00 || ptr[1] != 0x00) { + // 不是0x00 00开头 [AUTO-TRANSLATED:c406f0da] + // Not 0x00 00 at the beginning + return 0; + } + + if (ptr[2] == 0x00 && ptr[3] == 0x01) { + // 是0x00 00 00 01 [AUTO-TRANSLATED:70caae72] + // It is 0x00 00 00 01 + return 4; + } + + if (ptr[2] == 0x01) { + // 是0x00 00 01 [AUTO-TRANSLATED:78b4a3c9] + // It is 0x00 00 01 + return 3; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +H264Track::H264Track(const string &sps, const string &pps, int sps_prefix_len, int pps_prefix_len) { + _sps = sps.substr(sps_prefix_len); + _pps = pps.substr(pps_prefix_len); + H264Track::update(); +} + +CodecId H264Track::getCodecId() const { + return CodecH264; +} + +int H264Track::getVideoHeight() const { + return _height; +} + +int H264Track::getVideoWidth() const { + return _width; +} + +float H264Track::getVideoFps() const { + return _fps; +} + +bool H264Track::ready() const { + return !_sps.empty() && !_pps.empty(); +} + +bool H264Track::inputFrame(const Frame::Ptr &frame) { + using H264FrameInternal = FrameInternal; + int type = H264_TYPE(frame->data()[frame->prefixSize()]); + + if ((type == H264Frame::NAL_B_P || type == H264Frame::NAL_IDR) && ready()) { + return inputFrame_l(frame); + } + + // 非I/B/P帧情况下,split一下,防止多个帧粘合在一起 [AUTO-TRANSLATED:b69c6e75] + // In the case of non-I/B/P frames, split it to prevent multiple frames from sticking together + bool ret = false; + splitH264(frame->data(), frame->size(), frame->prefixSize(), [&](const char *ptr, size_t len, size_t prefix) { + H264FrameInternal::Ptr sub_frame = std::make_shared(frame, (char *)ptr, len, prefix); + if (inputFrame_l(sub_frame)) { + ret = true; + } + }); + return ret; +} + +toolkit::Buffer::Ptr H264Track::getExtraData() const { + CHECK(ready()); + +#ifdef ENABLE_MP4 + struct mpeg4_avc_t avc; + memset(&avc, 0, sizeof(avc)); + string sps_pps = string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps; + h264_annexbtomp4(&avc, sps_pps.data(), (int)sps_pps.size(), NULL, 0, NULL, NULL); + + std::string extra_data; + extra_data.resize(1024); + auto extra_data_size = mpeg4_avc_decoder_configuration_record_save(&avc, (uint8_t *)extra_data.data(), extra_data.size()); + if (extra_data_size == -1) { + WarnL << "生成H264 extra_data 失败"; + return nullptr; + } + extra_data.resize(extra_data_size); + return std::make_shared(std::move(extra_data)); +#else + std::string extra_data; + // AVCDecoderConfigurationRecord start + extra_data.push_back(1); // version + extra_data.push_back(_sps[1]); // profile + extra_data.push_back(_sps[2]); // compat + extra_data.push_back(_sps[3]); // level + extra_data.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) + extra_data.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) + // sps + uint16_t size = (uint16_t)_sps.size(); + size = htons(size); + extra_data.append((char *)&size, 2); + extra_data.append(_sps); + // pps + extra_data.push_back(1); // version + size = (uint16_t)_pps.size(); + size = htons(size); + extra_data.append((char *)&size, 2); + extra_data.append(_pps); + return std::make_shared(std::move(extra_data)); +#endif +} + +void H264Track::setExtraData(const uint8_t *data, size_t bytes) { +#ifdef ENABLE_MP4 + struct mpeg4_avc_t avc; + memset(&avc, 0, sizeof(avc)); + if (mpeg4_avc_decoder_configuration_record_load(data, bytes, &avc) > 0) { + std::vector config(bytes * 2); + int size = mpeg4_avc_to_nalu(&avc, config.data(), bytes * 2); + if (size > 4) { + splitH264((char *)config.data(), size, 4, [&](const char *ptr, size_t len, size_t prefix) { + inputFrame_l(std::make_shared((char *)ptr, len, 0, 0, prefix)); + }); + update(); + } + } +#else + CHECK(bytes >= 8); // 6 + 2 + size_t offset = 6; + + uint16_t sps_size = data[offset] << 8 | data[offset + 1]; + auto sps_ptr = data + offset + 2; + offset += (2 + sps_size); + CHECK(bytes >= offset + 2); // + pps_size + _sps.assign((char *)sps_ptr, sps_size); + + uint16_t pps_size = data[offset] << 8 | data[offset + 1]; + auto pps_ptr = data + offset + 2; + offset += (2 + pps_size); + CHECK(bytes >= offset); + _pps.assign((char *)pps_ptr, pps_size); + update(); +#endif +} + +bool H264Track::update() { + return getAVCInfo(_sps, _width, _height, _fps); +} + +std::vector H264Track::getConfigFrames() const { + if (!ready()) { + return {}; + } + return { createConfigFrame(_sps, 0, getIndex()), + createConfigFrame(_pps, 0, getIndex()) }; +} + +Track::Ptr H264Track::clone() const { + return std::make_shared(*this); +} + +bool H264Track::inputFrame_l(const Frame::Ptr &frame) { + int type = H264_TYPE(frame->data()[frame->prefixSize()]); + bool ret = true; + switch (type) { + case H264Frame::NAL_SPS: { + _sps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + _latest_is_config_frame = true; + ret = VideoTrack::inputFrame(frame); + break; + } + case H264Frame::NAL_PPS: { + _pps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + _latest_is_config_frame = true; + ret = VideoTrack::inputFrame(frame); + break; + } + default: + // 避免识别不出关键帧 [AUTO-TRANSLATED:8eb84679] + // Avoid not being able to recognize keyframes + if (_latest_is_config_frame && !frame->dropAble()) { + if (!frame->keyFrame()) { + const_cast(frame) = std::make_shared(frame, true); + } + } + // 判断是否是I帧, 并且如果是,那判断前面是否插入过config帧, 如果插入过就不插入了 [AUTO-TRANSLATED:40733cd8] + // Determine if it is an I frame, and if it is, determine if a config frame has been inserted before, and if it has been inserted, do not insert it + if (frame->keyFrame() && !_latest_is_config_frame) { + insertConfigFrame(frame); + } + if(!frame->dropAble()){ + _latest_is_config_frame = false; + } + ret = VideoTrack::inputFrame(frame); + break; + } + + if (_width == 0 && ready()) { + update(); + } + return ret; +} + +void H264Track::insertConfigFrame(const Frame::Ptr &frame) { + if (!_sps.empty()) { + VideoTrack::inputFrame(createConfigFrame(_sps, frame->dts(), frame->getIndex())); + } + + if (!_pps.empty()) { + VideoTrack::inputFrame(createConfigFrame(_pps, frame->dts(), frame->getIndex())); + } +} + +class H264Sdp : public Sdp { +public: + H264Sdp(const string &strSPS, const string &strPPS, int payload_type, int bitrate) : Sdp(90000, payload_type) { + _printer << "m=video 0 RTP/AVP " << payload_type << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + _printer << "a=rtpmap:" << payload_type << " " << getCodecName(CodecH264) << "/" << 90000 << "\r\n"; + + /** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + * + * [AUTO-TRANSLATED:6166738f] + **/ + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + _printer << "a=fmtp:" << payload_type << " packetization-mode=" << h264_stap_a << "; profile-level-id="; + + uint32_t profile_level_id = 0; + if (strSPS.length() >= 4) { // sanity check + profile_level_id = (uint8_t(strSPS[1]) << 16) | + (uint8_t(strSPS[2]) << 8) | + (uint8_t(strSPS[3])); // profile_idc|constraint_setN_flag|level_idc + } + + char profile[8]; + snprintf(profile, sizeof(profile), "%06X", profile_level_id); + _printer << profile; + _printer << "; sprop-parameter-sets="; + _printer << encodeBase64(strSPS) << ","; + _printer << encodeBase64(strPPS) << "\r\n"; + } + + string getSdp() const { return _printer; } + +private: + _StrPrinter _printer; +}; + +Sdp::Ptr H264Track::getSdp(uint8_t payload_type) const { + if (!ready()) { + WarnL << getCodecName() << " Track未准备好"; + return nullptr; + } + return std::make_shared(_sps, _pps, payload_type, getBitRate() / 1024); +} + +namespace { + +CodecId getCodec() { + return CodecH264; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + //a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH9oBQBboQAAAAwBAAAAPI8YMqA==,aM48gA== + auto map = Parser::parseArgs(track->_fmtp, ";", "="); + auto sps_pps = map["sprop-parameter-sets"]; + string base64_SPS = findSubString(sps_pps.data(), NULL, ","); + string base64_PPS = findSubString(sps_pps.data(), ",", NULL); + auto sps = decodeBase64(base64_SPS); + auto pps = decodeBase64(base64_PPS); + if (sps.empty() || pps.empty()) { + // 如果sdp里面没有sps/pps,那么可能在后续的rtp里面恢复出sps/pps [AUTO-TRANSLATED:60f03d45] + // If there is no sps/pps in the sdp, then it may be possible to recover the sps/pps in the subsequent rtp + return std::make_shared(); + } + return std::make_shared(sps, pps, 0, 0); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared((char *)data, bytes, dts, pts, prefixSize(data, bytes)); +} + +} // namespace + +CodecPlugin h264_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +} // namespace mediakit diff --git a/MediaServer/ext-codec/H264.h b/MediaServer/ext-codec/H264.h new file mode 100644 index 0000000..c65c847 --- /dev/null +++ b/MediaServer/ext-codec/H264.h @@ -0,0 +1,171 @@ +/* + * 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_H264_H +#define ZLMEDIAKIT_H264_H + +#include "Extension/Frame.h" +#include "Extension/Track.h" + +#define H264_TYPE(v) ((uint8_t)(v) & 0x1F) + +namespace mediakit{ + +void splitH264(const char *ptr, size_t len, size_t prefix, const std::function &cb); +size_t prefixSize(const char *ptr, size_t len); + +template +class H264FrameHelper : public Parent{ +public: + friend class FrameImp; + friend class toolkit::ResourcePool_l; + using Ptr = std::shared_ptr; + + enum { + NAL_IDR = 5, + NAL_SEI = 6, + NAL_SPS = 7, + NAL_PPS = 8, + NAL_AUD = 9, + NAL_B_P = 1, + }; + + template + H264FrameHelper(ARGS &&...args): Parent(std::forward(args)...) { + this->_codec_id = CodecH264; + } + + bool keyFrame() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + return H264_TYPE(*nal_ptr) == NAL_IDR && decodeAble(); + } + + bool configFrame() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + switch (H264_TYPE(*nal_ptr)) { + case NAL_SPS: + case NAL_PPS: return true; + default: return false; + } + } + + bool dropAble() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + switch (H264_TYPE(*nal_ptr)) { + case NAL_SEI: + case NAL_AUD: return true; + default: return false; + } + } + + bool decodeAble() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + auto type = H264_TYPE(*nal_ptr); + // 多slice情况下, first_mb_in_slice 表示其为一帧的开始 [AUTO-TRANSLATED:80e88e88] + // // In the case of multiple slices, first_mb_in_slice indicates the start of a frame + return type >= NAL_B_P && type <= NAL_IDR && (nal_ptr[1] & 0x80); + } +}; + +/** + * 264帧类 + * 264 frame class + + * [AUTO-TRANSLATED:342ccb1e] + */ +using H264Frame = H264FrameHelper; + +/** + * 防止内存拷贝的H264类 + * 用户可以通过该类型快速把一个指针无拷贝的包装成Frame类 + * H264 class that prevents memory copying + * Users can quickly wrap a pointer into a Frame class without copying using this type + + * [AUTO-TRANSLATED:ff9be1c8] + */ +using H264FrameNoCacheAble = H264FrameHelper; + +/** + * 264视频通道 + * 264 video channel + + * [AUTO-TRANSLATED:6936e76d] + */ +class H264Track : public VideoTrack { +public: + using Ptr = std::shared_ptr; + + /** + * 不指定sps pps构造h264类型的媒体 + * 在随后的inputFrame中获取sps pps + * Construct a media of h264 type without specifying sps pps + * Get sps pps in the subsequent inputFrame + + * [AUTO-TRANSLATED:84d01c7f] + */ + H264Track() = default; + + /** + * 构造h264类型的媒体 + * @param sps sps帧数据 + * @param pps pps帧数据 + * @param sps_prefix_len 264头长度,可以为3个或4个字节,一般为0x00 00 00 01 + * @param pps_prefix_len 264头长度,可以为3个或4个字节,一般为0x00 00 00 01 + * Construct a media of h264 type + * @param sps sps frame data + * @param pps pps frame data + * @param sps_prefix_len 264 header length, can be 3 or 4 bytes, generally 0x00 00 00 01 + * @param pps_prefix_len 264 header length, can be 3 or 4 bytes, generally 0x00 00 00 01 + + + * [AUTO-TRANSLATED:702c1433] + */ + H264Track(const std::string &sps, const std::string &pps, int sps_prefix_len = 4, int pps_prefix_len = 4); + + bool ready() const override; + CodecId getCodecId() const override; + int getVideoHeight() const override; + int getVideoWidth() const override; + float getVideoFps() const override; + bool inputFrame(const Frame::Ptr &frame) override; + toolkit::Buffer::Ptr getExtraData() const override; + void setExtraData(const uint8_t *data, size_t size) override; + bool update() override; + std::vector getConfigFrames() const override; + +private: + Sdp::Ptr getSdp(uint8_t payload_type) const override; + Track::Ptr clone() const override; + bool inputFrame_l(const Frame::Ptr &frame); + void insertConfigFrame(const Frame::Ptr &frame); + +private: + bool _latest_is_config_frame = false; + int _width = 0; + int _height = 0; + float _fps = 0; + std::string _sps; + std::string _pps; +}; + +template +Frame::Ptr createConfigFrame(const std::string &data, uint64_t dts, int index) { + auto frame = FrameImp::create(); + frame->_prefix_size = 4; + frame->_buffer.assign("\x00\x00\x00\x01", 4); + frame->_buffer.append(data); + frame->_dts = dts; + frame->setIndex(index); + return frame; +} + +}//namespace mediakit + +#endif //ZLMEDIAKIT_H264_H diff --git a/MediaServer/ext-codec/H264Rtmp.cpp b/MediaServer/ext-codec/H264Rtmp.cpp new file mode 100644 index 0000000..ed431e9 --- /dev/null +++ b/MediaServer/ext-codec/H264Rtmp.cpp @@ -0,0 +1,111 @@ +/* + * 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 "Rtmp/utils.h" +#include "H264Rtmp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { + if (pkt->isConfigFrame()) { + CHECK_RET(pkt->size() > 5); + getTrack()->setExtraData((uint8_t *)pkt->data() + 5, pkt->size() - 5); + return; + } + + CHECK_RET(pkt->size() > 9); + uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2); + int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000; + auto pts = pkt->time_stamp + cts; + splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts); +} + +void H264RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) { + auto end = data + size; + while (data + 4 < end) { + uint32_t frame_len = load_be32(data); + data += 4; + if (data + frame_len > end) { + break; + } + outputFrame((const char *)data, frame_len, dts, pts); + data += frame_len; + } +} + +void H264RtmpDecoder::outputFrame(const char *data, size_t len, uint32_t dts, uint32_t pts) { + auto frame = FrameImp::create(); + frame->_prefix_size = 4; + frame->_dts = dts; + frame->_pts = pts; + frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加264头 + frame->_buffer.append(data, len); + RtmpCodec::inputFrame(frame); +} + +//////////////////////////////////////////////////////////////////////// + +void H264RtmpEncoder::flush() { + inputFrame(nullptr); +} + +bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { + if (!_rtmp_packet) { + _rtmp_packet = RtmpPacket::create(); + // flags/not config/cts预占位 [AUTO-TRANSLATED:7effb692] + // flags/not config/cts placeholder + _rtmp_packet->buffer.resize(5); + } + + return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet [AUTO-TRANSLATED:d72e89a7] + // Output rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; + }, &_rtmp_packet->buffer); +} + +void H264RtmpEncoder::makeConfigPacket() { + auto flags = (uint8_t)RtmpVideoCodec::h264; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); + // AVCDecoderConfigurationRecord start + auto extra_data = getTrack()->getExtraData(); + CHECK(extra_data); + pkt->buffer.append(extra_data->data(), extra_data->size()); + + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); +} + +}//namespace mediakit diff --git a/MediaServer/ext-codec/H264Rtmp.h b/MediaServer/ext-codec/H264Rtmp.h new file mode 100644 index 0000000..eafadf0 --- /dev/null +++ b/MediaServer/ext-codec/H264Rtmp.h @@ -0,0 +1,106 @@ +/* + * 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_H264RTMPCODEC_H +#define ZLMEDIAKIT_H264RTMPCODEC_H + +#include "H264.h" +#include "Rtmp/RtmpCodec.h" +#include "Extension/Track.h" + +namespace mediakit { +/** + * h264 Rtmp解码类 + * 将 h264 over rtmp 解复用出 h264-Frame + * h264 Rtmp decoder class + * Demultiplex h264-Frame from h264 over rtmp + + * [AUTO-TRANSLATED:4908a1f3] + */ +class H264RtmpDecoder : public RtmpCodec { +public: + using Ptr = std::shared_ptr; + + H264RtmpDecoder(const Track::Ptr &track) : RtmpCodec(track) {} + + /** + * 输入264 Rtmp包 + * @param rtmp Rtmp包 + * Input 264 Rtmp package + * @param rtmp Rtmp package + + * [AUTO-TRANSLATED:06f3e94c] + */ + void inputRtmp(const RtmpPacket::Ptr &rtmp) override; + +private: + void outputFrame(const char *data, size_t len, uint32_t dts, uint32_t pts); + void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts); +}; + +/** + * 264 Rtmp打包类 + * 264 Rtmp packaging class + + * [AUTO-TRANSLATED:e5bc7c66] + */ +class H264RtmpEncoder : public RtmpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数,track可以为空,此时则在inputFrame时输入sps pps + * 如果track不为空且包含sps pps信息, + * 那么inputFrame时可以不输入sps pps + * @param track + * Constructor, track can be empty, in which case sps pps is input when inputFrame + * If track is not empty and contains sps pps information, + * then sps pps can be omitted when inputFrame + * @param track + + * [AUTO-TRANSLATED:e61fdfed] + */ + H264RtmpEncoder(const Track::Ptr &track) : RtmpCodec(track) {} + + /** + * 输入264帧,可以不带sps pps + * @param frame 帧数据 + * Input 264 frame, sps pps can be omitted + * @param frame Frame data + + * [AUTO-TRANSLATED:caefd055] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame cache output + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + + /** + * 生成config包 + * Generate config package + + + * [AUTO-TRANSLATED:8f851364] + */ + void makeConfigPacket() override; + +private: + RtmpPacket::Ptr _rtmp_packet; + FrameMerger _merger { FrameMerger::mp4_nal_size }; +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_H264RTMPCODEC_H diff --git a/MediaServer/ext-codec/H264Rtp.cpp b/MediaServer/ext-codec/H264Rtp.cpp new file mode 100644 index 0000000..5c800eb --- /dev/null +++ b/MediaServer/ext-codec/H264Rtp.cpp @@ -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 "H264Rtp.h" +#include "Common/config.h" + +namespace mediakit{ + +#pragma pack(push, 1) + +class FuFlags { +public: +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned start_bit: 1; + unsigned end_bit: 1; + unsigned reserved: 1; + unsigned nal_type: 5; +#else + unsigned nal_type: 5; + unsigned reserved: 1; + unsigned end_bit: 1; + unsigned start_bit: 1; +#endif +}; + +#pragma pack(pop) + +H264RtpDecoder::H264RtpDecoder() { + _frame = obtainFrame(); +} + +H264Frame::Ptr H264RtpDecoder::obtainFrame() { + auto frame = FrameImp::create(); + frame->_prefix_size = 4; + return frame; +} + +bool H264RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) { + auto seq = rtp->getSeq(); + auto last_is_gop = _is_gop; + _is_gop = decodeRtp(rtp); + if (!_gop_dropped && seq != (uint16_t)(_last_seq + 1) && _last_seq) { + _gop_dropped = true; + WarnL << "start drop h264 gop, last seq:" << _last_seq << ", rtp:\r\n" << rtp->dumpString(); + } + _last_seq = seq; + // 确保有sps rtp的时候,gop从sps开始;否则从关键帧开始 [AUTO-TRANSLATED:115ae07c] + // cpp +// Ensure that when there is sps rtp, the gop starts from sps; otherwise, it starts from the key frame + return _is_gop && !last_is_gop; +} + +/* +RTF3984 5.2节 Common Structure of the RTP Payload Format +Table 1. Summary of NAL unit types and their payload structures + + Type Packet Type name Section + --------------------------------------------------------- + 0 undefined - + 1-23 NAL unit Single NAL unit packet per H.264 5.6 + 24 STAP-A Single-time aggregation packet 5.7.1 + 25 STAP-B Single-time aggregation packet 5.7.1 + 26 MTAP16 Multi-time aggregation packet 5.7.2 + 27 MTAP24 Multi-time aggregation packet 5.7.2 + 28 FU-A Fragmentation unit 5.8 + 29 FU-B Fragmentation unit 5.8 + 30-31 undefined - + /* + RTF3984 Section 5.2 Common Structure of the RTP Payload Format + Table 1. Summary of NAL unit types and their payload structures + + Type Packet Type name Section + --------------------------------------------------------- + 0 undefined - + 1-23 NAL unit Single NAL unit packet per H.264 5.6 + 24 STAP-A Single-time aggregation packet 5.7.1 + 25 STAP-B Single-time aggregation packet 5.7.1 + 26 MTAP16 Multi-time aggregation packet 5.7.2 + 27 MTAP24 Multi-time aggregation packet 5.7.2 + 28 FU-A Fragmentation unit 5.8 + 29 FU-B Fragmentation unit 5.8 + 30-31 undefined - + + * [AUTO-TRANSLATED:57545317] +*/ + +bool H264RtpDecoder::singleFrame(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp){ + _frame->_buffer.assign("\x00\x00\x00\x01", 4); + _frame->_buffer.append((char *) ptr, size); + _frame->_pts = stamp; + auto key = _frame->keyFrame() || _frame->configFrame(); + outputFrame(rtp, _frame); + return key; +} + +bool H264RtpDecoder::unpackStapA(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp) { + // STAP-A 单一时间的组合包 [AUTO-TRANSLATED:cfa62307] + // STAP-A single-time aggregation packet + auto have_key_frame = false; + auto end = ptr + size; + while (ptr + 2 < end) { + uint16_t len = (ptr[0] << 8) | ptr[1]; + if (!len || ptr + len > end) { + WarnL << "invalid rtp data size:" << len << ",rtp:\r\n" << rtp->dumpString(); + _gop_dropped = true; + break; + } + ptr += 2; + if (singleFrame(rtp, ptr, len, stamp)) { + have_key_frame = true; + } + ptr += len; + } + return have_key_frame; +} + +bool H264RtpDecoder::mergeFu(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp, uint16_t seq){ + auto nal_suffix = *ptr & (~0x1F); + FuFlags *fu = (FuFlags *) (ptr + 1); + if (fu->start_bit) { + // 该帧的第一个rtp包 [AUTO-TRANSLATED:a9581a23] + // The first rtp packet of this frame + _frame->_buffer.assign("\x00\x00\x00\x01", 4); + _frame->_buffer.push_back(nal_suffix | fu->nal_type); + _frame->_pts = stamp; + _fu_dropped = false; + } + + if (_fu_dropped) { + // 该帧不完整 [AUTO-TRANSLATED:6bd7eca7] + // This frame is incomplete + return false; + } + + if (!fu->start_bit && seq != (uint16_t) (_last_seq + 1)) { + // 中间的或末尾的rtp包,其seq必须连续,否则说明rtp丢包,那么该帧不完整,必须得丢弃 [AUTO-TRANSLATED:6953b332] + // The middle or end rtp packet, its seq must be continuous, otherwise it indicates that the rtp packet is lost, then the frame is incomplete and must be discarded + _fu_dropped = true; + _frame->_buffer.clear(); + return false; + } + + // 后面追加数据 [AUTO-TRANSLATED:248516e9] + // Append data + _frame->_buffer.append((char *) ptr + 2, size - 2); + + if (!fu->end_bit) { + // 非末尾包 [AUTO-TRANSLATED:2e43ac3c] + // Not the end packet + return fu->start_bit ? (_frame->keyFrame() || _frame->configFrame()) : false; + } + + // 确保下一次fu必须收到第一个包 [AUTO-TRANSLATED:491d81ec] + // Ensure that the next fu must receive the first packet + _fu_dropped = true; + // 该帧最后一个rtp包,输出frame [AUTO-TRANSLATED:a648aaa5] + // The last rtp packet of this frame, output frame + outputFrame(rtp, _frame); + return false; +} + +bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtp) { + auto payload_size = rtp->getPayloadSize(); + if (payload_size <= 0) { + // 无实际负载 [AUTO-TRANSLATED:305af48f] + // No actual payload + return false; + } + auto frame = rtp->getPayload(); + auto stamp = rtp->getStampMS(); + auto seq = rtp->getSeq(); + int nal = H264_TYPE(frame[0]); + + switch (nal) { + case 24: + // 24 STAP-A Single-time aggregation packet 5.7.1 + return unpackStapA(rtp, frame + 1, payload_size - 1, stamp); + + case 28: + // 28 FU-A Fragmentation unit + return mergeFu(rtp, frame, payload_size, stamp, seq); + + default: { + if (nal < 24) { + //Single NAL Unit Packets + return singleFrame(rtp, frame, payload_size, stamp); + } + _gop_dropped = true; + WarnL << "不支持该类型的264 RTP包, nal type:" << nal << ", rtp:\r\n" << rtp->dumpString(); + return false; + } + } +} + +void H264RtpDecoder::outputFrame(const RtpPacket::Ptr &rtp, const H264Frame::Ptr &frame) { + if (frame->dropAble()) { + // 不参与dts生成 [AUTO-TRANSLATED:dff3b747] + // Not involved in dts generation + frame->_dts = frame->_pts; + } else { + // rtsp没有dts,那么根据pts排序算法生成dts [AUTO-TRANSLATED:f37c17f3] + // Rtsp does not have dts, so dts is generated according to the pts sorting algorithm + _dts_generator.getDts(frame->_pts, frame->_dts); + } + + if (frame->keyFrame() && _gop_dropped) { + _gop_dropped = false; + InfoL << "new gop received, rtp:\r\n" << rtp->dumpString(); + } + if (!_gop_dropped || frame->configFrame()) { + RtpCodec::inputFrame(frame); + } + _frame = obtainFrame(); +} + +//////////////////////////////////////////////////////////////////////// + +void H264RtpEncoder::insertConfigFrame(uint64_t pts){ + if (!_sps || !_pps) { + return; + } + // gop缓存从sps开始,sps、pps后面还有时间戳相同的关键帧,所以mark bit为false [AUTO-TRANSLATED:e8dcff77] + // The gop cache starts from sps, sps, pps and then there are key frames with the same timestamp, so the mark bit is false + packRtp(_sps->data() + _sps->prefixSize(), _sps->size() - _sps->prefixSize(), pts, false, true); + packRtp(_pps->data() + _pps->prefixSize(), _pps->size() - _pps->prefixSize(), pts, false, false); +} + +void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ + if (len + 3 <= getRtpInfo().getMaxSize()) { + // 采用STAP-A/Single NAL unit packet per H.264 模式 [AUTO-TRANSLATED:1a719984] + // Use STAP-A/Single NAL unit packet per H.264 mode + packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos); + } else { + // STAP-A模式打包会大于MTU,所以采用FU-A模式 [AUTO-TRANSLATED:f3923abc] + // STAP-A mode packaging will be larger than MTU, so FU-A mode is used + packRtpFu(ptr, len, pts, is_mark, gop_pos); + } +} + +void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ + auto packet_size = getRtpInfo().getMaxSize() - 2; + if (len <= packet_size + 1) { + // 小于FU-A打包最小字节长度要求,采用STAP-A/Single NAL unit packet per H.264 模式 [AUTO-TRANSLATED:b83bb4d1] + // Less than the minimum byte length requirement for FU-A packaging, use STAP-A/Single NAL unit packet per H.264 mode + packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos); + return; + } + + // 末尾5bit为nalu type,固定为28(FU-A) [AUTO-TRANSLATED:6293f1a9] + // The last 5 bits are the nalu type, fixed to 28 (FU-A) + auto fu_char_0 = (ptr[0] & (~0x1F)) | 28; + auto fu_char_1 = H264_TYPE(ptr[0]); + FuFlags *fu_flags = (FuFlags *) (&fu_char_1); + fu_flags->start_bit = 1; + + size_t offset = 1; + while (!fu_flags->end_bit) { + if (!fu_flags->start_bit && len <= offset + packet_size) { + //FU-A end + packet_size = len - offset; + fu_flags->end_bit = 1; + } + + // 传入nullptr先不做payload的内存拷贝 [AUTO-TRANSLATED:1858cf77] + // Pass in nullptr first, do not copy the payload memory + auto rtp = getRtpInfo().makeRtp(TrackVideo, nullptr, packet_size + 2, fu_flags->end_bit && is_mark, pts); + // rtp payload 负载部分 [AUTO-TRANSLATED:aecf73cc] + // rtp payload load part + uint8_t *payload = rtp->getPayload(); + // FU-A 第1个字节 [AUTO-TRANSLATED:b5558495] + // FU-A first byte + payload[0] = fu_char_0; + // FU-A 第2个字节 [AUTO-TRANSLATED:6b4540bb] + // FU-A second byte + payload[1] = fu_char_1; + // H264 数据 [AUTO-TRANSLATED:79204239] + // H264 data + memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size); + // 输入到rtp环形缓存 [AUTO-TRANSLATED:5208ef90] + // Input to the rtp ring buffer + RtpCodec::inputRtp(rtp, gop_pos); + + offset += packet_size; + fu_flags->start_bit = 0; + } +} + +void H264RtpEncoder::packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) { + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + if (h264_stap_a) { + packRtpStapA(data, len, pts, is_mark, gop_pos); + } else { + packRtpSingleNalu(data, len, pts, is_mark, gop_pos); + } +} + +void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ + // 如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包 [AUTO-TRANSLATED:a091199c] + // If the frame length does not exceed mtu, for compatibility with webrtc, use STAP-A mode packaging + auto rtp = getRtpInfo().makeRtp(TrackVideo, nullptr, len + 3, is_mark, pts); + uint8_t *payload = rtp->getPayload(); + //STAP-A + payload[0] = (ptr[0] & (~0x1F)) | 24; + payload[1] = (len >> 8) & 0xFF; + payload[2] = len & 0xff; + memcpy(payload + 3, (uint8_t *) ptr, len); + + RtpCodec::inputRtp(rtp, gop_pos); +} + +void H264RtpEncoder::packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) { + // Single NAL unit packet per H.264 模式 [AUTO-TRANSLATED:9332a8e4] + // Single NAL unit packet per H.264 mode + RtpCodec::inputRtp(getRtpInfo().makeRtp(TrackVideo, data, len, is_mark, pts), gop_pos); +} + +bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { + auto ptr = frame->data() + frame->prefixSize(); + switch (H264_TYPE(ptr[0])) { + case H264Frame::NAL_SPS: { + _sps = Frame::getCacheAbleFrame(frame); + return true; + } + case H264Frame::NAL_PPS: { + _pps = Frame::getCacheAbleFrame(frame); + return true; + } + default: break; + } + + GET_CONFIG(int,lowLatency,Rtp::kLowLatency); + if (lowLatency) { // 低延迟模式 + if (_last_frame) { + flush(); + } + inputFrame_l(frame, true); + } else { + if (_last_frame) { + // 如果时间戳发生了变化,那么markbit才置true [AUTO-TRANSLATED:19b68429] + // If the timestamp changes, then the markbit is set to true + inputFrame_l(_last_frame, _last_frame->pts() != frame->pts()); + } + _last_frame = Frame::getCacheAbleFrame(frame); + } + return true; +} + +void H264RtpEncoder::flush() { + if (_last_frame) { + // 如果时间戳发生了变化,那么markbit才置true [AUTO-TRANSLATED:6b1d0fe0] + // If the timestamp changes, then the markbit is set to true + inputFrame_l(_last_frame, true); + _last_frame = nullptr; + } +} + +bool H264RtpEncoder::inputFrame_l(const Frame::Ptr &frame, bool is_mark){ + if (frame->keyFrame()) { + // 保证每一个关键帧前都有SPS与PPS [AUTO-TRANSLATED:9d1a9d5e] + // Ensure that there are SPS and PPS before each key frame + insertConfigFrame(frame->pts()); + } + packRtp(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), frame->pts(), is_mark, false); + return true; +} + +}//namespace mediakit diff --git a/MediaServer/ext-codec/H264Rtp.h b/MediaServer/ext-codec/H264Rtp.h new file mode 100644 index 0000000..f7a2c57 --- /dev/null +++ b/MediaServer/ext-codec/H264Rtp.h @@ -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_H264RTPCODEC_H +#define ZLMEDIAKIT_H264RTPCODEC_H + +#include "H264.h" +// for DtsGenerator +#include "Common/Stamp.h" +#include "Rtsp/RtpCodec.h" + +namespace mediakit { + +/** + * h264 rtp解码类 + * 将 h264 over rtsp-rtp 解复用出 h264-Frame + * rfc3984 + * h264 rtp decoder class + * Demultiplex h264-Frame from h264 over rtsp-rtp + * rfc3984 + + * [AUTO-TRANSLATED:84b4831b] + */ +class H264RtpDecoder : public RtpCodec{ +public: + using Ptr = std::shared_ptr; + + H264RtpDecoder(); + + /** + * 输入264 rtp包 + * @param rtp rtp包 + * @param key_pos 此参数忽略之 + * Input 264 rtp packet + * @param rtp rtp packet + * @param key_pos This parameter is ignored + + * [AUTO-TRANSLATED:a9ed29db] + */ + bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = true) override; + +private: + bool singleFrame(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp); + bool unpackStapA(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp); + bool mergeFu(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp, uint16_t seq); + + bool decodeRtp(const RtpPacket::Ptr &rtp); + H264Frame::Ptr obtainFrame(); + void outputFrame(const RtpPacket::Ptr &rtp, const H264Frame::Ptr &frame); + +private: + bool _is_gop = false; + bool _gop_dropped = false; + bool _fu_dropped = true; + uint16_t _last_seq = 0; + H264Frame::Ptr _frame; + DtsGenerator _dts_generator; +}; + +/** + * 264 rtp打包类 + * 264 rtp packaging class + + * [AUTO-TRANSLATED:baed5b50] + */ +class H264RtpEncoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 输入264帧 + * @param frame 帧数据,必须 + * Input 264 frame + * @param frame Frame data, required + + * [AUTO-TRANSLATED:1190bc60] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame buffers in the output + + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + +private: + void insertConfigFrame(uint64_t pts); + bool inputFrame_l(const Frame::Ptr &frame, bool is_mark); + void packRtp(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpFu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpStapA(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + +private: + Frame::Ptr _sps; + Frame::Ptr _pps; + Frame::Ptr _last_frame; +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_H264RTPCODEC_H diff --git a/MediaServer/ext-codec/H265.cpp b/MediaServer/ext-codec/H265.cpp new file mode 100644 index 0000000..8566bb1 --- /dev/null +++ b/MediaServer/ext-codec/H265.cpp @@ -0,0 +1,329 @@ +/* + * 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 "H265.h" +#include "H265Rtp.h" +#include "H265Rtmp.h" +#include "SPSParser.h" +#include "Util/base64.h" +#include "Common/Parser.h" +#include "Extension/Factory.h" + +#ifdef ENABLE_MP4 +#include "mpeg4-hevc.h" +#endif + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +bool getHEVCInfo(const char * vps, size_t vps_len,const char * sps,size_t sps_len,int &iVideoWidth, int &iVideoHeight, float &iVideoFps){ + T_GetBitContext tGetBitBuf; + T_HEVCSPS tH265SpsInfo; + T_HEVCVPS tH265VpsInfo; + if ( vps_len > 2 ){ + memset(&tGetBitBuf,0,sizeof(tGetBitBuf)); + memset(&tH265VpsInfo,0,sizeof(tH265VpsInfo)); + tGetBitBuf.pu8Buf = (uint8_t*)vps+2; + tGetBitBuf.iBufSize = (int)(vps_len-2); + if(0 != h265DecVideoParameterSet((void *) &tGetBitBuf, &tH265VpsInfo)){ + return false; + } + } + + if ( sps_len > 2 ){ + memset(&tGetBitBuf,0,sizeof(tGetBitBuf)); + memset(&tH265SpsInfo,0,sizeof(tH265SpsInfo)); + tGetBitBuf.pu8Buf = (uint8_t*)sps+2; + tGetBitBuf.iBufSize = (int)(sps_len-2); + if(0 != h265DecSeqParameterSet((void *) &tGetBitBuf, &tH265SpsInfo)){ + return false; + } + } + else + return false; + h265GetWidthHeight(&tH265SpsInfo, &iVideoWidth, &iVideoHeight); + iVideoFps = 0; + h265GeFramerate(&tH265VpsInfo, &tH265SpsInfo, &iVideoFps); + return true; +} + +bool getHEVCInfo(const string &strVps, const string &strSps, int &iVideoWidth, int &iVideoHeight, float &iVideoFps) { + return getHEVCInfo(strVps.data(), strVps.size(), strSps.data(), strSps.size(), iVideoWidth, iVideoHeight,iVideoFps); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +H265Track::H265Track(const string &vps,const string &sps, const string &pps,int vps_prefix_len, int sps_prefix_len, int pps_prefix_len) { + _vps = vps.substr(vps_prefix_len); + _sps = sps.substr(sps_prefix_len); + _pps = pps.substr(pps_prefix_len); + H265Track::update(); +} + +CodecId H265Track::getCodecId() const { + return CodecH265; +} + +int H265Track::getVideoHeight() const { + return _height; +} + +int H265Track::getVideoWidth() const { + return _width; +} + +float H265Track::getVideoFps() const { + return _fps; +} + +bool H265Track::ready() const { + return !_vps.empty() && !_sps.empty() && !_pps.empty(); +} + +bool H265Track::inputFrame(const Frame::Ptr &frame) { + int type = H265_TYPE(frame->data()[frame->prefixSize()]); + if (!frame->configFrame() && type != H265Frame::NAL_SEI_PREFIX && ready()) { + return inputFrame_l(frame); + } + bool ret = false; + splitH264(frame->data(), frame->size(), frame->prefixSize(), [&](const char *ptr, size_t len, size_t prefix) { + using H265FrameInternal = FrameInternal; + H265FrameInternal::Ptr sub_frame = std::make_shared(frame, (char *) ptr, len, prefix); + if (inputFrame_l(sub_frame)) { + ret = true; + } + }); + return ret; +} + +bool H265Track::inputFrame_l(const Frame::Ptr &frame) { + int type = H265_TYPE(frame->data()[frame->prefixSize()]); + bool ret = true; + switch (type) { + case H265Frame::NAL_VPS: { + _vps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + _latest_is_config_frame = true; + ret = VideoTrack::inputFrame(frame); + break; + } + case H265Frame::NAL_SPS: { + _sps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + _latest_is_config_frame = true; + ret = VideoTrack::inputFrame(frame); + break; + } + case H265Frame::NAL_PPS: { + _pps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + _latest_is_config_frame = true; + ret = VideoTrack::inputFrame(frame); + break; + } + default: { + // 判断是否是I帧, 并且如果是,那判断前面是否插入过config帧, 如果插入过就不插入了 + if (frame->keyFrame() && !_latest_is_config_frame) { + insertConfigFrame(frame); + } + if (!frame->dropAble()) { + _latest_is_config_frame = false; + } + ret = VideoTrack::inputFrame(frame); + break; + } + } + if (_width == 0 && ready()) { + update(); + } + return ret; +} + +toolkit::Buffer::Ptr H265Track::getExtraData() const { + CHECK(ready()); +#ifdef ENABLE_MP4 + struct mpeg4_hevc_t hevc; + memset(&hevc, 0, sizeof(hevc)); + string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps; + h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int) vps_sps_pps.size(), NULL, 0, NULL, NULL); + + std::string extra_data; + extra_data.resize(1024); + auto extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, (uint8_t *)extra_data.data(), extra_data.size()); + if (extra_data_size == -1) { + WarnL << "生成H265 extra_data 失败"; + return nullptr; + } + return std::make_shared(std::move(extra_data)); +#else + WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265的支持不完善"; + return nullptr; +#endif +} + +void H265Track::setExtraData(const uint8_t *data, size_t bytes) { +#ifdef ENABLE_MP4 + struct mpeg4_hevc_t hevc; + memset(&hevc, 0, sizeof(hevc)); + if (mpeg4_hevc_decoder_configuration_record_load(data, bytes, &hevc) > 0) { + std::vector config(bytes * 2); + int size = mpeg4_hevc_to_nalu(&hevc, config.data(), bytes * 2); + if (size > 4) { + splitH264((char *)config.data(), size, 4, [&](const char *ptr, size_t len, size_t prefix) { + inputFrame_l(std::make_shared((char *)ptr, len, 0, 0, prefix)); + }); + update(); + } + } +#else + WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265的支持不完善"; +#endif +} + +bool H265Track::update() { + return getHEVCInfo(_vps, _sps, _width, _height, _fps); +} + +std::vector H265Track::getConfigFrames() const { + if (!ready()) { + return {}; + } + return { createConfigFrame(_vps, 0, getIndex()), + createConfigFrame(_sps, 0, getIndex()), + createConfigFrame(_pps, 0, getIndex()) }; +} + +Track::Ptr H265Track::clone() const { + return std::make_shared(*this); +} + +void H265Track::insertConfigFrame(const Frame::Ptr &frame) { + if (!_vps.empty()) { + VideoTrack::inputFrame(createConfigFrame(_vps, frame->dts(), frame->getIndex())); + } + if (!_sps.empty()) { + VideoTrack::inputFrame(createConfigFrame(_sps, frame->dts(), frame->getIndex())); + } + if (!_pps.empty()) { + VideoTrack::inputFrame(createConfigFrame(_pps, frame->dts(), frame->getIndex())); + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * h265类型sdp + * h265 type sdp + + * [AUTO-TRANSLATED:4418a7df] + */ +class H265Sdp : public Sdp { +public: + /** + * 构造函数 + * @param sps 265 sps,不带0x00000001头 + * @param pps 265 pps,不带0x00000001头 + * @param payload_type rtp payload type 默认96 + * @param bitrate 比特率 + * Constructor + * @param sps 265 sps, without 0x00000001 header + * @param pps 265 pps, without 0x00000001 header + * @param payload_type rtp payload type, default 96 + * @param bitrate Bitrate + + * [AUTO-TRANSLATED:93f4ec48] + */ + H265Sdp(const string &strVPS, const string &strSPS, const string &strPPS, int payload_type, int bitrate) : Sdp(90000, payload_type) { + // 视频通道 [AUTO-TRANSLATED:642ca881] + // Video channel + _printer << "m=video 0 RTP/AVP " << payload_type << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + _printer << "a=rtpmap:" << payload_type << " " << getCodecName(CodecH265) << "/" << 90000 << "\r\n"; + _printer << "a=fmtp:" << payload_type << " "; + _printer << "sprop-vps="; + _printer << encodeBase64(strVPS) << "; "; + _printer << "sprop-sps="; + _printer << encodeBase64(strSPS) << "; "; + _printer << "sprop-pps="; + _printer << encodeBase64(strPPS) << "\r\n"; + } + + string getSdp() const override { return _printer; } + +private: + _StrPrinter _printer; +}; + +Sdp::Ptr H265Track::getSdp(uint8_t payload_type) const { + if (!ready()) { + WarnL << getCodecName() << " Track未准备好"; + return nullptr; + } + return std::make_shared(_vps, _sps, _pps, payload_type, getBitRate() / 1024); +} + +namespace { + +CodecId getCodec() { + return CodecH265; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + // a=fmtp:96 sprop-sps=QgEBAWAAAAMAsAAAAwAAAwBdoAKAgC0WNrkky/AIAAADAAgAAAMBlQg=; sprop-pps=RAHA8vA8kAA= + auto map = Parser::parseArgs(track->_fmtp, ";", "="); + auto vps = decodeBase64(map["sprop-vps"]); + auto sps = decodeBase64(map["sprop-sps"]); + auto pps = decodeBase64(map["sprop-pps"]); + if (sps.empty() || pps.empty()) { + // 如果sdp里面没有sps/pps,那么可能在后续的rtp里面恢复出sps/pps [AUTO-TRANSLATED:9300510b] + // If there is no sps/pps in the sdp, then it may be possible to recover sps/pps from the subsequent rtp + return std::make_shared(); + } + return std::make_shared(vps, sps, pps, 0, 0, 0); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared((char *)data, bytes, dts, pts, prefixSize(data, bytes)); +} + +} // namespace + +CodecPlugin h265_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +}//namespace mediakit + diff --git a/MediaServer/ext-codec/H265.h b/MediaServer/ext-codec/H265.h new file mode 100644 index 0000000..0e86a0b --- /dev/null +++ b/MediaServer/ext-codec/H265.h @@ -0,0 +1,190 @@ +/* + * 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_H265_H +#define ZLMEDIAKIT_H265_H + +#include "H264.h" +#include "Extension/Track.h" +#include "Extension/Frame.h" + +#define H265_TYPE(v) (((uint8_t)(v) >> 1) & 0x3f) + +namespace mediakit { + +template +class H265FrameHelper : public Parent{ +public: + friend class FrameImp; + friend class toolkit::ResourcePool_l; + using Ptr = std::shared_ptr; + + enum { + NAL_TRAIL_N = 0, + NAL_TRAIL_R = 1, + NAL_TSA_N = 2, + NAL_TSA_R = 3, + NAL_STSA_N = 4, + NAL_STSA_R = 5, + NAL_RADL_N = 6, + NAL_RADL_R = 7, + NAL_RASL_N = 8, + NAL_RASL_R = 9, + NAL_BLA_W_LP = 16, + NAL_BLA_W_RADL = 17, + NAL_BLA_N_LP = 18, + NAL_IDR_W_RADL = 19, + NAL_IDR_N_LP = 20, + NAL_CRA_NUT = 21, + NAL_RSV_IRAP_VCL22 = 22, + NAL_RSV_IRAP_VCL23 = 23, + + NAL_VPS = 32, + NAL_SPS = 33, + NAL_PPS = 34, + NAL_AUD = 35, + NAL_EOS_NUT = 36, + NAL_EOB_NUT = 37, + NAL_FD_NUT = 38, + NAL_SEI_PREFIX = 39, + NAL_SEI_SUFFIX = 40, + }; + + template + H265FrameHelper(ARGS &&...args): Parent(std::forward(args)...) { + this->_codec_id = CodecH265; + } + + bool keyFrame() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + auto type = H265_TYPE(*nal_ptr); + // 参考自FFmpeg: IRAP VCL NAL unit types span the range [AUTO-TRANSLATED:45413c06] + // Referenced from FFmpeg: IRAP VCL NAL unit types span the range + // [BLA_W_LP (16), RSV_IRAP_VCL23 (23)]. + return (type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23) && decodeAble() ; + } + + bool configFrame() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + switch (H265_TYPE(*nal_ptr)) { + case NAL_VPS: + case NAL_SPS: + case NAL_PPS : return true; + default : return false; + } + } + + bool dropAble() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + switch (H265_TYPE(*nal_ptr)) { + case NAL_AUD: + case NAL_SEI_SUFFIX: + case NAL_SEI_PREFIX: return true; + default: return false; + } + } + + bool decodeAble() const override { + auto nal_ptr = (uint8_t *) this->data() + this->prefixSize(); + auto type = H265_TYPE(*nal_ptr); + // 多slice情况下, first_slice_segment_in_pic_flag 表示其为一帧的开始 [AUTO-TRANSLATED:0427551b] + // In the case of multiple slices, first_slice_segment_in_pic_flag indicates the beginning of a frame + return type >= NAL_TRAIL_N && type <= NAL_RSV_IRAP_VCL23 && (nal_ptr[2] & 0x80); + } +}; + +/** + * 265帧类 + * 265 frame class + + * [AUTO-TRANSLATED:9141a4be] + */ +using H265Frame = H265FrameHelper; + +/** + * 防止内存拷贝的H265类 + * 用户可以通过该类型快速把一个指针无拷贝的包装成Frame类 + * H265 class to prevent memory copying + * Users can quickly wrap a pointer into a Frame class without copying through this type + + * [AUTO-TRANSLATED:44bde991] + */ +using H265FrameNoCacheAble = H265FrameHelper; + +/** +* 265视频通道 + * 265 video channel + + * [AUTO-TRANSLATED:27c65a36] +*/ +class H265Track : public VideoTrack { +public: + using Ptr = std::shared_ptr; + + /** + * 不指定sps pps构造h265类型的媒体 + * 在随后的inputFrame中获取sps pps + * Construct a h265 media without specifying sps pps + * Get sps pps in the subsequent inputFrame + + * [AUTO-TRANSLATED:bf86e048] + */ + H265Track() = default; + + /** + * 构造h265类型的媒体 + * @param vps vps帧数据 + * @param sps sps帧数据 + * @param pps pps帧数据 + * @param vps_prefix_len 265头长度,可以为3个或4个字节,一般为0x00 00 00 01 + * @param sps_prefix_len 265头长度,可以为3个或4个字节,一般为0x00 00 00 01 + * @param pps_prefix_len 265头长度,可以为3个或4个字节,一般为0x00 00 00 01 + * Construct a h265 media + * @param vps vps frame data + * @param sps sps frame data + * @param pps pps frame data + * @param vps_prefix_len 265 header length, can be 3 or 4 bytes, generally 0x00 00 00 01 + * @param sps_prefix_len 265 header length, can be 3 or 4 bytes, generally 0x00 00 00 01 + * @param pps_prefix_len 265 header length, can be 3 or 4 bytes, generally 0x00 00 00 01 + + + * [AUTO-TRANSLATED:a8c42d9f] + */ + H265Track(const std::string &vps,const std::string &sps, const std::string &pps,int vps_prefix_len = 4, int sps_prefix_len = 4, int pps_prefix_len = 4); + + bool ready() const override; + CodecId getCodecId() const override; + int getVideoWidth() const override; + int getVideoHeight() const override; + float getVideoFps() const override; + bool inputFrame(const Frame::Ptr &frame) override; + toolkit::Buffer::Ptr getExtraData() const override; + void setExtraData(const uint8_t *data, size_t size) override; + bool update() override; + std::vector getConfigFrames() const override; + +private: + Sdp::Ptr getSdp(uint8_t payload_type) const override; + Track::Ptr clone() const override; + bool inputFrame_l(const Frame::Ptr &frame); + void insertConfigFrame(const Frame::Ptr &frame); + +private: + bool _latest_is_config_frame = false; + int _width = 0; + int _height = 0; + float _fps = 0; + std::string _vps; + std::string _sps; + std::string _pps; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_H265_H \ No newline at end of file diff --git a/MediaServer/ext-codec/H265Rtmp.cpp b/MediaServer/ext-codec/H265Rtmp.cpp new file mode 100644 index 0000000..47eea37 --- /dev/null +++ b/MediaServer/ext-codec/H265Rtmp.cpp @@ -0,0 +1,178 @@ +/* + * 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 "H265Rtmp.h" +#include "Rtmp/utils.h" +#include "Common/config.h" +#ifdef ENABLE_MP4 +#include "mpeg4-hevc.h" +#endif // ENABLE_MP4 + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { + if (_info.codec == CodecInvalid) { + // 先判断是否为增强型rtmp [AUTO-TRANSLATED:86c4f86a] + // First, determine if it is an enhanced rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + } + + if (_info.is_enhanced) { + // 增强型rtmp [AUTO-TRANSLATED:d7d72114] + // Enhanced rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + if (!_info.is_enhanced || _info.codec != CodecH265) { + throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!"); + } + + switch (_info.video.pkt_type) { + case RtmpPacketType::PacketTypeSequenceStart: { + getTrack()->setExtraData((uint8_t *)pkt->data() + RtmpPacketInfo::kEnhancedRtmpHeaderSize, pkt->size() - RtmpPacketInfo::kEnhancedRtmpHeaderSize); + break; + } + + case RtmpPacketType::PacketTypeCodedFramesX: + case RtmpPacketType::PacketTypeCodedFrames: { + auto data = (uint8_t *)pkt->data() + RtmpPacketInfo::kEnhancedRtmpHeaderSize; + auto size = pkt->size() - RtmpPacketInfo::kEnhancedRtmpHeaderSize; + auto pts = pkt->time_stamp; + CHECK_RET(size > 3); + if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) { + // SI24 = [CompositionTime Offset] + int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000; + pts += cts; + data += 3; + size -= 3; + } + CHECK_RET(size > 4); + splitFrame(data, size, pkt->time_stamp, pts); + break; + } + default: WarnL << "Unknown pkt_type: " << (int)_info.video.pkt_type; break; + } + return; + } + + // 国内扩展(12) H265 rtmp [AUTO-TRANSLATED:ba272139] + // Domestic extension (12) H265 rtmp + if (pkt->isConfigFrame()) { + CHECK_RET(pkt->size() > 5); + getTrack()->setExtraData((uint8_t *)pkt->data() + 5, pkt->size() - 5); + return; + } + + CHECK_RET(pkt->size() > 9); + uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2); + int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000; + auto pts = pkt->time_stamp + cts; + splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts); +} + +void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) { + auto end = data + size; + while (data + 4 < end) { + uint32_t frame_len = load_be32(data); + data += 4; + if (data + frame_len > end) { + break; + } + outputFrame((const char *)data, frame_len, dts, pts); + data += frame_len; + } +} + +inline void H265RtmpDecoder::outputFrame(const char *data, size_t size, uint32_t dts, uint32_t pts) { + auto frame = FrameImp::create(); + frame->_prefix_size = 4; + frame->_dts = dts; + frame->_pts = pts; + frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头 + frame->_buffer.append(data, size); + RtmpCodec::inputFrame(frame); +} + +//////////////////////////////////////////////////////////////////////// + +void H265RtmpEncoder::flush() { + inputFrame(nullptr); +} + +bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { + if (!_rtmp_packet) { + _rtmp_packet = RtmpPacket::create(); + GET_CONFIG(bool, enhanced, Rtmp::kEnhanced); + _rtmp_packet->buffer.resize((enhanced ? RtmpPacketInfo::kEnhancedRtmpHeaderSize : 2) + 3); + } + + return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { + GET_CONFIG(bool, enhanced, Rtmp::kEnhanced); + if (enhanced) { + auto header = (RtmpVideoHeaderEnhanced *)_rtmp_packet->data(); + header->enhanced = 1; + header->pkt_type = (int)RtmpPacketType::PacketTypeCodedFrames; + header->frame_type = have_key_frame ? (int)RtmpFrameType::key_frame : (int)RtmpFrameType::inter_frame; + header->fourcc = htonl((uint32_t)RtmpVideoCodec::fourcc_hevc); + } else { + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + } + + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[enhanced ? 5 : 2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet [AUTO-TRANSLATED:d72e89a7] + // Output rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; + }, &_rtmp_packet->buffer); +} + +void H265RtmpEncoder::makeConfigPacket() { + auto pkt = RtmpPacket::create(); + GET_CONFIG(bool, enhanced, Rtmp::kEnhanced); + if (enhanced) { + pkt->buffer.resize(RtmpPacketInfo::kEnhancedRtmpHeaderSize); + auto header = (RtmpVideoHeaderEnhanced *)pkt->data(); + header->enhanced = 1; + header->pkt_type = (int)RtmpPacketType::PacketTypeSequenceStart; + header->frame_type = (int)RtmpFrameType::key_frame; + header->fourcc = htonl((uint32_t)RtmpVideoCodec::fourcc_hevc); + } else { + auto flags = (uint8_t)RtmpVideoCodec::h265; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); + } + + // HEVCDecoderConfigurationRecord + auto extra_data = getTrack()->getExtraData(); + CHECK(extra_data); + pkt->buffer.append(extra_data->data(), extra_data->size()); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); +} + +} // namespace mediakit diff --git a/MediaServer/ext-codec/H265Rtmp.h b/MediaServer/ext-codec/H265Rtmp.h new file mode 100644 index 0000000..a87bc72 --- /dev/null +++ b/MediaServer/ext-codec/H265Rtmp.h @@ -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_H265RTMPCODEC_H +#define ZLMEDIAKIT_H265RTMPCODEC_H + +#include "H265.h" +#include "Rtmp/RtmpCodec.h" +#include "Extension/Track.h" + +namespace mediakit { +/** + * h265 Rtmp解码类 + * 将 h265 over rtmp 解复用出 h265-Frame + * h265 Rtmp decoder class + * Demultiplex h265-Frame from h265 over rtmp + + * [AUTO-TRANSLATED:2768e4bd] + */ +class H265RtmpDecoder : public RtmpCodec { +public: + using Ptr = std::shared_ptr; + + H265RtmpDecoder(const Track::Ptr &track) : RtmpCodec(track) {} + + /** + * 输入265 Rtmp包 + * @param rtmp Rtmp包 + * Input 265 Rtmp packet + * @param rtmp Rtmp packet + + * [AUTO-TRANSLATED:63dbe33f] + */ + void inputRtmp(const RtmpPacket::Ptr &rtmp) override; + +protected: + void outputFrame(const char *data, size_t size, uint32_t dts, uint32_t pts); + void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts); + +protected: + RtmpPacketInfo _info; +}; + +/** + * 265 Rtmp打包类 + * 265 Rtmp packaging class + + * [AUTO-TRANSLATED:5891c800] + */ +class H265RtmpEncoder : public RtmpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数,track可以为空,此时则在inputFrame时输入sps pps + * 如果track不为空且包含sps pps信息, + * 那么inputFrame时可以不输入sps pps + * @param track + * Constructor, track can be empty, in which case sps pps is input when inputFrame + * If track is not empty and contains sps pps information, + * Then sps pps can be omitted when inputFrame + * @param track + + * [AUTO-TRANSLATED:e61fdfed] + */ + H265RtmpEncoder(const Track::Ptr &track) : RtmpCodec(track) {} + + /** + * 输入265帧,可以不带sps pps + * @param frame 帧数据 + * Input 265 frame, sps pps can be omitted + * @param frame Frame data + + * [AUTO-TRANSLATED:7d1be1e7] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame cache output + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + + /** + * 生成config包 + * Generate config packet + + + * [AUTO-TRANSLATED:8f851364] + */ + void makeConfigPacket() override; + +private: + RtmpPacket::Ptr _rtmp_packet; + FrameMerger _merger { FrameMerger::mp4_nal_size }; +}; + +} // namespace mediakit + +#endif // ZLMEDIAKIT_H265RTMPCODEC_H diff --git a/MediaServer/ext-codec/H265Rtp.cpp b/MediaServer/ext-codec/H265Rtp.cpp new file mode 100644 index 0000000..8b3a350 --- /dev/null +++ b/MediaServer/ext-codec/H265Rtp.cpp @@ -0,0 +1,393 @@ +/* + * 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 "H265Rtp.h" +#include "Common/config.h" +namespace mediakit{ + +//https://datatracker.ietf.org/doc/rfc7798/ +// H265 nalu 头两个字节的定义 [AUTO-TRANSLATED:d896dd59] +// H265 nalu header definition of the first two bytes +/* + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F| Type | LayerId | TID | + +-------------+-----------------+ + Forbidden zero(F) : 1 bit + NAL unit type(Type) : 6 bits + NUH layer ID(LayerId) : 6 bits + NUH temporal ID plus 1 (TID) : 3 bits +*/ + +H265RtpDecoder::H265RtpDecoder() { + _frame = obtainFrame(); +} + +H265Frame::Ptr H265RtpDecoder::obtainFrame() { + auto frame = FrameImp::create(); + frame->_prefix_size = 4; + return frame; +} + +#define AV_RB16(x) \ + ((((const uint8_t*)(x))[0] << 8) | \ + ((const uint8_t*)(x))[1]) + +#define CHECK_SIZE(total, size, ret) \ + if (total < size) { \ + WarnL << "invalid rtp data size:" << total << " < " << size << ",rtp:\r\n" << rtp->dumpString(); _gop_dropped = true; return ret; \ + } + +// 4.4.2. Aggregation Packets (APs) (p25) +/* + 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 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=48) | NALU 1 DONL | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| NALU 1 Data . . . | +| | ++ . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 DOND | NALU 2 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 HDR | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 2 Data | +| | +| . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +bool H265RtpDecoder::unpackAp(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp){ + bool have_key_frame = false; + // 忽略PayloadHdr [AUTO-TRANSLATED:9868ddb5] + // Ignore PayloadHdr + CHECK_SIZE(size, 2, have_key_frame); + ptr += 2; + size -= 2; + + while (size) { + if (_using_donl_field) { + CHECK_SIZE(size, 2, have_key_frame); + uint16_t donl = AV_RB16(ptr); + size -= 2; + ptr += 2; + } + CHECK_SIZE(size, 2, have_key_frame); + uint16_t nalu_size = AV_RB16(ptr); + size -= 2; + ptr += 2; + CHECK_SIZE(size, nalu_size, have_key_frame) + if (singleFrame(rtp, ptr, nalu_size, stamp)) { + have_key_frame = true; + } + size -= nalu_size; + ptr += nalu_size; + } + return have_key_frame; +} + +// 4.4.3. Fragmentation Units (p29) +/* + 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 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=49) | FU header | DONL (cond) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| DONL (cond) | | +|-+-+-+-+-+-+-+-+ | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E| FuType | ++---------------+ +*/ + +bool H265RtpDecoder::mergeFu(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp, uint16_t seq){ + CHECK_SIZE(size, 4, false); + auto s_bit = ptr[2] >> 7; + auto e_bit = (ptr[2] >> 6) & 0x01; + auto type = ptr[2] & 0x3f; + if (s_bit) { + // 该帧的第一个rtp包 [AUTO-TRANSLATED:a9581a23] + // The first rtp packet of this frame + _frame->_buffer.assign("\x00\x00\x00\x01", 4); + _frame->_buffer.push_back((type << 1) | (ptr[0] & 0x81)); + _frame->_buffer.push_back(ptr[1]); + _frame->_pts = stamp; + _fu_dropped = false; + } + + if (_fu_dropped) { + // 该帧不完整 [AUTO-TRANSLATED:6bd7eca7] + // This frame is incomplete + return false; + } + + if (!s_bit && seq != (uint16_t) (_last_seq + 1)) { + // 中间的或末尾的rtp包,其seq必须连续,否则说明rtp丢包,那么该帧不完整,必须得丢弃 [AUTO-TRANSLATED:6953b332] + // The middle or end rtp packet, its seq must be continuous, otherwise it means rtp packet loss, then this frame is incomplete and must be discarded + _fu_dropped = true; + _frame->_buffer.clear(); + return false; + } + + // 跳过PayloadHdr + FU header [AUTO-TRANSLATED:51ec6760] + // Skip PayloadHdr + FU header + ptr += 3; + size -= 3; + if (_using_donl_field) { + // DONL确保不少于2个字节 [AUTO-TRANSLATED:7e72ecc1] + // DONL must be no less than 2 bytes + CHECK_SIZE(size, 2, false); + uint16_t donl = AV_RB16(ptr); + size -= 2; + ptr += 2; + } + + CHECK_SIZE(size, 1, false); + + // 后面追加数据 [AUTO-TRANSLATED:248516e9] + // Append data later + _frame->_buffer.append((char *) ptr, size); + + if (!e_bit) { + // 非末尾包 [AUTO-TRANSLATED:2e43ac3c] + // Non-end packet + return s_bit ? (_frame->keyFrame() || _frame->configFrame()) : false; + } + + // 确保下一次fu必须收到第一个包 [AUTO-TRANSLATED:491d81ec] + // Ensure that the next fu must receive the first packet + _fu_dropped = true; + // 该帧最后一个rtp包 [AUTO-TRANSLATED:ea395f0e] + // The last rtp packet of this frame + outputFrame(rtp, _frame); + return false; +} + +bool H265RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool) { + auto seq = rtp->getSeq(); + auto last_is_gop = _is_gop; + _is_gop = decodeRtp(rtp); + if (!_gop_dropped && seq != (uint16_t) (_last_seq + 1) && _last_seq) { + _gop_dropped = true; + WarnL << "start drop h265 gop, last seq:" << _last_seq << ", rtp:\r\n" << rtp->dumpString(); + } + _last_seq = seq; + // 确保有sps rtp的时候,gop从sps开始;否则从关键帧开始 [AUTO-TRANSLATED:115ae07c] + // Ensure that when there is sps rtp, gop starts from sps; otherwise, it starts from the key frame + return _is_gop && !last_is_gop; +} + +bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtp) { + auto payload_size = rtp->getPayloadSize(); + if (payload_size <= 0) { + // 无实际负载 [AUTO-TRANSLATED:305af48f] + // No actual payload + return false; + } + auto frame = rtp->getPayload(); + auto stamp = rtp->getStampMS(); + auto seq = rtp->getSeq(); + int nal = H265_TYPE(frame[0]); + + switch (nal) { + case 48: + // aggregated packet (AP) - with two or more NAL units + return unpackAp(rtp, frame, payload_size, stamp); + + case 49: + // fragmentation unit (FU) + return mergeFu(rtp, frame, payload_size, stamp, seq); + + default: { + if (nal < 48) { + // Single NAL Unit Packets (p24) + return singleFrame(rtp, frame, payload_size, stamp); + } + _gop_dropped = true; + WarnL << "不支持该类型的265 RTP包, nal type" << nal << ", rtp:\r\n" << rtp->dumpString(); + return false; + } + } +} + +bool H265RtpDecoder::singleFrame(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp){ + _frame->_buffer.assign("\x00\x00\x00\x01", 4); + _frame->_buffer.append((char *) ptr, size); + _frame->_pts = stamp; + auto key = _frame->keyFrame() || _frame->configFrame(); + outputFrame(rtp, _frame); + return key; +} + +void H265RtpDecoder::outputFrame(const RtpPacket::Ptr &rtp, const H265Frame::Ptr &frame) { + if (frame->dropAble()) { + // 不参与dts生成 [AUTO-TRANSLATED:dff3b747] + // Not involved in dts generation + frame->_dts = frame->_pts; + } else { + // rtsp没有dts,那么根据pts排序算法生成dts [AUTO-TRANSLATED:f37c17f3] + // rtsp does not have dts, so dts is generated according to the pts sorting algorithm + _dts_generator.getDts(frame->_pts, frame->_dts); + } + + if (frame->keyFrame() && _gop_dropped) { + _gop_dropped = false; + InfoL << "new gop received, rtp:\r\n" << rtp->dumpString(); + } + if (!_gop_dropped || frame->configFrame()) { + RtpCodec::inputFrame(frame); + } + _frame = obtainFrame(); +} + +//////////////////////////////////////////////////////////////////////// + +void H265RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ + auto max_size = getRtpInfo().getMaxSize() - 3; + auto nal_type = H265_TYPE(ptr[0]); //获取NALU的5bit 帧类型 + unsigned char s_e_flags; + bool fu_start = true; + bool mark_bit = false; + size_t offset = 2; + while (!mark_bit) { + if (len <= offset + max_size) { + // FU end + mark_bit = true; + max_size = len - offset; + s_e_flags = (1 << 6) | nal_type; + } else if (fu_start) { + // FU start + s_e_flags = (1 << 7) | nal_type; + } else { + // FU mid + s_e_flags = nal_type; + } + + { + // 传入nullptr先不做payload的内存拷贝 [AUTO-TRANSLATED:7ed49f0a] + // Pass in nullptr first, do not copy the payload memory + auto rtp = getRtpInfo().makeRtp(TrackVideo, nullptr, max_size + 3, mark_bit, pts); + // rtp payload 负载部分 [AUTO-TRANSLATED:03a5ef9b] + // rtp payload load part + uint8_t *payload = rtp->getPayload(); + // FU 第1个字节,表明为FU [AUTO-TRANSLATED:9cf07fda] + // FU first byte, indicating FU + payload[0] = 49 << 1; + // FU 第2个字节貌似固定为1 [AUTO-TRANSLATED:77983091] + // FU second byte seems to be fixed to 1 + payload[1] = ptr[1]; // 1; + // FU 第3个字节 [AUTO-TRANSLATED:c627abd0] + // FU third byte + payload[2] = s_e_flags; + // H265 数据 [AUTO-TRANSLATED:a2c3135f] + // H265 data + memcpy(payload + 3, ptr + offset, max_size); + // 输入到rtp环形缓存 [AUTO-TRANSLATED:6bafd42b] + // Input to rtp ring buffer + RtpCodec::inputRtp(rtp, fu_start && gop_pos); + } + + offset += max_size; + fu_start = false; + } +} + +void H265RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ + if (len <= getRtpInfo().getMaxSize()) { + //signal-nalu + RtpCodec::inputRtp(getRtpInfo().makeRtp(TrackVideo, ptr, len, is_mark, pts), gop_pos); + } else { + // FU-A模式 [AUTO-TRANSLATED:a273a49c] + // FU-A mode + packRtpFu(ptr, len, pts, is_mark, gop_pos); + } +} +void H265RtpEncoder::insertConfigFrame(uint64_t pts){ + if (!_sps || !_pps || !_vps) { + WarnL<<" not ok"; + return; + } + // gop缓存从vps 开始,vps ,sps、pps后面还有时间戳相同的关键帧,所以mark bit为false [AUTO-TRANSLATED:2534b06f] + // gop cache starts from vps, vps, sps, pps followed by key frames with the same timestamp, so mark bit is false + packRtp(_vps->data() + _vps->prefixSize(), _vps->size() - _vps->prefixSize(), pts, false, true); + packRtp(_sps->data() + _sps->prefixSize(), _sps->size() - _sps->prefixSize(), pts, false, false); + packRtp(_pps->data() + _pps->prefixSize(), _pps->size() - _pps->prefixSize(), pts, false, false); + +} +bool H265RtpEncoder::inputFrame_l(const Frame::Ptr &frame, bool is_mark){ + if (frame->keyFrame()) { + // 保证每一个关键帧前都有SPS PPS VPS [AUTO-TRANSLATED:9189f8d7] + // Ensure that there are SPS PPS VPS before each key frame + insertConfigFrame(frame->pts()); + } + packRtp(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), frame->pts(), is_mark, false); + return true; +} +bool H265RtpEncoder::inputFrame(const Frame::Ptr &frame) { + auto ptr = (uint8_t *) frame->data() + frame->prefixSize(); + auto nal_type = H265_TYPE(ptr[0]); //获取NALU的5bit 帧类型 + + switch (nal_type) { + case H265Frame::NAL_SPS: { + _sps = Frame::getCacheAbleFrame(frame); + return true; + } + case H265Frame::NAL_PPS: { + _pps = Frame::getCacheAbleFrame(frame); + return true; + } + case H265Frame::NAL_VPS:{ + _vps = Frame::getCacheAbleFrame(frame); + return true; + } + default: break; + } + + GET_CONFIG(int,lowLatency,Rtp::kLowLatency); + if (lowLatency) { // 低延迟模式 + if (_last_frame) { + flush(); + } + inputFrame_l(frame, true); + } else { + if (_last_frame) { + // 如果时间戳发生了变化,那么markbit才置true [AUTO-TRANSLATED:19b68429] + // If the timestamp changes, then markbit is set to true + inputFrame_l(_last_frame, _last_frame->pts() != frame->pts()); + } + _last_frame = Frame::getCacheAbleFrame(frame); + } + return true; +} + +void H265RtpEncoder::flush() { + if (_last_frame) { + // 如果时间戳发生了变化,那么markbit才置true [AUTO-TRANSLATED:6b1d0fe0] + // If the timestamp changes, then markbit is set to true + inputFrame_l(_last_frame, true); + _last_frame = nullptr; + } +} + +}//namespace mediakit diff --git a/MediaServer/ext-codec/H265Rtp.h b/MediaServer/ext-codec/H265Rtp.h new file mode 100644 index 0000000..b7b5745 --- /dev/null +++ b/MediaServer/ext-codec/H265Rtp.h @@ -0,0 +1,111 @@ +/* + * 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_H265RTPCODEC_H +#define ZLMEDIAKIT_H265RTPCODEC_H + +#include "H265.h" +#include "Rtsp/RtpCodec.h" +// for DtsGenerator +#include "Common/Stamp.h" + +namespace mediakit { + +/** + * h265 rtp解码类 + * 将 h265 over rtsp-rtp 解复用出 h265-Frame + * 《草案(H265-over-RTP)draft-ietf-payload-rtp-h265-07.pdf》 + * h265 rtp decoder class + * Demultiplex h265-Frame from h265 over rtsp-rtp + * 《Draft (H265-over-RTP) draft-ietf-payload-rtp-h265-07.pdf》 + + * [AUTO-TRANSLATED:24e7e278] + */ +class H265RtpDecoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + H265RtpDecoder(); + + /** + * 输入265 rtp包 + * @param rtp rtp包 + * @param key_pos 此参数忽略之 + * Input 265 rtp packet + * @param rtp rtp packet + * @param key_pos This parameter is ignored + + * [AUTO-TRANSLATED:35e8fa1d] + */ + bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = true) override; + +private: + bool unpackAp(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp); + bool mergeFu(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp, uint16_t seq); + bool singleFrame(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp); + + bool decodeRtp(const RtpPacket::Ptr &rtp); + H265Frame::Ptr obtainFrame(); + void outputFrame(const RtpPacket::Ptr &rtp, const H265Frame::Ptr &frame); + +private: + bool _is_gop = false; + bool _using_donl_field = false; + bool _gop_dropped = false; + bool _fu_dropped = true; + uint16_t _last_seq = 0; + H265Frame::Ptr _frame; + DtsGenerator _dts_generator; +}; + +/** + * 265 rtp打包类 + * 265 rtp packer class + + * [AUTO-TRANSLATED:4b3f96fe] + */ +class H265RtpEncoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + /** + * 输入265帧 + * @param frame 帧数据,必须 + * Input 265 frame + * @param frame Frame data, required + + * [AUTO-TRANSLATED:48454707] + */ + bool inputFrame(const Frame::Ptr &frame) override; + + /** + * 刷新输出所有frame缓存 + * Flush all frame cache in output + + + * [AUTO-TRANSLATED:adaea568] + */ + void flush() override; + +private: + void packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void insertConfigFrame(uint64_t pts); + bool inputFrame_l(const Frame::Ptr &frame, bool is_mark); +private: + Frame::Ptr _sps; + Frame::Ptr _pps; + Frame::Ptr _vps; + Frame::Ptr _last_frame; +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_H265RTPCODEC_H diff --git a/MediaServer/ext-codec/JPEG.cpp b/MediaServer/ext-codec/JPEG.cpp new file mode 100644 index 0000000..df9964f --- /dev/null +++ b/MediaServer/ext-codec/JPEG.cpp @@ -0,0 +1,101 @@ +#include "JPEG.h" +#include "JPEGRtp.h" +#include "Rtsp/Rtsp.h" +#include "Util/util.h" +#include "Extension/Factory.h" + +using namespace toolkit; + +namespace mediakit { + +bool JPEGTrack::inputFrame(const Frame::Ptr &frame) { + if (!ready()) { + if (_height > 0 && _width > 0) { + if (_tmp == 0) _tmp = frame->dts(); + else _fps = 1000.0 / (frame->dts() - _tmp); + } else getVideoResolution((uint8_t*)frame->data(), frame->size()); + return false; + } + return VideoTrack::inputFrame(frame); +} + +void JPEGTrack::getVideoResolution(const uint8_t *buf, int len) { + for (int i = 0; i < len - 8; i++) { + if (buf[i] != 0xff) + continue; + if (buf[i + 1] == 0xC0 /*SOF0*/) { + _height = buf[i + 5] * 256 + buf[i + 6]; + _width = buf[i + 7] * 256 + buf[i + 8]; + return; + } + } +} + +class JPEGSdp : public Sdp { +public: + JPEGSdp(int bitrate) : Sdp(90000, Rtsp::PT_JPEG) { + _printer << "m=video 0 RTP/AVP " << (int)getPayloadType() << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + } + + std::string getSdp() const { return _printer; } + +private: + _StrPrinter _printer; +}; + +Sdp::Ptr JPEGTrack::getSdp(uint8_t) const { + return std::make_shared(getBitRate() / 1024); +} + + +namespace { + +CodecId getCodec() { + return CodecJPEG; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + WarnL << "Unsupported jpeg rtmp encoder"; + return nullptr; +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + WarnL << "Unsupported jpeg rtmp decoder"; + return nullptr; +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared>(0, CodecJPEG, (char *)data, bytes, dts, pts); +} + +} // namespace + +CodecPlugin jpeg_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +} // namespace mediakit diff --git a/MediaServer/ext-codec/JPEG.h b/MediaServer/ext-codec/JPEG.h new file mode 100644 index 0000000..adc5c47 --- /dev/null +++ b/MediaServer/ext-codec/JPEG.h @@ -0,0 +1,69 @@ +#ifndef ZLMEDIAKIT_JPEG_H +#define ZLMEDIAKIT_JPEG_H + +#include "Extension/Frame.h" +#include "Extension/Track.h" + +namespace mediakit { + +class JPEGTrack : public VideoTrack { +public: + using Ptr = std::shared_ptr; + + CodecId getCodecId() const override { return CodecJPEG; } + int getVideoHeight() const override { return _height; } + int getVideoWidth() const override { return _width; } + float getVideoFps() const override { return _fps; } + bool ready() const override { return _fps > 0; } + bool inputFrame(const Frame::Ptr &frame) override; + +private: + Sdp::Ptr getSdp(uint8_t payload_type) const override; + Track::Ptr clone() const override { return std::make_shared(*this); } + void getVideoResolution(const uint8_t *buf, int len); + +private: + int _width = 0; + int _height = 0; + float _fps = 0; + uint64_t _tmp = 0; +}; + +class JPEGFrameType { +public: + virtual ~JPEGFrameType() = default; + virtual uint8_t pixType() const = 0; +}; + +template +class JPEGFrame : public Parent, public JPEGFrameType { +public: + static constexpr auto kJFIFSize = 20u; + /** + * JPEG/MJPEG帧 + * @param pix_type pixel format type; AV_PIX_FMT_YUVJ422P || (AVCOL_RANGE_JPEG && AV_PIX_FMT_YUV422P) : 1; AV_PIX_FMT_YUVJ420P || (AVCOL_RANGE_JPEG && AV_PIX_FMT_YUV420P) : 0 + * JPEG/MJPEG frame + * @param pix_type pixel format type; AV_PIX_FMT_YUVJ422P || (AVCOL_RANGE_JPEG && AV_PIX_FMT_YUV422P) : 1; AV_PIX_FMT_YUVJ420P || (AVCOL_RANGE_JPEG && AV_PIX_FMT_YUV420P) : 0 + + * [AUTO-TRANSLATED:d746e541] + */ + template + JPEGFrame(uint8_t pix_type, ARGS &&...args) : Parent(std::forward(args)...) { + _pix_type = pix_type; + // JFIF头固定20个字节长度 [AUTO-TRANSLATED:bd63b447] + // JFIF header is fixed at 20 bytes in length + CHECK(this->size() > kJFIFSize); + } + size_t prefixSize() const override { return 0; } + bool keyFrame() const override { return true; } + bool configFrame() const override { return false; } + CodecId getCodecId() const override { return CodecJPEG; } + uint8_t pixType() const override { return _pix_type; } + +private: + uint8_t _pix_type; +}; + +}//namespace mediakit + +#endif //ZLMEDIAKIT_JPEG_H diff --git a/MediaServer/ext-codec/JPEGRtp.cpp b/MediaServer/ext-codec/JPEGRtp.cpp new file mode 100644 index 0000000..648d2d0 --- /dev/null +++ b/MediaServer/ext-codec/JPEGRtp.cpp @@ -0,0 +1,847 @@ +#include "JPEGRtp.h" +#include "JPEG.h" + +using namespace std; +using namespace mediakit; + +#define AV_WB24(p, d) \ + do { \ + ((uint8_t *)(p))[2] = (d); \ + ((uint8_t *)(p))[1] = (d) >> 8; \ + ((uint8_t *)(p))[0] = (d) >> 16; \ + } while (0) + +#define AV_WB16(p, d) \ + do { \ + ((uint8_t *)(p))[1] = (d); \ + ((uint8_t *)(p))[0] = (d) >> 8; \ + } while (0) + +#define AV_WB8(p, d) do { ((uint8_t*)(p))[0] = (d); } while(0) + +/* JPEG marker codes */ +enum JpegMarker { + /* start of frame */ + SOF0 = 0xc0, /* baseline */ + SOF1 = 0xc1, /* extended sequential, huffman */ + SOF2 = 0xc2, /* progressive, huffman */ + SOF3 = 0xc3, /* lossless, huffman */ + + SOF5 = 0xc5, /* differential sequential, huffman */ + SOF6 = 0xc6, /* differential progressive, huffman */ + SOF7 = 0xc7, /* differential lossless, huffman */ + JPG = 0xc8, /* reserved for JPEG extension */ + SOF9 = 0xc9, /* extended sequential, arithmetic */ + SOF10 = 0xca, /* progressive, arithmetic */ + SOF11 = 0xcb, /* lossless, arithmetic */ + + SOF13 = 0xcd, /* differential sequential, arithmetic */ + SOF14 = 0xce, /* differential progressive, arithmetic */ + SOF15 = 0xcf, /* differential lossless, arithmetic */ + + DHT = 0xc4, /* define huffman tables */ + + DAC = 0xcc, /* define arithmetic-coding conditioning */ + + /* restart with modulo 8 count "m" */ + RST0 = 0xd0, + RST1 = 0xd1, + RST2 = 0xd2, + RST3 = 0xd3, + RST4 = 0xd4, + RST5 = 0xd5, + RST6 = 0xd6, + RST7 = 0xd7, + + SOI = 0xd8, /* start of image */ + EOI = 0xd9, /* end of image */ + SOS = 0xda, /* start of scan */ + DQT = 0xdb, /* define quantization tables */ + DNL = 0xdc, /* define number of lines */ + DRI = 0xdd, /* define restart interval */ + DHP = 0xde, /* define hierarchical progression */ + EXP = 0xdf, /* expand reference components */ + + APP0 = 0xe0, + APP1 = 0xe1, + APP2 = 0xe2, + APP3 = 0xe3, + APP4 = 0xe4, + APP5 = 0xe5, + APP6 = 0xe6, + APP7 = 0xe7, + APP8 = 0xe8, + APP9 = 0xe9, + APP10 = 0xea, + APP11 = 0xeb, + APP12 = 0xec, + APP13 = 0xed, + APP14 = 0xee, + APP15 = 0xef, + + JPG0 = 0xf0, + JPG1 = 0xf1, + JPG2 = 0xf2, + JPG3 = 0xf3, + JPG4 = 0xf4, + JPG5 = 0xf5, + JPG6 = 0xf6, + SOF48 = 0xf7, ///< JPEG-LS + LSE = 0xf8, ///< JPEG-LS extension parameters + JPG9 = 0xf9, + JPG10 = 0xfa, + JPG11 = 0xfb, + JPG12 = 0xfc, + JPG13 = 0xfd, + + COM = 0xfe, /* comment */ + + TEM = 0x01, /* temporary private use for arithmetic coding */ + + /* 0x02 -> 0xbf reserved */ +}; + +typedef struct PutByteContext { + uint8_t *buffer, *buffer_end, *buffer_start; + int eof; +} PutByteContext; + +static void bytestream2_init_writer(PutByteContext *p, uint8_t *buf, int buf_size) { + assert(buf_size >= 0); + p->buffer = buf; + p->buffer_start = buf; + p->buffer_end = buf + buf_size; + p->eof = 0; +} + +static inline void bytestream2_put_byte(PutByteContext *p, uint8_t value) { + if (!p->eof && (p->buffer_end - p->buffer >= 1)) { + p->buffer[0] = value; + p->buffer += 1; + } else { + p->eof = 1; + } +} + +static inline void bytestream2_put_be16(PutByteContext *p, uint16_t value) { + if (!p->eof && (p->buffer_end - p->buffer >= 2)) { + p->buffer[0] = value >> 8; + p->buffer[1] = value & 0x00FF; + p->buffer += 2; + } else { + p->eof = 1; + } +} + +static inline void bytestream2_put_be24(PutByteContext *p, uint16_t value) { + if (!p->eof && (p->buffer_end - p->buffer >= 2)) { + p->buffer[0] = value >> 16; + p->buffer[1] = value >> 8; + p->buffer[2] = value & 0x00FF; + p->buffer += 2; + } else { + p->eof = 1; + } +} + +static unsigned int bytestream2_put_buffer(PutByteContext *p, const uint8_t *src, unsigned int size) { + int size2 = 0; + if (p->eof) { + return 0; + } + size2 = MIN(p->buffer_end - p->buffer, size); + if (size2 != (int)size) { + p->eof = 1; + } + memcpy(p->buffer, src, size2); + p->buffer += size2; + return size2; +} + +static inline int bytestream2_tell_p(PutByteContext *p) { + return (int) (p->buffer - p->buffer_start); +} + +static inline void avio_write(string &str, const void *ptr, size_t size) { + str.append((char *) ptr, size); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +static const uint8_t default_quantizers[128] = { + /* luma table */ + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99, + + /* chroma table */ + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; + + +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +const uint8_t avpriv_mjpeg_bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}; +const uint8_t avpriv_mjpeg_val_dc[12] = + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + +const uint8_t avpriv_mjpeg_bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}; + +const uint8_t avpriv_mjpeg_bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d}; +const uint8_t avpriv_mjpeg_val_ac_luminance[] = + {0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }; + +const uint8_t avpriv_mjpeg_bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77}; + +const uint8_t avpriv_mjpeg_val_ac_chrominance[] = + {0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }; + +static int jpeg_create_huffman_table(PutByteContext *p, int table_class, + int table_id, const uint8_t *bits_table, + const uint8_t *value_table) { + int i = 0, n = 0; + + bytestream2_put_byte(p, table_class << 4 | table_id); + + for (i = 1; i <= 16; i++) { + n += bits_table[i]; + bytestream2_put_byte(p, bits_table[i]); + } + + for (i = 0; i < n; i++) { + bytestream2_put_byte(p, value_table[i]); + } + return n + 17; +} + +static void jpeg_put_marker(PutByteContext *pbc, int code) { + bytestream2_put_byte(pbc, 0xff); + bytestream2_put_byte(pbc, code); +} + +static int jpeg_create_header(uint8_t *buf, int size, uint32_t type, uint32_t w, + uint32_t h, const uint8_t *qtable, int nb_qtable, + int dri) { + PutByteContext pbc; + uint8_t *dht_size_ptr; + int dht_size = 0, i = 0; + + bytestream2_init_writer(&pbc, buf, size); + + /* Convert from blocks to pixels. */ + w <<= 3; + h <<= 3; + + /* SOI */ + jpeg_put_marker(&pbc, SOI); + + /* JFIF header */ + jpeg_put_marker(&pbc, APP0); + bytestream2_put_be16(&pbc, 16); + bytestream2_put_buffer(&pbc, (const uint8_t *) "JFIF", 5); + bytestream2_put_be16(&pbc, 0x0201); + bytestream2_put_byte(&pbc, 0); + bytestream2_put_be16(&pbc, 1); + bytestream2_put_be16(&pbc, 1); + bytestream2_put_byte(&pbc, 0); + bytestream2_put_byte(&pbc, 0); + + if (dri) { + jpeg_put_marker(&pbc, DRI); + bytestream2_put_be16(&pbc, 4); + bytestream2_put_be16(&pbc, dri); + } + + /* DQT */ + jpeg_put_marker(&pbc, DQT); + bytestream2_put_be16(&pbc, 2 + nb_qtable * (1 + 64)); + + for (i = 0; i < nb_qtable; i++) { + bytestream2_put_byte(&pbc, i); + + /* Each table is an array of 64 values given in zig-zag + * order, identical to the format used in a JFIF DQT + * marker segment. */ + bytestream2_put_buffer(&pbc, qtable + 64 * i, 64); + } + + /* DHT */ + jpeg_put_marker(&pbc, DHT); + dht_size_ptr = pbc.buffer; + bytestream2_put_be16(&pbc, 0); + + dht_size = 2; + dht_size += jpeg_create_huffman_table(&pbc, 0, 0, avpriv_mjpeg_bits_dc_luminance, + avpriv_mjpeg_val_dc); + dht_size += jpeg_create_huffman_table(&pbc, 0, 1, avpriv_mjpeg_bits_dc_chrominance, + avpriv_mjpeg_val_dc); + dht_size += jpeg_create_huffman_table(&pbc, 1, 0, avpriv_mjpeg_bits_ac_luminance, + avpriv_mjpeg_val_ac_luminance); + dht_size += jpeg_create_huffman_table(&pbc, 1, 1, avpriv_mjpeg_bits_ac_chrominance, + avpriv_mjpeg_val_ac_chrominance); + AV_WB16(dht_size_ptr, dht_size); + + /* SOF0 */ + jpeg_put_marker(&pbc, SOF0); + bytestream2_put_be16(&pbc, 17); /* size */ + bytestream2_put_byte(&pbc, 8); /* bits per component */ + bytestream2_put_be16(&pbc, h); + bytestream2_put_be16(&pbc, w); + bytestream2_put_byte(&pbc, 3); /* number of components */ + bytestream2_put_byte(&pbc, 1); /* component number */ + bytestream2_put_byte(&pbc, (2 << 4) | (type ? 2 : 1)); /* hsample/vsample */ + bytestream2_put_byte(&pbc, 0); /* matrix number */ + bytestream2_put_byte(&pbc, 2); /* component number */ + bytestream2_put_byte(&pbc, 1 << 4 | 1); /* hsample/vsample */ + bytestream2_put_byte(&pbc, nb_qtable == 2 ? 1 : 0); /* matrix number */ + bytestream2_put_byte(&pbc, 3); /* component number */ + bytestream2_put_byte(&pbc, 1 << 4 | 1); /* hsample/vsample */ + bytestream2_put_byte(&pbc, nb_qtable == 2 ? 1 : 0); /* matrix number */ + + /* SOS */ + jpeg_put_marker(&pbc, SOS); + bytestream2_put_be16(&pbc, 12); + bytestream2_put_byte(&pbc, 3); + bytestream2_put_byte(&pbc, 1); + bytestream2_put_byte(&pbc, 0); + bytestream2_put_byte(&pbc, 2); + bytestream2_put_byte(&pbc, 17); + bytestream2_put_byte(&pbc, 3); + bytestream2_put_byte(&pbc, 17); + bytestream2_put_byte(&pbc, 0); + bytestream2_put_byte(&pbc, 63); + bytestream2_put_byte(&pbc, 0); + + /* Return the length in bytes of the JPEG header. */ + return bytestream2_tell_p(&pbc); +} + +static inline int av_clip(int a, int amin, int amax) { + if (a < amin) { return amin; } + else if (a > amax) { return amax; } + else { return a; } +} + +static void create_default_qtables(uint8_t *qtables, uint8_t q) { + int factor = q; + int i = 0; + uint16_t S; + + factor = av_clip(q, 1, 99); + + if (q < 50) { + S = 5000 / factor; + } else { + S = 200 - factor * 2; + } + + for (i = 0; i < 128; i++) { + int val = (default_quantizers[i] * S + 50) / 100; + + /* Limit the quantizers to 1 <= q <= 255. */ + val = av_clip(val, 1, 255); + qtables[i] = val; + } +} + +#define AVERROR_INVALIDDATA -1 +#define AVERROR_PATCHWELCOME -2 +#define AVERROR_EAGAIN -3 +#define RTP_FLAG_KEY 0x1 ///< RTP packet contains a keyframe +#define RTP_FLAG_MARKER 0x2 ///< RTP marker bit was set for this packet +#define av_log(ctx, level, ...) PrintD(__VA_ARGS__) + +#ifndef AV_RB24 +# define AV_RB24(x) \ + ((((const uint8_t*)(x))[0] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[2]) +#endif + +#define AV_RB8(x) (((const uint8_t*)(x))[0]) + +#ifndef AV_RB16 +# define AV_RB16(x) ((((const uint8_t*)(x))[0] << 8) | (((const uint8_t*)(x))[1] )) +#endif + +static int jpeg_parse_packet(void *ctx, PayloadContext *jpeg, uint32_t *timestamp, const uint8_t *buf, int len, + uint16_t seq, int flags, uint8_t *type) { + uint8_t q = 0, width = 0, height = 0; + const uint8_t *qtables = NULL; + uint16_t qtable_len = 0; + uint32_t off = 0; + int ret = 0, dri = 0; + + if (len < 8) { + av_log(ctx, AV_LOG_ERROR, "Too short RTP/JPEG packet.\n"); + return AVERROR_INVALIDDATA; + } + + /* Parse the main JPEG header. */ + off = AV_RB24(buf + 1); /* fragment byte offset */ + *type = AV_RB8(buf + 4); /* id of jpeg decoder params */ + q = AV_RB8(buf + 5); /* quantization factor (or table id) */ + width = AV_RB8(buf + 6); /* frame width in 8 pixel blocks */ + height = AV_RB8(buf + 7); /* frame height in 8 pixel blocks */ + buf += 8; + len -= 8; + + if (*type & 0x40) { + if (len < 4) { + av_log(ctx, AV_LOG_ERROR, "Too short RTP/JPEG packet.\n"); + return AVERROR_INVALIDDATA; + } + dri = AV_RB16(buf); + buf += 4; + len -= 4; + *type &= ~0x40; + } + if (*type > 1) { + av_log(ctx, AV_LOG_ERROR, "RTP/JPEG type %d", (int) *type); + return AVERROR_PATCHWELCOME; + } + + /* Parse the quantization table header. */ + if (off == 0) { + /* Start of JPEG data packet. */ + uint8_t new_qtables[128]; + uint8_t hdr[1024]; + + if (q > 127) { + uint8_t precision; + if (len < 4) { + av_log(ctx, AV_LOG_ERROR, "Too short RTP/JPEG packet.\n"); + return AVERROR_INVALIDDATA; + } + + /* The first byte is reserved for future use. */ + precision = AV_RB8(buf + 1); /* size of coefficients */ + qtable_len = AV_RB16(buf + 2); /* length in bytes */ + buf += 4; + len -= 4; + + if (precision) { + av_log(ctx, AV_LOG_WARNING, "Only 8-bit precision is supported.\n"); + } + + if (qtable_len > 0) { + if (len < qtable_len) { + av_log(ctx, AV_LOG_ERROR, "Too short RTP/JPEG packet.\n"); + return AVERROR_INVALIDDATA; + } + qtables = buf; + buf += qtable_len; + len -= qtable_len; + if (q < 255) { + if (jpeg->qtables_len[q - 128] && + (jpeg->qtables_len[q - 128] != qtable_len || + memcmp(qtables, &jpeg->qtables[q - 128][0], qtable_len))) { + av_log(ctx, AV_LOG_WARNING, + "Quantization tables for q=%d changed\n", q); + } else if (!jpeg->qtables_len[q - 128] && qtable_len <= 128) { + memcpy(&jpeg->qtables[q - 128][0], qtables, + qtable_len); + jpeg->qtables_len[q - 128] = qtable_len; + } + } + } else { + if (q == 255) { + av_log(ctx, AV_LOG_ERROR, + "Invalid RTP/JPEG packet. Quantization tables not found.\n"); + return AVERROR_INVALIDDATA; + } + if (!jpeg->qtables_len[q - 128]) { + av_log(ctx, AV_LOG_ERROR, + "No quantization tables known for q=%d yet.\n", q); + return AVERROR_INVALIDDATA; + } + qtables = &jpeg->qtables[q - 128][0]; + qtable_len = jpeg->qtables_len[q - 128]; + } + } else { /* q <= 127 */ + if (q == 0 || q > 99) { + av_log(ctx, AV_LOG_ERROR, "Reserved q value %d\n", q); + return AVERROR_INVALIDDATA; + } + create_default_qtables(new_qtables, q); + qtables = new_qtables; + qtable_len = sizeof(new_qtables); + } + + /* Skip the current frame in case of the end packet + * has been lost somewhere. */ + jpeg->frame.clear(); + jpeg->frame.reserve(1024 + len); + jpeg->timestamp = *timestamp; + + /* Generate a frame and scan headers that can be prepended to the + * RTP/JPEG data payload to produce a JPEG compressed image in + * interchange format. */ + jpeg->hdr_size = jpeg_create_header(hdr, sizeof(hdr), *type, width, + height, qtables, + qtable_len / 64, dri); + + /* Copy JPEG header to frame buffer. */ + avio_write(jpeg->frame, hdr, jpeg->hdr_size); + } + + if (jpeg->frame.empty()) { + av_log(ctx, AV_LOG_ERROR, + "Received packet without a start chunk; dropping frame.\n"); + return AVERROR_EAGAIN; + } + + if (jpeg->timestamp != *timestamp) { + /* Skip the current frame if timestamp is incorrect. + * A start packet has been lost somewhere. */ + jpeg->frame.clear(); + av_log(ctx, AV_LOG_ERROR, "RTP timestamps don't match.\n"); + return AVERROR_INVALIDDATA; + } + + if (off != jpeg->frame.size() - jpeg->hdr_size) { + av_log(ctx, AV_LOG_ERROR, + "Missing packets; dropping frame.\n"); + return AVERROR_EAGAIN; + } + + /* Copy data to frame buffer. */ + avio_write(jpeg->frame, buf, len); + + if (flags & RTP_FLAG_MARKER) { + /* End of JPEG data packet. */ + uint8_t buf[2] = {0xff, EOI}; + + /* Put EOI marker. */ + avio_write(jpeg->frame, buf, sizeof(buf)); + return 0; + } + + return AVERROR_EAGAIN; +} + +//---------------------------------------------------------------------------------- +#define DEF(type, name, bytes, write) \ + static inline void bytestream_put_##name(uint8_t **b, const type value) { \ + write(*b, value); \ + (*b) += bytes; \ + } + +DEF(unsigned int, be24, 3, AV_WB24) +DEF(unsigned int, be16, 2, AV_WB16) +DEF(unsigned int, byte, 1, AV_WB8) + +static inline void bytestream_put_buffer(uint8_t **b, const uint8_t *src, unsigned int size) { + memcpy(*b, src, size); + (*b) += size; +} + +void JPEGRtpEncoder::rtpSendJpeg(const uint8_t *buf, int size, uint64_t pts, uint8_t type) +{ + const uint8_t *qtables[4] = { NULL }; + int nb_qtables = 0; + uint8_t w { 0 }, h { 0 }; + uint8_t *p; + int off = 0; /* fragment offset of the current JPEG frame */ + int len; + int i; + int default_huffman_tables = 0; + uint8_t *out = nullptr; + uint16_t restart_interval = 0; + + /* preparse the header for getting some info */ + for (i = 0; i < size; i++) { + if (buf[i] != 0xff) + continue; + + if (buf[i + 1] == DQT) { + int tables, j; + if (buf[i + 4] & 0xF0) + av_log(s1, AV_LOG_WARNING, + "Only 8-bit precision is supported.\n"); + + /* a quantization table is 64 bytes long */ + tables = AV_RB16(&buf[i + 2]) / 65; + if (i + 5 + tables * 65 > size) { + av_log(s1, AV_LOG_ERROR, "Too short JPEG header. Aborted!\n"); + return; + } + if (nb_qtables + tables > 4) { + av_log(s1, AV_LOG_ERROR, "Invalid number of quantisation tables\n"); + return; + } + + for (j = 0; j < tables; j++) + qtables[nb_qtables + j] = buf + i + 5 + j * 65; + nb_qtables += tables; + // 大致忽略DQT/qtable所占字节数,提高搜寻速度 [AUTO-TRANSLATED:63423997] + // Roughly ignore the number of bytes occupied by DQT/qtable to improve search speed + i += tables << 6; + } else if (buf[i + 1] == SOF0) { + if (buf[i + 14] != 17 || buf[i + 17] != 17) { + av_log(s1, AV_LOG_ERROR, + "Only 1x1 chroma blocks are supported. Aborted!\n"); + return; + } + h = (buf[i + 5] * 256 + buf[i + 6]) / 8; + w = (buf[i + 7] * 256 + buf[i + 8]) / 8; + // 大致忽略SOF0所占字节数,提高搜寻速度 [AUTO-TRANSLATED:438cbf70] + // Roughly ignore the number of bytes occupied by SOF0 to improve search speed + i += 16; + } else if (buf[i + 1] == DHT) { + int dht_size = AV_RB16(&buf[i + 2]); + default_huffman_tables |= 1 << 4; + i += 3; + dht_size -= 2; + if (i + dht_size >= size) + continue; + while (dht_size > 0) + switch (buf[i + 1]) { + case 0x00: + if ( dht_size >= 29 + && !memcmp(buf + i + 2, avpriv_mjpeg_bits_dc_luminance + 1, 16) + && !memcmp(buf + i + 18, avpriv_mjpeg_val_dc, 12)) { + default_huffman_tables |= 1; + i += 29; + dht_size -= 29; + } else { + i += dht_size; + dht_size = 0; + } + break; + case 0x01: + if ( dht_size >= 29 + && !memcmp(buf + i + 2, avpriv_mjpeg_bits_dc_chrominance + 1, 16) + && !memcmp(buf + i + 18, avpriv_mjpeg_val_dc, 12)) { + default_huffman_tables |= 1 << 1; + i += 29; + dht_size -= 29; + } else { + i += dht_size; + dht_size = 0; + } + break; + case 0x10: + if ( dht_size >= 179 + && !memcmp(buf + i + 2, avpriv_mjpeg_bits_ac_luminance + 1, 16) + && !memcmp(buf + i + 18, avpriv_mjpeg_val_ac_luminance, 162)) { + default_huffman_tables |= 1 << 2; + i += 179; + dht_size -= 179; + } else { + i += dht_size; + dht_size = 0; + } + break; + case 0x11: + if ( dht_size >= 179 + && !memcmp(buf + i + 2, avpriv_mjpeg_bits_ac_chrominance + 1, 16) + && !memcmp(buf + i + 18, avpriv_mjpeg_val_ac_chrominance, 162)) { + default_huffman_tables |= 1 << 3; + i += 179; + dht_size -= 179; + } else { + i += dht_size; + dht_size = 0; + } + break; + default: + i += dht_size; + dht_size = 0; + continue; + } + } else if (buf[i + 1] == SOS) { + /* SOS is last marker in the header */ + i += AV_RB16(&buf[i + 2]) + 2; + if (i > size) { + av_log(s1, AV_LOG_ERROR, + "Insufficient data. Aborted!\n"); + return; + } + break; + } else if (buf[i + 1] == DRI) { + type |= 0x40; + restart_interval = AV_RB16(&buf[i + 4]); + } + } + if (default_huffman_tables && default_huffman_tables != 31) { + av_log(s1, AV_LOG_ERROR, + "RFC 2435 requires standard Huffman tables for jpeg\n"); + return; + } + if (nb_qtables && nb_qtables != 2) + av_log(s1, AV_LOG_WARNING, + "RFC 2435 suggests two quantization tables, %d provided\n", + nb_qtables); + + /* skip JPEG header */ + buf += i; + size -= i; + + for (i = size - 2; i >= 0; i--) { + if (buf[i] == 0xff && buf[i + 1] == EOI) { + /* Remove the EOI marker */ + size = i; + break; + } + } + + while (size > 0) { + int hdr_size = 8; + + if (off == 0 && nb_qtables) + hdr_size += 4 + 64 * nb_qtables; + + if (type & 0x40) + hdr_size += 4; + + /* payload max in one packet */ + len = MIN(size, (int)getRtpInfo().getMaxSize() - hdr_size); + + /* marker bit is last packet in frame */ + auto rtp_packet = getRtpInfo().makeRtp(TrackVideo, nullptr, len + hdr_size, size == len, pts); + p = rtp_packet->getPayload(); + + /* set main header */ + bytestream_put_byte(&p, 0); + bytestream_put_be24(&p, off); + bytestream_put_byte(&p, type); + bytestream_put_byte(&p, 255); + bytestream_put_byte(&p, w); + bytestream_put_byte(&p, h); + + /* set dri */ + if (type & 0x40) { + bytestream_put_be16(&p, restart_interval); + bytestream_put_byte(&p, 0xff); + bytestream_put_byte(&p, 0xff); + } + + if (off == 0 && nb_qtables) { + /* set quantization tables header */ + bytestream_put_byte(&p, 0); + bytestream_put_byte(&p, 0); + bytestream_put_be16(&p, 64 * nb_qtables); + + for (i = 0; i < nb_qtables; i++) + bytestream_put_buffer(&p, qtables[i], 64); + } + + /* copy payload data */ + memcpy(p, buf, len); + + // output rtp packet + RtpCodec::inputRtp(std::move(rtp_packet), false); + + buf += len; + size -= len; + off += len; + } + free(out); +} + +//////////////////////////////////////////////////////////// + +JPEGRtpDecoder::JPEGRtpDecoder() { + memset(&_ctx.timestamp, 0, sizeof(_ctx) - offsetof(decltype(_ctx), timestamp)); +} + +using JPEGFrameImp = JPEGFrame >; + +bool JPEGRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool) { + auto payload = rtp->getPayload(); + auto size = rtp->getPayloadSize(); + auto stamp = rtp->getStamp(); + auto seq = rtp->getSeq(); + auto marker = rtp->getHeader()->mark; + if (size <= 0) { + // 无实际负载 [AUTO-TRANSLATED:305af48f] + // No actual load + return false; + } + + uint8_t type; + if (0 == jpeg_parse_packet(nullptr, &_ctx, &stamp, payload, size, seq, marker ? RTP_FLAG_MARKER : 0, &type)) { + auto buffer = std::make_shared(std::move(_ctx.frame)); + auto frame = std::make_shared(type, std::move(buffer), stamp / 90, 0); + _ctx.frame.clear(); + RtpCodec::inputFrame(std::move(frame)); + } + + return false; +} + +//////////////////////////////////////////////////////////////////////// + +bool JPEGRtpEncoder::inputFrame(const Frame::Ptr &frame) { + // JFIF头固定20个字节长度 [AUTO-TRANSLATED:bd63b447] + // JFIF header is fixed at 20 bytes in length + auto ptr = (uint8_t *)frame->data() + frame->prefixSize() + JPEGFrameImp::kJFIFSize; + auto len = frame->size() - frame->prefixSize() - JPEGFrameImp::kJFIFSize; + auto pts = frame->pts(); + auto type = 1; + auto jpeg = dynamic_pointer_cast(frame); + if (jpeg) { + type = jpeg->pixType(); + } + rtpSendJpeg(ptr, len, pts, type); + return len > 0; +} diff --git a/MediaServer/ext-codec/JPEGRtp.h b/MediaServer/ext-codec/JPEGRtp.h new file mode 100644 index 0000000..4931d84 --- /dev/null +++ b/MediaServer/ext-codec/JPEGRtp.h @@ -0,0 +1,59 @@ +#ifndef ZLMEDIAKIT_JPEGRTP_H +#define ZLMEDIAKIT_JPEGRTP_H + +#include "Rtsp/RtpCodec.h" +#include "Extension/Frame.h" + +namespace mediakit { + +/** + * RTP/JPEG specific private data. + */ +struct PayloadContext { + std::string frame; ///< current frame buffer + uint32_t timestamp; ///< current frame timestamp + int hdr_size; ///< size of the current frame header + uint8_t qtables[128][128]; + uint8_t qtables_len[128]; +}; + +/** + * 通用 rtp解码类 + * General rtp decoding class + + * [AUTO-TRANSLATED:41b57089] + */ +class JPEGRtpDecoder : public RtpCodec { +public: + typedef std::shared_ptr Ptr; + + JPEGRtpDecoder(); + + /** + * 输入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: + struct PayloadContext _ctx; +}; + +class JPEGRtpEncoder : public RtpCodec { +public: + using Ptr = std::shared_ptr; + + bool inputFrame(const Frame::Ptr &frame) override; + +private: + void rtpSendJpeg(const uint8_t *buf, int size, uint64_t pts, uint8_t type); +}; +}//namespace mediakit +#endif //ZLMEDIAKIT_JPEGRTP_H diff --git a/MediaServer/ext-codec/L16.cpp b/MediaServer/ext-codec/L16.cpp new file mode 100644 index 0000000..64ff4da --- /dev/null +++ b/MediaServer/ext-codec/L16.cpp @@ -0,0 +1,120 @@ +/* + * 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 "L16.h" +#include "Extension/Factory.h" +#include "Extension/CommonRtp.h" +#include "Extension/CommonRtmp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +/** + * L16类型SDP + * L16 type SDP + + * [AUTO-TRANSLATED:11b1196d] + */ +class L16Sdp : public Sdp { +public: + /** + * L16采样位数固定为16位 + * @param payload_type rtp payload type + * @param channels 通道数 + * @param sample_rate 音频采样率 + * @param bitrate 比特率 + * L16 sampling bit width is fixed to 16 bits + * @param payload_type rtp payload type + * @param channels number of channels + * @param sample_rate audio sampling rate + * @param bitrate bitrate + + + * [AUTO-TRANSLATED:7a08a400] + */ + L16Sdp(int payload_type, int sample_rate, int channels, int bitrate) : Sdp(sample_rate, payload_type) { + _printer << "m=audio 0 RTP/AVP " << payload_type << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + _printer << "a=rtpmap:" << payload_type << " " << getCodecName(CodecL16) << "/" << sample_rate << "/" << channels << "\r\n"; + } + + string getSdp() const override { return _printer; } + +private: + _StrPrinter _printer; +}; + +Sdp::Ptr L16Track::getSdp(uint8_t payload_type) const { + WarnL << "Enter L16Track::getSdp function"; + if (!ready()) { + WarnL << getCodecName() << " Track未准备好"; + return nullptr; + } + return std::make_shared(payload_type, getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); +} + +Track::Ptr L16Track::clone() const { + return std::make_shared(*this); +} + +namespace { + +CodecId getCodec() { + return CodecL16; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(sample_rate, channels); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + return std::make_shared(track->_samplerate, track->_channel); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(CodecL16); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + WarnL << "Unsupported L16 rtmp encoder"; + return nullptr; +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + WarnL << "Unsupported L16 rtmp decoder"; + return nullptr; +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared(CodecL16, (char *)data, bytes, dts, pts); +} + +} // namespace + +CodecPlugin l16_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +}//namespace mediakit + + diff --git a/MediaServer/ext-codec/L16.h b/MediaServer/ext-codec/L16.h new file mode 100644 index 0000000..d3574fc --- /dev/null +++ b/MediaServer/ext-codec/L16.h @@ -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 ZLMEDIAKIT_L16_H +#define ZLMEDIAKIT_L16_H + +#include "Extension/Frame.h" +#include "Extension/Track.h" + +namespace mediakit { + +/** + * L16音频通道 + * L16 audio channel + + + * [AUTO-TRANSLATED:7a4b086f] + */ +class L16Track : public AudioTrackImp{ +public: + using Ptr = std::shared_ptr; + L16Track(int sample_rate, int channels) : AudioTrackImp(CodecL16,sample_rate,channels,16){} + +private: + Sdp::Ptr getSdp(uint8_t payload_type) const override; + Track::Ptr clone() const override; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_L16_H \ No newline at end of file diff --git a/MediaServer/ext-codec/Opus.cpp b/MediaServer/ext-codec/Opus.cpp new file mode 100644 index 0000000..beea2a1 --- /dev/null +++ b/MediaServer/ext-codec/Opus.cpp @@ -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. + */ + +#include "Opus.h" +#include "Extension/Factory.h" +#include "Extension/CommonRtp.h" +#include "Extension/CommonRtmp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +/** + * Opus类型SDP + * Opus type SDP + + * [AUTO-TRANSLATED:6c0a72ed] + */ +class OpusSdp : public Sdp { +public: + /** + * 构造opus sdp + * @param payload_type rtp payload type + * @param sample_rate 音频采样率 + * @param channels 通道数 + * @param bitrate 比特率 + * Construct opus sdp + * @param payload_type rtp payload type + * @param sample_rate audio sample rate + * @param channels number of channels + * @param bitrate bitrate + + + * [AUTO-TRANSLATED:40713e9d] + */ + OpusSdp(int payload_type, int sample_rate, int channels, int bitrate) : Sdp(sample_rate, payload_type) { + _printer << "m=audio 0 RTP/AVP " << payload_type << "\r\n"; + if (bitrate) { + _printer << "b=AS:" << bitrate << "\r\n"; + } + _printer << "a=rtpmap:" << payload_type << " " << getCodecName(CodecOpus) << "/" << sample_rate << "/" << channels << "\r\n"; + } + + string getSdp() const override { + return _printer; + } + +private: + _StrPrinter _printer; +}; + +Sdp::Ptr OpusTrack::getSdp(uint8_t payload_type) const { + if (!ready()) { + WarnL << getCodecName() << " Track未准备好"; + return nullptr; + } + return std::make_shared(payload_type, getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); +} + +namespace { + +CodecId getCodec() { + return CodecOpus; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(CodecOpus); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared(CodecOpus, (char *)data, bytes, dts, pts); +} + +} // namespace + +CodecPlugin opus_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +}//namespace mediakit \ No newline at end of file diff --git a/MediaServer/ext-codec/Opus.h b/MediaServer/ext-codec/Opus.h new file mode 100644 index 0000000..a626b2f --- /dev/null +++ b/MediaServer/ext-codec/Opus.h @@ -0,0 +1,42 @@ +/* + * 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_OPUS_H +#define ZLMEDIAKIT_OPUS_H + +#include "Extension/Frame.h" +#include "Extension/Track.h" + +namespace mediakit { + +/** + * Opus帧音频通道 + * Opus frame audio channel + + * [AUTO-TRANSLATED:522e95da] + */ +class OpusTrack : public AudioTrackImp{ +public: + using Ptr = std::shared_ptr; + OpusTrack() : AudioTrackImp(CodecOpus,48000,2,16){} + +private: + // 克隆该Track [AUTO-TRANSLATED:9a15682a] + // Clone this Track + Track::Ptr clone() const override { + return std::make_shared(*this); + } + // 生成sdp [AUTO-TRANSLATED:663a9367] + // Generate sdp + Sdp::Ptr getSdp(uint8_t payload_type) const override ; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_OPUS_H diff --git a/MediaServer/ext-codec/SPSParser.c b/MediaServer/ext-codec/SPSParser.c new file mode 100644 index 0000000..0a33ae8 --- /dev/null +++ b/MediaServer/ext-codec/SPSParser.c @@ -0,0 +1,2321 @@ +#include +#include +#include +#include /* for uint32_t, etc */ +#include "SPSParser.h" + +/******************************************** +*define here +********************************************/ +#define SPS_PPS_DEBUG +//#undef SPS_PPS_DEBUG + + +#define MAX_LEN 32 +#define EXTENDED_SAR 255 +#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) +#define MAX_SPS_COUNT 32 +#define MAX_LOG2_MAX_FRAME_NUM (12 + 4) +#define MIN_LOG2_MAX_FRAME_NUM 4 +#define H264_MAX_PICTURE_COUNT 36 +#define CODEC_FLAG2_IGNORE_CROP 0x00010000 ///< Discard cropping information from SPS. +#ifndef INT_MAX +#define INT_MAX 65535 +#endif //INT_MAX + +#ifndef FFMIN +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) +#endif +#ifndef FFMAX +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/* report level */ +#define RPT_ERR (1) // error, system error +#define RPT_WRN (2) // warning, maybe wrong, maybe OK +#define RPT_INF (3) // important information +#define RPT_DBG (4) // debug information + +static int rpt_lvl = RPT_WRN; /* report level: ERR, WRN, INF, DBG */ + +/* report micro */ +#define RPT(lvl, ...) \ + do { \ + if(lvl <= rpt_lvl) { \ + switch(lvl) { \ + case RPT_ERR: \ + fprintf(stderr, "\"%s\" line %d [err]: ", __FILE__, __LINE__); \ + break; \ + case RPT_WRN: \ + fprintf(stderr, "\"%s\" line %d [wrn]: ", __FILE__, __LINE__); \ + break; \ + case RPT_INF: \ + fprintf(stderr, "\"%s\" line %d [inf]: ", __FILE__, __LINE__); \ + break; \ + case RPT_DBG: \ + fprintf(stderr, "\"%s\" line %d [dbg]: ", __FILE__, __LINE__); \ + break; \ + default: \ + fprintf(stderr, "\"%s\" line %d [???]: ", __FILE__, __LINE__); \ + break; \ + } \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } \ + } while(0) + +static const uint8_t sg_aau8DefaultScaling4[2][16] = { + { 6, 13, 20, 28, 13, 20, 28, 32, + 20, 28, 32, 37, 28, 32, 37, 42 }, + { 10, 14, 20, 24, 14, 20, 24, 27, + 20, 24, 27, 30, 24, 27, 30, 34 } +}; + +static const uint8_t sg_aau8DefaultScaling8[2][64] = { + { 6, 10, 13, 16, 18, 23, 25, 27, + 10, 11, 16, 18, 23, 25, 27, 29, + 13, 16, 18, 23, 25, 27, 29, 31, + 16, 18, 23, 25, 27, 29, 31, 33, + 18, 23, 25, 27, 29, 31, 33, 36, + 23, 25, 27, 29, 31, 33, 36, 38, + 25, 27, 29, 31, 33, 36, 38, 40, + 27, 29, 31, 33, 36, 38, 40, 42 }, + { 9, 13, 15, 17, 19, 21, 22, 24, + 13, 13, 17, 19, 21, 22, 24, 25, + 15, 17, 19, 21, 22, 24, 25, 27, + 17, 19, 21, 22, 24, 25, 27, 28, + 19, 21, 22, 24, 25, 27, 28, 30, + 21, 22, 24, 25, 27, 28, 30, 32, + 22, 24, 25, 27, 28, 30, 32, 33, + 24, 25, 27, 28, 30, 32, 33, 35 } +}; + +static const T_AVRational sg_atFfH264PixelSspect[17] = { + { 0, 1 }, + { 1, 1 }, + { 12, 11 }, + { 10, 11 }, + { 16, 11 }, + { 40, 33 }, + { 24, 11 }, + { 20, 11 }, + { 32, 11 }, + { 80, 33 }, + { 18, 11 }, + { 15, 11 }, + { 64, 33 }, + { 160, 99 }, + { 4, 3 }, + { 3, 2 }, + { 2, 1 }, +}; + +static const uint8_t sg_au8ZigzagScan[16+1] = { + 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, + 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4, + 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, + 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4, +}; + +const uint8_t g_au8FfZigzagDirect[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + + +static const uint8_t sg_au8HevcSubWidthC[] = { + 1, 2, 2, 1 +}; + +static const uint8_t sg_au8HevcSubHeightC[] = { + 1, 2, 1, 1 +}; + +static const uint8_t sg_au8DefaultScalingListIntra[] = { + 16, 16, 16, 16, 17, 18, 21, 24, + 16, 16, 16, 16, 17, 19, 22, 25, + 16, 16, 17, 18, 20, 22, 25, 29, + 16, 16, 18, 21, 24, 27, 31, 36, + 17, 17, 20, 24, 30, 35, 41, 47, + 18, 19, 22, 27, 35, 44, 54, 65, + 21, 22, 25, 31, 41, 54, 70, 88, + 24, 25, 29, 36, 47, 65, 88, 115 +}; + +static const uint8_t sg_au8DefaultScalingListInter[] = { + 16, 16, 16, 16, 17, 18, 20, 24, + 16, 16, 16, 17, 18, 20, 24, 25, + 16, 16, 17, 18, 20, 24, 25, 28, + 16, 17, 18, 20, 24, 25, 28, 33, + 17, 18, 20, 24, 25, 28, 33, 41, + 18, 20, 24, 25, 28, 33, 41, 54, + 20, 24, 25, 28, 33, 41, 54, 71, + 24, 25, 28, 33, 41, 54, 71, 91 +}; + + +const uint8_t g_au8HevcDiagScan4x4X[16] = { + 0, 0, 1, 0, + 1, 2, 0, 1, + 2, 3, 1, 2, + 3, 2, 3, 3, +}; + +const uint8_t g_au8HevcDiagScan4x4Y[16] = { + 0, 1, 0, 2, + 1, 0, 3, 2, + 1, 0, 3, 2, + 1, 3, 2, 3, +}; + +const uint8_t g_au8HevcDiagScan8x8X[64] = { + 0, 0, 1, 0, + 1, 2, 0, 1, + 2, 3, 0, 1, + 2, 3, 4, 0, + 1, 2, 3, 4, + 5, 0, 1, 2, + 3, 4, 5, 6, + 0, 1, 2, 3, + 4, 5, 6, 7, + 1, 2, 3, 4, + 5, 6, 7, 2, + 3, 4, 5, 6, + 7, 3, 4, 5, + 6, 7, 4, 5, + 6, 7, 5, 6, + 7, 6, 7, 7, +}; + +const uint8_t g_au8HevcDiagScan8x8Y[64] = { + 0, 1, 0, 2, + 1, 0, 3, 2, + 1, 0, 4, 3, + 2, 1, 0, 5, + 4, 3, 2, 1, + 0, 6, 5, 4, + 3, 2, 1, 0, + 7, 6, 5, 4, + 3, 2, 1, 0, + 7, 6, 5, 4, + 3, 2, 1, 7, + 6, 5, 4, 3, + 2, 7, 6, 5, + 4, 3, 7, 6, + 5, 4, 7, 6, + 5, 7, 6, 7, +}; + +static const T_AVRational sg_atVuiSar[] = { + { 0, 1 }, + { 1, 1 }, + { 12, 11 }, + { 10, 11 }, + { 16, 11 }, + { 40, 33 }, + { 24, 11 }, + { 20, 11 }, + { 32, 11 }, + { 80, 33 }, + { 18, 11 }, + { 15, 11 }, + { 64, 33 }, + { 160, 99 }, + { 4, 3 }, + { 3, 2 }, + { 2, 1 }, +}; + +static inline int getBitsLeft(void *pvHandle) +{ + int iResLen = 0; + T_GetBitContext *ptPtr = (T_GetBitContext *)pvHandle; + if(ptPtr->iBufSize <= 0 || ptPtr->iTotalBit <= 0) + { + RPT(RPT_WRN, "buffer size is zero"); + return 0; + } + + + iResLen = ptPtr->iTotalBit - ptPtr->iBitPos; + return iResLen; +} + + +/******************************************** +*functions +********************************************/ +/** + * @brief Function getOneBit() get next bit + * @param[in] h T_GetBitContext structrue + * @retval other : success, -1 : failure + * @pre + * @post + */ +static int getOneBit(void *pvHandle) +{ + T_GetBitContext *ptPtr = (T_GetBitContext *)pvHandle; + int iRet = 0; + uint8_t *pu8CurChar = NULL; + uint8_t u8Shift; + int iResoLen = 0; + + if(NULL == ptPtr) + { + RPT(RPT_ERR, "NULL pointer"); + iRet = -1; + goto exit; + } + iResoLen = getBitsLeft(ptPtr); + if(iResoLen < 1) + { + iRet = -1; + goto exit; + } + + pu8CurChar = ptPtr->pu8Buf + (ptPtr->iBitPos >> 3); + u8Shift = 7 - (ptPtr->iCurBitPos); + ptPtr->iBitPos++; + ptPtr->iCurBitPos = ptPtr->iBitPos & 0x7; + iRet = ((*pu8CurChar) >> u8Shift) & 0x01; + +exit: + return iRet; +} + + +/** + * @brief Function getBits() get next bits + * @param[in] h T_GetBitContext structrue + * @param[in] n how many bits you want? + * @retval other : success, -1 : failure + * @pre + * @post + */ +static int getBits(void *pvHandle, int iN) +{ + T_GetBitContext *ptPtr = (T_GetBitContext *)pvHandle; + uint8_t au8Temp[5] = {0}; + uint8_t *pu8CurChar = NULL; + uint8_t u8Nbyte; + uint8_t u8Shift; + uint32_t u32Result = 0; + uint32_t iRet = 0; + int iResoLen = 0; + + if(NULL == ptPtr) + { + RPT(RPT_ERR, "NULL pointer"); + goto exit; + } + + if(iN > MAX_LEN) + { + iN = MAX_LEN; + } + + iResoLen = getBitsLeft(ptPtr); + if(iResoLen < iN) + { + goto exit; + } + + + if((ptPtr->iBitPos + iN) > ptPtr->iTotalBit) + { + iN = ptPtr->iTotalBit- ptPtr->iBitPos; + } + + pu8CurChar = ptPtr->pu8Buf+ (ptPtr->iBitPos>>3); + u8Nbyte = (ptPtr->iCurBitPos + iN + 7) >> 3; + u8Shift = (8 - (ptPtr->iCurBitPos + iN))& 0x07; + + if(iN == MAX_LEN) + { + RPT(RPT_DBG, "12(ptPtr->iBitPos(:%d) + iN(:%d)) > ptPtr->iTotalBit(:%d)!!! ",\ + ptPtr->iBitPos, iN, ptPtr->iTotalBit); + RPT(RPT_DBG, "0x%x 0x%x 0x%x 0x%x", (*pu8CurChar), *(pu8CurChar+1),*(pu8CurChar+2),*(pu8CurChar+3)); + } + + memcpy(&au8Temp[5-u8Nbyte], pu8CurChar, u8Nbyte); + iRet = (uint32_t)au8Temp[0] << 24; + iRet = iRet << 8; + iRet = ((uint32_t)au8Temp[1]<<24)|((uint32_t)au8Temp[2] << 16)\ + |((uint32_t)au8Temp[3] << 8)|au8Temp[4]; + + iRet = (iRet >> u8Shift) & (((uint64_t)1<iBitPos += iN; + ptPtr->iCurBitPos = ptPtr->iBitPos & 0x7; + +exit: + return u32Result; +} + + +/** + * Show 1-25 bits. + */ +static inline unsigned int showBits(void *pvHandle, int iN) +{ + T_GetBitContext *ptPtr = (T_GetBitContext *)pvHandle; + uint8_t au8Temp[5] = {0}; + uint8_t *pu8CurChar = NULL; + uint8_t u8Nbyte; + uint8_t u8Shift; + uint32_t u32Result = 0; + int iRet = 0; + int iResoLen = 0; + + if(NULL == ptPtr) + { + RPT(RPT_ERR, "NULL pointer"); + iRet = -1; + goto exit; + } + + if(iN > MAX_LEN) + { + iN = MAX_LEN; + } + + iResoLen = getBitsLeft(ptPtr); + if(iResoLen < iN) + { + iRet = -1; + goto exit; + } + + + if((ptPtr->iBitPos + iN) > ptPtr->iTotalBit) + { + iN = ptPtr->iTotalBit- ptPtr->iBitPos; + } + + pu8CurChar = ptPtr->pu8Buf+ (ptPtr->iBitPos>>3); + u8Nbyte = (ptPtr->iCurBitPos + iN + 7) >> 3; + u8Shift = (8 - (ptPtr->iCurBitPos + iN))& 0x07; + + if(iN == MAX_LEN) + { + RPT(RPT_DBG, "12(ptPtr->iBitPos(:%d) + iN(:%d)) > ptPtr->iTotalBit(:%d)!!! ",\ + ptPtr->iBitPos, iN, ptPtr->iTotalBit); + RPT(RPT_DBG, "0x%x 0x%x 0x%x 0x%x", (*pu8CurChar), *(pu8CurChar+1),*(pu8CurChar+2),*(pu8CurChar+3)); + } + + memcpy(&au8Temp[5-u8Nbyte], pu8CurChar, u8Nbyte); + iRet = (uint32_t)au8Temp[0] << 24; + iRet = iRet << 8; + iRet = ((uint32_t)au8Temp[1]<<24)|((uint32_t)au8Temp[2] << 16)\ + |((uint32_t)au8Temp[3] << 8)|au8Temp[4]; + + iRet = (iRet >> u8Shift) & (((uint64_t)1<iBitPos += iN; +// ptPtr->iCurBitPos = ptPtr->iBitPos & 0x7; + +exit: + return u32Result; +} + + + +/** + * Show 0-32 bits. + */ +static inline unsigned int showBitsLong(void *pvHandle, int iN) +{ + T_GetBitContext *ptPtr = (T_GetBitContext *)pvHandle; + + if (iN <= 32) { + return showBits(ptPtr, iN); + } + return 0; +} + + + +/** + * @brief Function parseCodenum() + * @param[in] buf + * @retval u32CodeNum + * @pre + * @post + */ +static int parseCodenum(void *pvBuf) +{ + uint8_t u8LeadingZeroBits = -1; + uint8_t u8B; + uint32_t u32CodeNum = 0; + + for(u8B=0; !u8B; u8LeadingZeroBits++) + { + u8B = getOneBit(pvBuf); + } + + u32CodeNum = ((uint32_t)1 << u8LeadingZeroBits) - 1 + getBits(pvBuf, u8LeadingZeroBits); + + return u32CodeNum; +} + +/** + * @brief Function parseUe() + * @param[in] buf sps_pps parse buf + * @retval u32CodeNum + * @pre + * @post + */ +static int parseUe(void *pvBuf) +{ + return parseCodenum(pvBuf); +} + + +/** + * @brief Function parseSe() + * @param[in] buf sps_pps parse buf + * @retval u32CodeNum + * @pre + * @post + */ +static int parseSe(void *pvBuf) +{ + int iRet = 0; + int u32CodeNum; + + u32CodeNum = parseCodenum(pvBuf); + iRet = (u32CodeNum + 1) >> 1; + iRet = (u32CodeNum & 0x01)? iRet : -iRet; + + return iRet; +} + + +/** + * @brief Function getBitContextFree() + * @param[in] buf T_GetBitContext buf + * @retval none + * @pre + * @post + */ +static void getBitContextFree(void *pvBuf) +{ + T_GetBitContext *ptPtr = (T_GetBitContext *)pvBuf; + + if(ptPtr) + { + if(ptPtr->pu8Buf) + { + free(ptPtr->pu8Buf); + } + + free(ptPtr); + } +} + + + + +/** + * @brief Function deEmulationPrevention() + * @param[in] buf T_GetBitContext buf + * @retval none + * @pre + * @post + * @note: + * http://www.cnblogs.com/eustoma/archive/2012/02/13/2415764.html + * 0x000000 >>>>>> 0x00000300 + * 0x000001 >>>>>> 0x00000301 + * 0x000002 >>>>>> 0x00000302 + * 0x000003 >>>>>> 0x00000303 + */ +static void *deEmulationPrevention(void *pvBuf) +{ + T_GetBitContext *ptPtr = NULL; + T_GetBitContext *ptBufPtr = (T_GetBitContext *)pvBuf; + int i = 0, j = 0; + uint8_t *pu8TmpPtr = NULL; + int tmp_buf_size = 0; + int iVal = 0; + + if(NULL == ptBufPtr) + { + RPT(RPT_ERR, "NULL ptPtr"); + goto exit; + } + + ptPtr = (T_GetBitContext *)malloc(sizeof(T_GetBitContext)); + if(NULL == ptPtr) + { + RPT(RPT_ERR, "NULL ptPtr"); + goto exit; + } + + memcpy(ptPtr, ptBufPtr, sizeof(T_GetBitContext)); + + ptPtr->pu8Buf = (uint8_t *)malloc(ptPtr->iBufSize); + if(NULL == ptPtr->pu8Buf) + { + RPT(RPT_ERR, "NULL ptPtr"); + goto exit; + } + + memcpy(ptPtr->pu8Buf, ptBufPtr->pu8Buf, ptBufPtr->iBufSize); + + pu8TmpPtr = ptPtr->pu8Buf; + tmp_buf_size = ptPtr->iBufSize; + for(i=0; i<(tmp_buf_size-2); i++) + { + + iVal = (pu8TmpPtr[i]^0x00) + (pu8TmpPtr[i+1]^0x00) + (pu8TmpPtr[i+2]^0x03); + if(iVal == 0) + { + + for(j=i+2; jiBufSize--; + } + } + ptPtr->iTotalBit = ptPtr->iBufSize << 3; + + return (void *)ptPtr; + +exit: + getBitContextFree(ptPtr); + return NULL; +} + +static void decodeScalingList(void *pvBuf, uint8_t *pu8Factors, int iSize, + const uint8_t *pu8JvtList, + const uint8_t *pu8FallbackList) +{ + int i; + int iLast = 8; + int iNext = 8; + const uint8_t *pu8Scan = iSize == 16 ? sg_au8ZigzagScan : g_au8FfZigzagDirect; + + if (!getOneBit(pvBuf)) /* matrix not written, we use the predicted one */ + memcpy(pu8Factors, pu8FallbackList, iSize * sizeof(uint8_t)); + else + for (i = 0; i < iSize; i++) + { + if (iNext) + { + + iNext = (iLast + parseSe(pvBuf)) & 0xff; + } + if (!i && !iNext) + { + /* matrix not written, we use the preset one */ + memcpy(pu8Factors, pu8JvtList, iSize * sizeof(uint8_t)); + break; + } + iLast = pu8Factors[pu8Scan[i]] = iNext ? iNext : iLast; + } +} + + + int decodeScalingMatrices(void *pvBuf, T_SPS *ptSps, + T_PPS *ptPps, int iIsSps, + uint8_t(*pau8ScalingMatrix4)[16], + uint8_t(*pau8ScalingMatrix8)[64]) +{ + int iFallbackSps = !iIsSps && ptSps->iScalingMatrixPresent; + const uint8_t *pau8Fallback[4] = { + iFallbackSps ? ptSps->aau8ScalingMatrix4[0] : sg_aau8DefaultScaling4[0], + iFallbackSps ? ptSps->aau8ScalingMatrix4[3] : sg_aau8DefaultScaling4[1], + iFallbackSps ? ptSps->aau8ScalingMatrix8[0] : sg_aau8DefaultScaling8[0], + iFallbackSps ? ptSps->aau8ScalingMatrix8[3] : sg_aau8DefaultScaling8[1] + }; + if (getOneBit(pvBuf)) { + ptSps->iScalingMatrixPresent |= iIsSps; + decodeScalingList(pvBuf, pau8ScalingMatrix4[0], 16, sg_aau8DefaultScaling4[0], pau8Fallback[0]); // Intra, Y + decodeScalingList(pvBuf, pau8ScalingMatrix4[1], 16, sg_aau8DefaultScaling4[0], pau8ScalingMatrix4[0]); // Intra, Cr + decodeScalingList(pvBuf, pau8ScalingMatrix4[2], 16, sg_aau8DefaultScaling4[0], pau8ScalingMatrix4[1]); // Intra, Cb + + decodeScalingList(pvBuf, pau8ScalingMatrix4[3], 16, sg_aau8DefaultScaling4[1], pau8Fallback[1]); // Inter, Y + + decodeScalingList(pvBuf, pau8ScalingMatrix4[4], 16, sg_aau8DefaultScaling4[1], pau8ScalingMatrix4[3]); // Inter, Cr + + decodeScalingList(pvBuf, pau8ScalingMatrix4[5], 16, sg_aau8DefaultScaling4[1], pau8ScalingMatrix4[4]); // Inter, Cb + + + if (iIsSps || ptPps->iTransform8x8Mode) + { + decodeScalingList(pvBuf, pau8ScalingMatrix8[0], 64, sg_aau8DefaultScaling8[0], pau8Fallback[2]); // Intra, Y + decodeScalingList(pvBuf, pau8ScalingMatrix8[3], 64, sg_aau8DefaultScaling8[1], pau8Fallback[3]); // Inter, Y + if (ptSps->iChromaFormatIdc == 3) { + decodeScalingList(pvBuf, pau8ScalingMatrix8[1], 64, sg_aau8DefaultScaling8[0], pau8ScalingMatrix8[0]); // Intra, Cr + decodeScalingList(pvBuf, pau8ScalingMatrix8[4], 64, sg_aau8DefaultScaling8[1], pau8ScalingMatrix8[3]); // Inter, Cr + decodeScalingList(pvBuf, pau8ScalingMatrix8[2], 64, sg_aau8DefaultScaling8[0], pau8ScalingMatrix8[1]); // Intra, Cb + decodeScalingList(pvBuf, pau8ScalingMatrix8[5], 64, sg_aau8DefaultScaling8[1], pau8ScalingMatrix8[4]); // Inter, Cb + } + } + } + return 0; +} + +static int decodeHrdPAarameters(void *pvBuf, T_SPS *ptSps) +{ + int iCpbCount = 0; + int i; + iCpbCount = parseUe(pvBuf); + + if (iCpbCount > 32U) + { + RPT(RPT_ERR,"iCpbCount %d invalid\n", iCpbCount); + return -1; + + } + + getBits(pvBuf, 4); /* bit_rate_scale */ + getBits(pvBuf, 4); /* cpb_size_scale */ + for (i = 0; i < iCpbCount; i++) + { + parseUe(pvBuf); + parseUe(pvBuf); + //get_ue_golomb_long(&h->gb); /* bit_rate_value_minus1 */ + //get_ue_golomb_long(&h->gb); /* cpb_size_value_minus1 */ + getOneBit(pvBuf); /* cbr_flag */ + } + ptSps->iInitialCpbRemovalDelayLength = getBits(pvBuf, 5) + 1; + ptSps->iCpbRemovalDelayLength = getBits(pvBuf, 5) + 1; + ptSps->iDpbOutputDelayLength = getBits(pvBuf, 5) + 1; + ptSps->iTimeOffsetLength = getBits(pvBuf, 5); + ptSps->iCpbCnt = iCpbCount; + return 0; +} + + +static inline int decodeVuiParameters(void *pvBuf, T_SPS *ptSps) +{ + int iAspectRatioInfoPresentFlag; + unsigned int uiAspectRatioIdc; + + iAspectRatioInfoPresentFlag = getOneBit(pvBuf); + + if (iAspectRatioInfoPresentFlag) { + uiAspectRatioIdc = getBits(pvBuf, 8); + if (uiAspectRatioIdc == EXTENDED_SAR) { + ptSps->tSar.num = getBits(pvBuf, 16); + ptSps->tSar.den = getBits(pvBuf, 16); + } else if (uiAspectRatioIdc < FF_ARRAY_ELEMS(sg_atFfH264PixelSspect)) { + ptSps->tSar = sg_atFfH264PixelSspect[uiAspectRatioIdc]; + } else + { + RPT(RPT_ERR,"illegal aspect ratio\n"); + return -1; + } + } else + { + ptSps->tSar.num = + ptSps->tSar.den = 0; + } + + if (getOneBit(pvBuf)) /* iOverscanInfoPresentFlag */ + getOneBit(pvBuf); /* iOverscanAppropriateFlag */ + + ptSps->iVideoSignalTypePresentFlag = getOneBit(pvBuf); + if (ptSps->iVideoSignalTypePresentFlag) { + getBits(pvBuf, 3); /* video_format */ + ptSps->iFullRange = getOneBit(pvBuf); /* iVideoFullRangeFlag */ + + ptSps->iColourDescriptionPresentFlag = getOneBit(pvBuf); + if (ptSps->iColourDescriptionPresentFlag) { + ptSps->tColorPrimaries = getBits(pvBuf, 8); /* u8ColourPrimaries */ + ptSps->tColorTrc = getBits(pvBuf, 8); /* transfer_characteristics */ + ptSps->tColorspace = getBits(pvBuf, 8); /* matrix_coefficients */ + if (ptSps->tColorPrimaries >= AVCOL_PRI_NB) + ptSps->tColorPrimaries = AVCOL_PRI_UNSPECIFIED; + if (ptSps->tColorTrc >= AVCOL_TRC_NB) + ptSps->tColorTrc = AVCOL_TRC_UNSPECIFIED; + if (ptSps->tColorspace >= AVCOL_SPC_NB) + ptSps->tColorspace = AVCOL_SPC_UNSPECIFIED; + } + } + + /* chroma_location_info_present_flag */ + if (getOneBit(pvBuf)) + { + parseUe(pvBuf); /* chroma_sample_location_type_top_field */ + parseUe(pvBuf); /* chroma_sample_location_type_bottom_field */ + } + if(getBitsLeft(pvBuf) < 10) + { + return 0; + } + + + ptSps->iTimingInfoPresentFlag = getOneBit(pvBuf); + if (ptSps->iTimingInfoPresentFlag) { + unsigned u32NumUnitsInTick = getBits(pvBuf, 32); + unsigned u32TimeScale = getBits(pvBuf, 32); + if (!u32NumUnitsInTick || !u32TimeScale) { + + RPT(RPT_ERR,"u32TimeScale/u32NumUnitsInTick invalid or unsupported (%u/%u)\n",u32TimeScale, u32NumUnitsInTick); + ptSps->iTimingInfoPresentFlag = 0; + } else { + ptSps->u32NumUnitsInTick = u32NumUnitsInTick; + ptSps->u32TimeScale = u32TimeScale; + } + ptSps->iFixedFrameRateFlag = getOneBit(pvBuf); + } + + ptSps->iNalHrdParametersPresentFlag = getOneBit(pvBuf); + if (ptSps->iNalHrdParametersPresentFlag) + if (decodeHrdPAarameters(pvBuf, ptSps) < 0) + return -1; + ptSps->iVclHrdParametersPresentFlag = getOneBit(pvBuf); + if (ptSps->iVclHrdParametersPresentFlag) + if (decodeHrdPAarameters(pvBuf, ptSps) < 0) + return -1; + if (ptSps->iNalHrdParametersPresentFlag || + ptSps->iVclHrdParametersPresentFlag) + getOneBit(pvBuf); /* low_delay_hrd_flag */ + ptSps->iPicStructPresentFlag = getOneBit(pvBuf); + + if(getBitsLeft(pvBuf) == 0) + return 0; + ptSps->iBitstreamRestrictionFlag = getOneBit(pvBuf); + if (ptSps->iBitstreamRestrictionFlag) { + getOneBit(pvBuf); /* motion_vectors_over_pic_boundaries_flag */ + parseUe(pvBuf); + //get_ue_golomb(&h->gb); /* max_bytes_per_pic_denom */ + parseUe(pvBuf); + //get_ue_golomb(&h->gb); /* max_bits_per_mb_denom */ + parseUe(pvBuf); + //get_ue_golomb(&h->gb); /* log2_max_mv_length_horizontal */ + parseUe(pvBuf); + //get_ue_golomb(&h->gb); /* log2_max_mv_length_vertical */ + ptSps->iNumReorderFrames = parseUe(pvBuf); + + parseUe(pvBuf); + //get_ue_golomb(&h->gb); /*max_dec_frame_buffering*/ + + if (getBitsLeft(pvBuf) < 0) + { + ptSps->iNumReorderFrames = 0; + ptSps->iBitstreamRestrictionFlag = 0; + } + + if (ptSps->iNumReorderFrames > 16U + /* max_dec_frame_buffering || max_dec_frame_buffering > 16 */) + { + RPT(RPT_DBG, "Clipping illegal iNumReorderFrames %d\n", + ptSps->iNumReorderFrames); + ptSps->iNumReorderFrames = 16; + return -1; + } + } + + return 0; +} + + +int h264DecSeqParameterSet(void *pvBufSrc, T_SPS *ptSps) +{ + int iLevelIdc; + int iConstraintSetFlags = 0; + unsigned int uiSpsId; + int i; + int iLog2MaxFrameNumMinus4; + + int iRet = 0; + int iProfileIdc = 0; + void *pvBuf = NULL; + + + + if(NULL == pvBufSrc || NULL == ptSps) + { + RPT(RPT_ERR,"ERR null pointer\n"); + iRet = -1; + goto exit; + } + + memset((void *)ptSps, 0, sizeof(T_SPS)); + + pvBuf = deEmulationPrevention(pvBufSrc); + if(NULL == pvBuf) + { + RPT(RPT_ERR,"ERR null pointer\n"); + iRet = -1; + goto exit; + } + + iProfileIdc = getBits(pvBuf, 8); + iConstraintSetFlags |= getOneBit(pvBuf) << 0; // constraint_set0_flag + iConstraintSetFlags |= getOneBit(pvBuf) << 1; // constraint_set1_flag + iConstraintSetFlags |= getOneBit(pvBuf) << 2; // constraint_set2_flag + iConstraintSetFlags |= getOneBit(pvBuf) << 3; // constraint_set3_flag + iConstraintSetFlags |= getOneBit(pvBuf) << 4; // constraint_set4_flag + iConstraintSetFlags |= getOneBit(pvBuf) << 5; // constraint_set5_flag + getBits(pvBuf, 2); // reserved_zero_2bits + iLevelIdc = getBits(pvBuf, 8); + uiSpsId = parseUe(pvBuf); + + if (uiSpsId >= MAX_SPS_COUNT) { + RPT(RPT_ERR, "uiSpsId %u out of range\n", uiSpsId); + iRet = -1; + goto exit; + + } + + + ptSps->uiSpsId = uiSpsId; + ptSps->iTimeOffsetLength = 24; + ptSps->iProfileIdc = iProfileIdc; + ptSps->iConstraintSetFlags = iConstraintSetFlags; + ptSps->iLevelIdc = iLevelIdc; + ptSps->iFullRange = -1; + + memset(ptSps->aau8ScalingMatrix4, 16, sizeof(ptSps->aau8ScalingMatrix4)); + memset(ptSps->aau8ScalingMatrix8, 16, sizeof(ptSps->aau8ScalingMatrix8)); + ptSps->iScalingMatrixPresent = 0; + ptSps->tColorspace = 2; //AVCOL_SPC_UNSPECIFIED + + if (ptSps->iProfileIdc == 100 || // High profile + ptSps->iProfileIdc == 110 || // High10 profile + ptSps->iProfileIdc == 122 || // High422 profile + ptSps->iProfileIdc == 244 || // High444 Predictive profile + ptSps->iProfileIdc == 44 || // Cavlc444 profile + ptSps->iProfileIdc == 83 || // Scalable Constrained High profile (SVC) + ptSps->iProfileIdc == 86 || // Scalable High Intra profile (SVC) + ptSps->iProfileIdc == 118 || // Stereo High profile (MVC) + ptSps->iProfileIdc == 128 || // Multiview High profile (MVC) + ptSps->iProfileIdc == 138 || // Multiview Depth High profile (MVCD) + ptSps->iProfileIdc == 144) { // old High444 profile + ptSps->iChromaFormatIdc = parseUe(pvBuf); + if (ptSps->iChromaFormatIdc > 3U) + { + RPT(RPT_ERR, "iChromaFormatIdc %u",ptSps->iChromaFormatIdc); + iRet = -1; + goto exit; + } + else if (ptSps->iChromaFormatIdc == 3) + { + ptSps->iResidualColorTransformFlag = getOneBit(pvBuf); + if (ptSps->iResidualColorTransformFlag) + { + RPT(RPT_ERR, "separate color planes are not supported\n"); + iRet = -1; + goto exit; + + } + } + ptSps->iBitDepthLuma = parseUe(pvBuf) + 8; + ptSps->iBitDepthChroma = parseUe(pvBuf) + 8; + if (ptSps->iBitDepthChroma != ptSps->iBitDepthLuma) + { + RPT(RPT_ERR, "Different chroma and luma bit depth"); + iRet = -1; + goto exit; + + } + if (ptSps->iBitDepthLuma < 8 || ptSps->iBitDepthLuma > 14 || + ptSps->iBitDepthChroma < 8 || ptSps->iBitDepthChroma > 14) + { + RPT(RPT_ERR, "illegal bit depth value (%d, %d)\n",ptSps->iBitDepthLuma, ptSps->iBitDepthChroma); + iRet = -1; + goto exit; + } + ptSps->iTransformBypass = getOneBit(pvBuf); + decodeScalingMatrices(pvBuf, ptSps, NULL, 1, + ptSps->aau8ScalingMatrix4, ptSps->aau8ScalingMatrix8); + } + else + { + + ptSps->iChromaFormatIdc = 1; + ptSps->iBitDepthLuma = 8; + ptSps->iBitDepthChroma = 8; + } + + iLog2MaxFrameNumMinus4 = parseUe(pvBuf); + + if (iLog2MaxFrameNumMinus4 < MIN_LOG2_MAX_FRAME_NUM - 4 || + iLog2MaxFrameNumMinus4 > MAX_LOG2_MAX_FRAME_NUM - 4) + { + RPT(RPT_ERR, "iLog2MaxFrameNumMinus4 out of range (0-12): %d\n", iLog2MaxFrameNumMinus4); + iRet = -1; + goto exit; + + } + ptSps->iLog2MaxFrameNum = iLog2MaxFrameNumMinus4 + 4; + + ptSps->iPocType = parseUe(pvBuf); + + if (ptSps->iPocType == 0) + { + // FIXME #define + unsigned t = parseUe(pvBuf); + if (t>12) + { + RPT(RPT_ERR, "iLog2MaxPocLsb (%d) is out of range\n", t); + iRet = -1; + goto exit; + + } + ptSps->iLog2MaxPocLsb = t + 4; + } + else if (ptSps->iPocType == 1) + { + // FIXME #define + ptSps->iDeltaPicOrderAlwaysZeroFlag = getOneBit(pvBuf); + ptSps->iOffsetForNonRefPic = parseSe(pvBuf); + ptSps->iOffsetForTopToBottomField = parseSe(pvBuf); + ptSps->iPocCycleLength = parseUe(pvBuf); + + if ((unsigned)ptSps->iPocCycleLength >= FF_ARRAY_ELEMS(ptSps->asOffsetForRefFrame)) + { + RPT(RPT_ERR, "iPocCycleLength overflow %d\n", ptSps->iPocCycleLength); + iRet = -1; + goto exit; + + } + + for (i = 0; i < ptSps->iPocCycleLength; i++) + ptSps->asOffsetForRefFrame[i] = parseSe(pvBuf); + } + else if (ptSps->iPocType != 2) + { + RPT(RPT_ERR, "illegal POC type %d\n", ptSps->iPocType); + iRet = -1; + goto exit; + + } + + ptSps->iRefFrameCount = parseUe(pvBuf); + if (ptSps->iRefFrameCount > H264_MAX_PICTURE_COUNT - 2 || + ptSps->iRefFrameCount > 16U) + { + RPT(RPT_ERR, "too many reference frames %d\n", ptSps->iRefFrameCount); + iRet = -1; + goto exit; + + } + ptSps->iGapsInFrameNumAllowedFlag = getOneBit(pvBuf); + ptSps->iMbWidth = parseUe(pvBuf) + 1; + ptSps->iMbHeight = parseUe(pvBuf) + 1; + + + ptSps->iFrameMbsOnlyFlag = getOneBit(pvBuf); + if (!ptSps->iFrameMbsOnlyFlag) + ptSps->iMbAff = getOneBit(pvBuf); + else + ptSps->iMbAff = 0; + + ptSps->iDirect8x8InferenceFlag = getOneBit(pvBuf); + + ptSps->iCrop = getOneBit(pvBuf); + if (ptSps->iCrop) { + unsigned int uiCropLeft = parseUe(pvBuf); + unsigned int uiCropRight = parseUe(pvBuf); + unsigned int uiCropTop = parseUe(pvBuf); + unsigned int uiCropBottom = parseUe(pvBuf); + int iWidth = 16 * ptSps->iMbWidth; + int iHeight = 16 * ptSps->iMbHeight * (2 - ptSps->iFrameMbsOnlyFlag); + + if(1) + { + int vsub = (ptSps->iChromaFormatIdc == 1) ? 1 : 0; + int hsub = (ptSps->iChromaFormatIdc == 1 || + ptSps->iChromaFormatIdc == 2) ? 1 : 0; + int step_x = 1 << hsub; + int step_y = (2 - ptSps->iFrameMbsOnlyFlag) << vsub; + + if (uiCropLeft & (0x1F >> (ptSps->iBitDepthLuma > 8))) + { + uiCropLeft &= ~(0x1F >> (ptSps->iBitDepthLuma > 8)); + } + + if (uiCropLeft > (unsigned)INT_MAX / 4 / step_x || + uiCropRight > (unsigned)INT_MAX / 4 / step_x || + uiCropTop > (unsigned)INT_MAX / 4 / step_y || + uiCropBottom> (unsigned)INT_MAX / 4 / step_y || + (uiCropLeft + uiCropRight ) * step_x >= iWidth || + (uiCropTop + uiCropBottom) * step_y >= iHeight + ) + { + RPT(RPT_ERR, "crop values invalid %d %d %d %d / %d %d\n", uiCropLeft, uiCropRight, uiCropTop, uiCropBottom, iWidth, iHeight); + iRet = -1; + goto exit; + } + + ptSps->uiCropLeft = uiCropLeft * step_x; + ptSps->uiCropRight = uiCropRight * step_x; + ptSps->uiCropTop = uiCropTop * step_y; + ptSps->uiCropBottom = uiCropBottom * step_y; + } + } + else + { + ptSps->uiCropLeft = + ptSps->uiCropRight = + ptSps->uiCropTop = + ptSps->uiCropBottom = + ptSps->iCrop = 0; + } + + ptSps->iVuiParametersPresentFlag = getOneBit(pvBuf); + if (ptSps->iVuiParametersPresentFlag) { + int ret = decodeVuiParameters(pvBuf, ptSps); + if (ret < 0) + goto exit; + } + + if (getBitsLeft(pvBuf) < 0) + { + RPT(RPT_ERR, "Overread %s by %d bits\n", ptSps->iVuiParametersPresentFlag ? "VUI" : "SPS", -getBitsLeft(pvBuf)); + iRet = -1; + } + + if (!ptSps->tSar.den) + ptSps->tSar.den = 1; + + ptSps->iNew = 1; + +exit: +#ifdef SPS_PPS_DEBUG + + if (1) + { + static const char csp[4][5] = { "Gray", "420", "422", "444" }; + RPT(RPT_DBG, + "ptSps:%u profile:%d/%d poc:%d ref:%d %dx%d %s %s crop:%u/%u/%u/%u %s %s %d/%d b%d reo:%d\n", + uiSpsId, ptSps->iProfileIdc, ptSps->iLevelIdc, + ptSps->iPocType, + ptSps->iRefFrameCount, + ptSps->iMbWidth, ptSps->iMbHeight, + ptSps->iFrameMbsOnlyFlag ? "FRM" : (ptSps->iMbAff ? "MB-AFF" : "PIC-AFF"), + ptSps->iDirect8x8InferenceFlag ? "8B8" : "", + ptSps->uiCropLeft, ptSps->uiCropRight, + ptSps->uiCropTop, ptSps->uiCropBottom, + ptSps->iVuiParametersPresentFlag ? "VUI" : "", + csp[ptSps->iChromaFormatIdc], + ptSps->iTimingInfoPresentFlag ? ptSps->u32NumUnitsInTick : 0, + ptSps->iTimingInfoPresentFlag ? ptSps->u32TimeScale : 0, + ptSps->iBitDepthLuma, + ptSps->iBitstreamRestrictionFlag ? ptSps->iNumReorderFrames : -1 + ); + } + +#endif + getBitContextFree(pvBuf); + + return iRet; +} + + +static int decodeProfileTierLevel(T_GetBitContext *pvBuf, T_PTLCommon *tPtl) +{ + int i; + + if (getBitsLeft(pvBuf) < 2+1+5 + 32 + 4 + 16 + 16 + 12) + return -1; + + tPtl->u8ProfileSpace = getBits(pvBuf, 2); + tPtl->u8TierFlag = getOneBit(pvBuf); + tPtl->u8ProfileIdc = getBits(pvBuf, 5); + if (tPtl->u8ProfileIdc == T_PROFILE_HEVC_MAIN) + RPT(RPT_DBG, "Main profile bitstream\n"); + else if (tPtl->u8ProfileIdc == T_PROFILE_HEVC_MAIN_10) + RPT(RPT_DBG, "Main 10 profile bitstream\n"); + else if (tPtl->u8ProfileIdc == T_PROFILE_HEVC_MAIN_STILL_PICTURE) + RPT(RPT_DBG, "Main Still Picture profile bitstream\n"); + else if (tPtl->u8ProfileIdc == T_PROFILE_HEVC_REXT) + RPT(RPT_DBG, "Range Extension profile bitstream\n"); + else + RPT(RPT_WRN, "Unknown HEVC profile: %d\n", tPtl->u8ProfileIdc); + + for (i = 0; i < 32; i++) { + tPtl->au8ProfileCompatibilityFlag[i] = getOneBit(pvBuf); + + if (tPtl->u8ProfileIdc == 0 && i > 0 && tPtl->au8ProfileCompatibilityFlag[i]) + tPtl->u8ProfileIdc = i; + } + tPtl->u8ProgressiveSourceFlag = getOneBit(pvBuf); + tPtl->u8InterlacedSourceFlag = getOneBit(pvBuf); + tPtl->u8NonPackedConstraintFlag = getOneBit(pvBuf); + tPtl->u8FrameOnlyConstraintFlag = getOneBit(pvBuf); + + getBits(pvBuf, 16); // XXX_reserved_zero_44bits[0..15] + getBits(pvBuf, 16); // XXX_reserved_zero_44bits[16..31] + getBits(pvBuf, 12); // XXX_reserved_zero_44bits[32..43] + + return 0; +} + + +static int parsePtl(T_GetBitContext *pvBuf, T_PTL *tPtl, int max_num_sub_layers) +{ + int i; + if (decodeProfileTierLevel(pvBuf, &tPtl->tGeneralPtl) < 0 || + getBitsLeft(pvBuf) < 8 + (8*2 * (max_num_sub_layers - 1 > 0))) { + RPT(RPT_ERR, "PTL information too short\n"); + return -1; + } + + tPtl->tGeneralPtl.u8LevelIdc = getBits(pvBuf, 8); + + for (i = 0; i < max_num_sub_layers - 1; i++) { + tPtl->au8SubLayerProfilePresentFlag[i] = getOneBit(pvBuf); + tPtl->au8SubLayerLevelPresentFlag[i] = getOneBit(pvBuf); + } + + if (max_num_sub_layers - 1> 0) + for (i = max_num_sub_layers - 1; i < 8; i++) + getBits(pvBuf, 2); // reserved_zero_2bits[i] + for (i = 0; i < max_num_sub_layers - 1; i++) { + if (tPtl->au8SubLayerProfilePresentFlag[i] && + decodeProfileTierLevel(pvBuf, &tPtl->atSubLayerPtl[i]) < 0) { + RPT(RPT_ERR, + "PTL information for sublayer %i too short\n", i); + return -1; + } + if (tPtl->au8SubLayerLevelPresentFlag[i]) { + if (getBitsLeft(pvBuf) < 8) { + RPT(RPT_ERR, + "Not enough data for sublayer %i level_idc\n", i); + return -1; + } else + tPtl->atSubLayerPtl[i].u8LevelIdc = getBits(pvBuf, 8); + } + } + + return 0; +} + + +static void setDefaultScalingListData(T_ScalingList *sl) +{ + int matrixId; + + for (matrixId = 0; matrixId < 6; matrixId++) { + // 4x4 default is 16 + memset(sl->aaau8Sl[0][matrixId], 16, 16); + sl->aau8SlDc[0][matrixId] = 16; // default for 16x16 + sl->aau8SlDc[1][matrixId] = 16; // default for 32x32 + } + memcpy(sl->aaau8Sl[1][0], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[1][1], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[1][2], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[1][3], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[1][4], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[1][5], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[2][0], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[2][1], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[2][2], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[2][3], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[2][4], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[2][5], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[3][0], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[3][1], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[3][2], sg_au8DefaultScalingListIntra, 64); + memcpy(sl->aaau8Sl[3][3], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[3][4], sg_au8DefaultScalingListInter, 64); + memcpy(sl->aaau8Sl[3][5], sg_au8DefaultScalingListInter, 64); +} + +static int scalingListData(T_GetBitContext *pvBuf, T_ScalingList *sl, T_HEVCSPS *ptSps) +{ + uint8_t scaling_list_pred_mode_flag; + int32_t scaling_list_dc_coef[2][6]; + int size_id, matrix_id, pos; + int i; + + for (size_id = 0; size_id < 4; size_id++) + for (matrix_id = 0; matrix_id < 6; matrix_id += ((size_id == 3) ? 3 : 1)) { + scaling_list_pred_mode_flag = getOneBit(pvBuf); + if (!scaling_list_pred_mode_flag) { + unsigned int delta = parseUe(pvBuf); + /* Only need to handle non-zero delta. Zero means default, + * which should already be in the arrays. */ + if (delta) { + // Copy from previous array. + delta *= (size_id == 3) ? 3 : 1; + if (matrix_id < delta) { + RPT(RPT_ERR, + "Invalid delta in scaling list data: %d.\n", delta); + return -1; + } + + memcpy(sl->aaau8Sl[size_id][matrix_id], + sl->aaau8Sl[size_id][matrix_id - delta], + size_id > 0 ? 64 : 16); + if (size_id > 1) + sl->aau8SlDc[size_id - 2][matrix_id] = sl->aau8SlDc[size_id - 2][matrix_id - delta]; + } + } else { + int next_coef, coef_num; + int32_t scaling_list_delta_coef; + + next_coef = 8; + coef_num = FFMIN(64, 1 << (4 + (size_id << 1))); + if (size_id > 1) { + scaling_list_dc_coef[size_id - 2][matrix_id] = parseSe(pvBuf) + 8; + next_coef = scaling_list_dc_coef[size_id - 2][matrix_id]; + sl->aau8SlDc[size_id - 2][matrix_id] = next_coef; + } + for (i = 0; i < coef_num; i++) { + if (size_id == 0) + pos = 4 * g_au8HevcDiagScan4x4Y[i] + + g_au8HevcDiagScan4x4X[i]; + else + pos = 8 * g_au8HevcDiagScan8x8Y[i] + + g_au8HevcDiagScan8x8X[i]; + + scaling_list_delta_coef = parseSe(pvBuf); + next_coef = (next_coef + 256U + scaling_list_delta_coef) % 256; + sl->aaau8Sl[size_id][matrix_id][pos] = next_coef; + } + } + } + + if (ptSps->iChromaFormatIdc == 3) { + for (i = 0; i < 64; i++) { + sl->aaau8Sl[3][1][i] = sl->aaau8Sl[2][1][i]; + sl->aaau8Sl[3][2][i] = sl->aaau8Sl[2][2][i]; + sl->aaau8Sl[3][4][i] = sl->aaau8Sl[2][4][i]; + sl->aaau8Sl[3][5][i] = sl->aaau8Sl[2][5][i]; + } + sl->aau8SlDc[1][1] = sl->aau8SlDc[0][1]; + sl->aau8SlDc[1][2] = sl->aau8SlDc[0][2]; + sl->aau8SlDc[1][4] = sl->aau8SlDc[0][4]; + sl->aau8SlDc[1][5] = sl->aau8SlDc[0][5]; + } + + + return 0; +} + +int hevcDecodeShortTermRps(T_GetBitContext *pvBuf, + T_ShortTermRPS *rps, const T_HEVCSPS *ptSps, int is_slice_header) +{ + uint8_t rps_predict = 0; + int au32DeltaPoc; + int k0 = 0; + int k1 = 0; + int k = 0; + int i; + + if (rps != ptSps->atStRps && ptSps->uiNbStRps) + rps_predict = getOneBit(pvBuf); + + if (rps_predict) { + const T_ShortTermRPS *ptRpsRidx; + int iDeltaRps; + unsigned int uiAbsDeltaRps; + uint8_t u8UseDeltaFlag = 0; + uint8_t u8DeltaRpsSign = 0; + + if (is_slice_header) { + unsigned int uiDeltaIdx = parseUe(pvBuf) + 1; + if (u8DeltaRpsSign > ptSps->uiNbStRps) { + RPT(RPT_ERR, + "Invalid value of delta_idx in slice header RPS: %d > %d.\n", + u8DeltaRpsSign, ptSps->uiNbStRps); + return -1; + } + ptRpsRidx = &ptSps->atStRps[ptSps->uiNbStRps - u8DeltaRpsSign]; + rps->iRpsIdxNumDeltaPocs = ptRpsRidx->iNumDeltaPocs; + } else + ptRpsRidx = &ptSps->atStRps[rps - ptSps->atStRps - 1]; + + u8DeltaRpsSign = getOneBit(pvBuf); + uiAbsDeltaRps = parseUe(pvBuf) + 1; + if (uiAbsDeltaRps < 1 || uiAbsDeltaRps > 32768) { + RPT(RPT_ERR, + "Invalid value of uiAbsDeltaRps: %d\n", + uiAbsDeltaRps); + return -1; + } + iDeltaRps = (1 - (u8DeltaRpsSign << 1)) * uiAbsDeltaRps; + for (i = 0; i <= ptRpsRidx->iNumDeltaPocs; i++) { + int used = rps->au8Used[k] = getOneBit(pvBuf); + + if (!used) + u8UseDeltaFlag = getOneBit(pvBuf); + + if (used || u8UseDeltaFlag) { + if (i < ptRpsRidx->iNumDeltaPocs) + au32DeltaPoc = iDeltaRps + ptRpsRidx->au32DeltaPoc[i]; + else + au32DeltaPoc = iDeltaRps; + rps->au32DeltaPoc[k] = au32DeltaPoc; + if (au32DeltaPoc < 0) + k0++; + else + k1++; + k++; + } + } + + if (k >= FF_ARRAY_ELEMS(rps->au8Used)) { + RPT(RPT_ERR, + "Invalid iNumDeltaPocs: %d\n", k); + return -1; + } + + rps->iNumDeltaPocs = k; + rps->uiNumNegativePics = k0; + // sort in increasing order (smallest first) + if (rps->iNumDeltaPocs != 0) { + int used, tmp; + for (i = 1; i < rps->iNumDeltaPocs; i++) { + au32DeltaPoc = rps->au32DeltaPoc[i]; + used = rps->au8Used[i]; + for (k = i - 1; k >= 0; k--) { + tmp = rps->au32DeltaPoc[k]; + if (au32DeltaPoc < tmp) { + rps->au32DeltaPoc[k + 1] = tmp; + rps->au8Used[k + 1] = rps->au8Used[k]; + rps->au32DeltaPoc[k] = au32DeltaPoc; + rps->au8Used[k] = used; + } + } + } + } + if ((rps->uiNumNegativePics >> 1) != 0) { + int used; + k = rps->uiNumNegativePics - 1; + // flip the negative values to largest first + for (i = 0; i < rps->uiNumNegativePics >> 1; i++) { + au32DeltaPoc = rps->au32DeltaPoc[i]; + used = rps->au8Used[i]; + rps->au32DeltaPoc[i] = rps->au32DeltaPoc[k]; + rps->au8Used[i] = rps->au8Used[k]; + rps->au32DeltaPoc[k] = au32DeltaPoc; + rps->au8Used[k] = used; + k--; + } + } + } else { + unsigned int uiPrev, uiNbPositivePics; + rps->uiNumNegativePics = parseUe(pvBuf); + uiNbPositivePics = parseUe(pvBuf); + + if (rps->uiNumNegativePics >= HEVC_MAX_REFS || + uiNbPositivePics >= HEVC_MAX_REFS) { + RPT(RPT_ERR, "Too many refs in a short term RPS.\n"); + return -1; + } + + rps->iNumDeltaPocs = rps->uiNumNegativePics + uiNbPositivePics; + if (rps->iNumDeltaPocs) { + uiPrev = 0; + for (i = 0; i < rps->uiNumNegativePics; i++) { + au32DeltaPoc = parseUe(pvBuf) + 1; + if (au32DeltaPoc < 1 || au32DeltaPoc > 32768) { + RPT(RPT_ERR, + "Invalid value of au32DeltaPoc: %d\n", + au32DeltaPoc); + return -1; + } + uiPrev -= au32DeltaPoc; + rps->au32DeltaPoc[i] = uiPrev; + rps->au8Used[i] = getOneBit(pvBuf); + } + uiPrev = 0; + for (i = 0; i < uiNbPositivePics; i++) { + au32DeltaPoc = parseUe(pvBuf) + 1; + if (au32DeltaPoc < 1 || au32DeltaPoc > 32768) { + RPT(RPT_ERR, + "Invalid value of au32DeltaPoc: %d\n", + au32DeltaPoc); + return -1; + } + uiPrev += au32DeltaPoc; + rps->au32DeltaPoc[rps->uiNumNegativePics + i] = uiPrev; + rps->au8Used[rps->uiNumNegativePics + i] = getOneBit(pvBuf); + } + } + } + return 0; +} + +static void decodeSublayerHrd(T_GetBitContext *pvBuf, unsigned int nb_cpb, + int iSubpicParamsPresent) +{ + int i; + + for (i = 0; i < nb_cpb; i++) { + parseUe(pvBuf); // bit_rate_value_minus1 + parseUe(pvBuf); // cpb_size_value_minus1 + + if (iSubpicParamsPresent) { + parseUe(pvBuf); // cpb_size_du_value_minus1 + parseUe(pvBuf); // bit_rate_du_value_minus1 + } + getOneBit(pvBuf); // cbr_flag + } +} + +static int decodeHrd(T_GetBitContext *pvBuf, int common_inf_present, + int max_sublayers) +{ + int iNalParamsPresent = 0, iVclParamsPresent = 0; + int iSubpicParamsPresent = 0; + int i; + + if (common_inf_present) { + iNalParamsPresent = getOneBit(pvBuf); + iVclParamsPresent = getOneBit(pvBuf); + + if (iNalParamsPresent || iVclParamsPresent) { + iSubpicParamsPresent = getOneBit(pvBuf); + + if (iSubpicParamsPresent) { + getBits(pvBuf, 8); // tick_divisor_minus2 + getBits(pvBuf, 5); // du_cpb_removal_delay_increment_length_minus1 + getBits(pvBuf, 1); // sub_pic_cpb_params_in_pic_timing_sei_flag + getBits(pvBuf, 5); // dpb_output_delay_du_length_minus1 + } + + getBits(pvBuf, 4); // bit_rate_scale + getBits(pvBuf, 4); // cpb_size_scale + + if (iSubpicParamsPresent) + getBits(pvBuf, 4); // cpb_size_du_scale + + getBits(pvBuf, 5); // initial_cpb_removal_delay_length_minus1 + getBits(pvBuf, 5); // au_cpb_removal_delay_length_minus1 + getBits(pvBuf, 5); // dpb_output_delay_length_minus1 + } + } + + for (i = 0; i < max_sublayers; i++) { + int low_delay = 0; + unsigned int nb_cpb = 1; + int iFixedRate = getOneBit(pvBuf); + + if (!iFixedRate) + iFixedRate = getOneBit(pvBuf); + + if (iFixedRate) + parseUe(pvBuf); // elemental_duration_in_tc_minus1 + else + low_delay = getOneBit(pvBuf); + + if (!low_delay) { + nb_cpb = parseUe(pvBuf) + 1; + if (nb_cpb < 1 || nb_cpb > 32) { + RPT(RPT_ERR, "nb_cpb %d invalid\n", nb_cpb); + return -1; + } + } + + if (iNalParamsPresent) + decodeSublayerHrd(pvBuf, nb_cpb, iSubpicParamsPresent); + if (iVclParamsPresent) + decodeSublayerHrd(pvBuf, nb_cpb, iSubpicParamsPresent); + } + return 0; +} + + + +static void decodeVui(T_GetBitContext *pvBuf, T_HEVCSPS *ptSps) +{ + T_VUI tBackupVui, *tVui = &ptSps->tVui; + T_GetBitContext tBackup; + int sar_present, alt = 0; + + RPT(RPT_DBG, "Decoding VUI\n"); + + sar_present = getOneBit(pvBuf); + if (sar_present) { + uint8_t sar_idx = getBits(pvBuf, 8); + if (sar_idx < FF_ARRAY_ELEMS(sg_atVuiSar)) + tVui->tSar = sg_atVuiSar[sar_idx]; + else if (sar_idx == 255) { + tVui->tSar.num = getBits(pvBuf, 16); + tVui->tSar.den = getBits(pvBuf, 16); + } else + RPT(RPT_WRN, + "Unknown SAR index: %u.\n", sar_idx); + } + + tVui->iOverscanInfoPresentFlag = getOneBit(pvBuf); + if (tVui->iOverscanInfoPresentFlag) + tVui->iOverscanAppropriateFlag = getOneBit(pvBuf); + + tVui->iVideoSignalTypePresentFlag = getOneBit(pvBuf); + if (tVui->iVideoSignalTypePresentFlag) { + tVui->iVideoFormat = getBits(pvBuf, 3); + tVui->iVideoFullRangeFlag = getOneBit(pvBuf); + tVui->iColourDescriptionPresentFlag = getOneBit(pvBuf); +// if (tVui->iVideoFullRangeFlag && ptSps->pix_fmt == AV_PIX_FMT_YUV420P) +// ptSps->pix_fmt = AV_PIX_FMT_YUVJ420P; + if (tVui->iColourDescriptionPresentFlag) { + tVui->u8ColourPrimaries = getBits(pvBuf, 8); + tVui->u8TransferCharacteristic = getBits(pvBuf, 8); + tVui->u8MatrixCoeffs = getBits(pvBuf, 8); + } + } + + tVui->iChromaLocInfoPresentFlag = getOneBit(pvBuf); + if (tVui->iChromaLocInfoPresentFlag) { + tVui->iChromaSampleLocTypeTopField = parseUe(pvBuf); + tVui->iChromaSampleLocTypeBottomField = parseUe(pvBuf); + } + + tVui->iNeutraChromaIndicationFlag = getOneBit(pvBuf); + tVui->iFieldSeqFlag = getOneBit(pvBuf); + tVui->iFrameFieldInfoPresentFlag = getOneBit(pvBuf); + + // Backup context in case an alternate header is detected + memcpy(&tBackup, pvBuf, sizeof(tBackup)); + memcpy(&tBackupVui, tVui, sizeof(tBackupVui)); + if (getBitsLeft(pvBuf) >= 68 && showBitsLong(pvBuf, 21) == 0x100000) { + tVui->iDefaultDisplayWindowFlag = 0; + RPT(RPT_WRN, "Invalid default display window\n"); + } else + tVui->iDefaultDisplayWindowFlag = getOneBit(pvBuf); + + if (tVui->iDefaultDisplayWindowFlag) { + int vert_mult = sg_au8HevcSubHeightC[ptSps->iChromaFormatIdc]; + int horiz_mult = sg_au8HevcSubWidthC[ptSps->iChromaFormatIdc]; + tVui->tDefDispWin.uiLeftOffset = parseUe(pvBuf) * horiz_mult; + tVui->tDefDispWin.uiRightOffset = parseUe(pvBuf) * horiz_mult; + tVui->tDefDispWin.uiTopOffset = parseUe(pvBuf) * vert_mult; + tVui->tDefDispWin.uiBottomOffset = parseUe(pvBuf) * vert_mult; + } + +timing_info: + tVui->iVuiTimingInfoPresentFlag = getOneBit(pvBuf); + + if (tVui->iVuiTimingInfoPresentFlag) { + if( getBitsLeft(pvBuf) < 66 && !alt) { + // The alternate syntax seem to have timing info located + // at where tDefDispWin is normally located + RPT(RPT_WRN, + "Strange VUI timing information, retrying...\n"); + memcpy(tVui, &tBackupVui, sizeof(tBackupVui)); + memcpy(pvBuf, &tBackup, sizeof(tBackup)); + alt = 1; + goto timing_info; + } + tVui->u32VuiNumUnitsInTick = getBits(pvBuf, 32); + tVui->u32VuiTimeScale = getBits(pvBuf, 32); + if (alt) { + RPT(RPT_INF, "Retry got %u/%u fps\n", + tVui->u32VuiTimeScale, tVui->u32VuiNumUnitsInTick); + } + tVui->iVuiPocProportionalToTimingFlag = getOneBit(pvBuf); + if (tVui->iVuiPocProportionalToTimingFlag) + tVui->iVuiNumTicksPocDiffOneMinus1 = parseUe(pvBuf); + tVui->iVuiHrdParametersPresentFlag = getOneBit(pvBuf); + if (tVui->iVuiHrdParametersPresentFlag) + decodeHrd(pvBuf, 1, ptSps->iMaxSubLayers); + } + + tVui->iBitstreamRestrictionFlag = getOneBit(pvBuf); + if (tVui->iBitstreamRestrictionFlag) { + if (getBitsLeft(pvBuf) < 8 && !alt) { + RPT(RPT_WRN, + "Strange VUI bitstream restriction information, retrying" + " from timing information...\n"); + memcpy(tVui, &tBackupVui, sizeof(tBackupVui)); + memcpy(pvBuf, &tBackup, sizeof(tBackup)); + alt = 1; + goto timing_info; + } + tVui->iTilesFixedStructureFlag = getOneBit(pvBuf); + tVui->iMotionVectorsOverPicBoundariesFlag = getOneBit(pvBuf); + tVui->iRestrictedRefPicListsFlag = getOneBit(pvBuf); + tVui->iMinSpatialSegmentationIdc = parseUe(pvBuf); + tVui->iMaxBytesPerPicDenom = parseUe(pvBuf); + tVui->iMaxBitsPerMinCuDenom = parseUe(pvBuf); + tVui->iLog2MaxMvLengthHorizontal = parseUe(pvBuf); + tVui->iLog2MaxMvLengthVertical = parseUe(pvBuf); + } + + if (getBitsLeft(pvBuf) < 1 && !alt) { + // XXX: Alternate syntax when iSpsRangeExtensionFlag != 0? + RPT(RPT_WRN, + "Overread in VUI, retrying from timing information...\n"); + memcpy(tVui, &tBackupVui, sizeof(tBackupVui)); + memcpy(pvBuf, &tBackup, sizeof(tBackup)); + alt = 1; + goto timing_info; + } +} + +static unsigned avModUintp2c(unsigned a, unsigned p) +{ + return a & ((1 << p) - 1); +} + + +int h265DecSeqParameterSet( void *pvBufSrc, T_HEVCSPS *ptSps ) +{ + T_HEVCWindow *ow; + int iLog2DiffMaxMinTransformBlockSize; + int iBitDepthChroma, iStart, iVuiPresent, iSublayerOrderingInfo; + int i; + int iRet = 0; + + void *pvBuf = NULL; + if(NULL == pvBufSrc || NULL == ptSps) + { + RPT(RPT_ERR,"ERR null pointer\n"); + iRet = -1; + goto exit; + } + + memset((void *)ptSps, 0, sizeof(T_HEVCSPS)); + + pvBuf = deEmulationPrevention(pvBufSrc); + if(NULL == pvBuf) + { + RPT(RPT_ERR,"ERR null pointer\n"); + iRet = -1; + goto exit; + } + + // Coded parameters + + ptSps->uiVpsId = getBits(pvBuf, 4); + if (ptSps->uiVpsId >= HEVC_MAX_VPS_COUNT) { + RPT(RPT_ERR, "VPS id out of range: %d\n", ptSps->uiVpsId); + iRet = -1; + goto exit; + } + + ptSps->iMaxSubLayers = getBits(pvBuf, 3) + 1; + if (ptSps->iMaxSubLayers > HEVC_MAX_SUB_LAYERS) { + RPT(RPT_ERR, "sps_max_sub_layers out of range: %d\n", + ptSps->iMaxSubLayers); + iRet = -1; + goto exit; + } + + ptSps->u8temporalIdNestingFlag = getBits(pvBuf, 1); + if ((iRet = parsePtl(pvBuf, &ptSps->tPtl, ptSps->iMaxSubLayers)) < 0) + goto exit; + + int sps_id = parseUe(pvBuf); + if (sps_id >= HEVC_MAX_SPS_COUNT) { + RPT(RPT_ERR, "SPS id out of range: %d\n", sps_id); + iRet = -1; + goto exit; + } + + ptSps->iChromaFormatIdc = parseUe(pvBuf); + if (ptSps->iChromaFormatIdc > 3U) { + RPT(RPT_ERR, "iChromaFormatIdc %d is invalid\n", ptSps->iChromaFormatIdc); + iRet = -1; + goto exit; + } + + if (ptSps->iChromaFormatIdc == 3) + ptSps->u8SeparateColourPlaneFlag = getOneBit(pvBuf); + + if (ptSps->u8SeparateColourPlaneFlag) + ptSps->iChromaFormatIdc = 0; + + ptSps->iWidth = parseUe(pvBuf); + ptSps->iHeight = parseUe(pvBuf); + + if (getOneBit(pvBuf)) { // pic_conformance_flag + int vert_mult = sg_au8HevcSubHeightC[ptSps->iChromaFormatIdc]; + int horiz_mult = sg_au8HevcSubWidthC[ptSps->iChromaFormatIdc]; + ptSps->tPicConfWin.uiLeftOffset = parseUe(pvBuf) * horiz_mult; + ptSps->tPicConfWin.uiRightOffset = parseUe(pvBuf) * horiz_mult; + ptSps->tPicConfWin.uiTopOffset = parseUe(pvBuf) * vert_mult; + ptSps->tPicConfWin.uiBottomOffset = parseUe(pvBuf) * vert_mult; + + ptSps->tOutputWindow = ptSps->tPicConfWin; + } + + ptSps->iBitDepth = parseUe(pvBuf) + 8; + iBitDepthChroma = parseUe(pvBuf) + 8; + + if (ptSps->iChromaFormatIdc && iBitDepthChroma != ptSps->iBitDepth) { + RPT(RPT_ERR, + "Luma bit depth (%d) is different from chroma bit depth (%d), " + "this is unsupported.\n", + ptSps->iBitDepth, iBitDepthChroma); + iRet = -1; + goto exit; + } + ptSps->iBitDepthChroma = iBitDepthChroma; + + ptSps->uiLog2MaxPocLsb = parseUe(pvBuf) + 4; + if (ptSps->uiLog2MaxPocLsb > 16) { + RPT(RPT_ERR, "log2_max_pic_order_cnt_lsb_minus4 out range: %d\n", + ptSps->uiLog2MaxPocLsb - 4); + iRet = -1; + goto exit; + } + + iSublayerOrderingInfo = getOneBit(pvBuf); + iStart = iSublayerOrderingInfo ? 0 : ptSps->iMaxSubLayers - 1; + for (i = iStart; i < ptSps->iMaxSubLayers; i++) { + ptSps->stTemporalLayer[i].iMaxDecPicBuffering = parseUe(pvBuf) + 1; + ptSps->stTemporalLayer[i].iNumReorderPics = parseUe(pvBuf); + ptSps->stTemporalLayer[i].iMaxLatencyIncrease = parseUe(pvBuf) - 1; + if (ptSps->stTemporalLayer[i].iMaxDecPicBuffering > (unsigned)HEVC_MAX_DPB_SIZE) { + RPT(RPT_ERR, "sps_max_dec_pic_buffering_minus1 out of range: %d\n", + ptSps->stTemporalLayer[i].iMaxDecPicBuffering - 1U); + iRet = -1; + goto exit; + } + if (ptSps->stTemporalLayer[i].iNumReorderPics > ptSps->stTemporalLayer[i].iMaxDecPicBuffering - 1) { + RPT(RPT_WRN, "sps_max_num_reorder_pics out of range: %d\n", + ptSps->stTemporalLayer[i].iNumReorderPics); + if (ptSps->stTemporalLayer[i].iNumReorderPics > HEVC_MAX_DPB_SIZE - 1) { + iRet = -1; + goto exit; + } + ptSps->stTemporalLayer[i].iMaxDecPicBuffering = ptSps->stTemporalLayer[i].iNumReorderPics + 1; + } + } + + if (!iSublayerOrderingInfo) { + for (i = 0; i < iStart; i++) { + ptSps->stTemporalLayer[i].iMaxDecPicBuffering = ptSps->stTemporalLayer[iStart].iMaxDecPicBuffering; + ptSps->stTemporalLayer[i].iNumReorderPics = ptSps->stTemporalLayer[iStart].iNumReorderPics; + ptSps->stTemporalLayer[i].iMaxLatencyIncrease = ptSps->stTemporalLayer[iStart].iMaxLatencyIncrease; + } + } + + ptSps->uiLog2MinCbSize = parseUe(pvBuf) + 3; + ptSps->uiLog2DiffMaxMinCodingBlockSize = parseUe(pvBuf); + ptSps->uiLog2MinTbSize = parseUe(pvBuf) + 2; + iLog2DiffMaxMinTransformBlockSize = parseUe(pvBuf); + ptSps->uiLog2MaxTrafoSize = iLog2DiffMaxMinTransformBlockSize + + ptSps->uiLog2MinTbSize; + + if (ptSps->uiLog2MinCbSize < 3 || ptSps->uiLog2MinCbSize > 30) { + RPT(RPT_ERR, "Invalid value %d for uiLog2MinCbSize", ptSps->uiLog2MinCbSize); + iRet = -1; + goto exit; + } + + if (ptSps->uiLog2DiffMaxMinCodingBlockSize > 30) { + RPT(RPT_ERR, "Invalid value %d for uiLog2DiffMaxMinCodingBlockSize", ptSps->uiLog2DiffMaxMinCodingBlockSize); + iRet = -1; + goto exit; + } + + if (ptSps->uiLog2MinTbSize >= ptSps->uiLog2MinCbSize || ptSps->uiLog2MinTbSize < 2) { + RPT(RPT_ERR, "Invalid value for uiLog2MinTbSize"); + iRet = -1; + goto exit; + } + + if (iLog2DiffMaxMinTransformBlockSize < 0 || iLog2DiffMaxMinTransformBlockSize > 30) { + RPT(RPT_ERR, "Invalid value %d for iLog2DiffMaxMinTransformBlockSize", iLog2DiffMaxMinTransformBlockSize); + iRet = -1; + goto exit; + } + + ptSps->iMaxTransformHierarchyDepthInter = parseUe(pvBuf); + ptSps->iMaxTransformHierarchyDepthIntra = parseUe(pvBuf); + + ptSps->u8ScalingListEnableFlag = getOneBit(pvBuf); + + if (ptSps->u8ScalingListEnableFlag) { + setDefaultScalingListData(&ptSps->tScalingList); + + if (getOneBit(pvBuf)) { + iRet = scalingListData(pvBuf, &ptSps->tScalingList, ptSps); + if (iRet < 0) + goto exit; + } + } + + ptSps->u8AmpEnabledFlag = getOneBit(pvBuf); + ptSps->u8SaoEnabled = getOneBit(pvBuf); + + ptSps->iPcmEnabledFlag = getOneBit(pvBuf); + + if (ptSps->iPcmEnabledFlag) { + ptSps->pcm.u8BitDepth = getBits(pvBuf, 4) + 1; + ptSps->pcm.u8BitDepthChroma = getBits(pvBuf, 4) + 1; + ptSps->pcm.uiLog2MinPcmCbSize = parseUe(pvBuf) + 3; + ptSps->pcm.uiLog2MaxPcmCbSize = ptSps->pcm.uiLog2MinPcmCbSize + + parseUe(pvBuf); + if (FFMAX(ptSps->pcm.u8BitDepth, ptSps->pcm.u8BitDepthChroma) > ptSps->iBitDepth) { + RPT(RPT_ERR, + "PCM bit depth (%d, %d) is greater than normal bit depth (%d)\n", + ptSps->pcm.u8BitDepth, ptSps->pcm.u8BitDepthChroma, ptSps->iBitDepth); + iRet = -1; + goto exit; + } + + ptSps->pcm.u8LoopFilterDisableFlag = getOneBit(pvBuf); + } + + ptSps->uiNbStRps = parseUe(pvBuf); + if (ptSps->uiNbStRps > HEVC_MAX_SHORT_TERM_REF_PIC_SETS) { + RPT(RPT_ERR, "Too many short term RPS: %d.\n", + ptSps->uiNbStRps); + iRet = -1; + goto exit; + } + for (i = 0; i < ptSps->uiNbStRps; i++) { + if ((iRet = hevcDecodeShortTermRps(pvBuf, &ptSps->atStRps[i], + ptSps, 0)) < 0) + goto exit; + } + + ptSps->u8LongTermRefPicsPresentFlag = getOneBit(pvBuf); + if (ptSps->u8LongTermRefPicsPresentFlag) { + ptSps->u8NumLongTermRefPicsSps = parseUe(pvBuf); + if (ptSps->u8NumLongTermRefPicsSps > HEVC_MAX_LONG_TERM_REF_PICS) { + RPT(RPT_ERR, "Too many long term ref pics: %d.\n", + ptSps->u8NumLongTermRefPicsSps); + iRet = -1; + goto exit; + } + for (i = 0; i < ptSps->u8NumLongTermRefPicsSps; i++) { + ptSps->au16LtRefPicPocLsbSps[i] = getBits(pvBuf, ptSps->uiLog2MaxPocLsb); + ptSps->au8UsedByCurrPicLtSpsFlag[i] = getOneBit(pvBuf); + } + } + + ptSps->u8SpsTemporalMvpEnabledFlag = getOneBit(pvBuf); + ptSps->u8SpsStrongIntraMmoothingEnableFlag = getOneBit(pvBuf); + ptSps->tVui.tSar = (T_AVRational){0, 1}; + ptSps->iVuiPresent = getOneBit(pvBuf); + if (ptSps->iVuiPresent) + decodeVui(pvBuf, ptSps); + + + if (getOneBit(pvBuf)) { // sps_extension_flag + int iSpsRangeExtensionFlag = getOneBit(pvBuf); + getBits(pvBuf, 7); //sps_extension_7bits = getBits(pvBuf, 7); + if (iSpsRangeExtensionFlag) { + int iExtendedPrecisionProcessingFlag; + int iCabacBypassAlignmentEnabledFlag; + + ptSps->iTransformSkipRotationEnabledFlag = getOneBit(pvBuf); + ptSps->iTransformSkipContextEnabledFlag = getOneBit(pvBuf); + ptSps->iImplicitRdpcmEnabledFlag = getOneBit(pvBuf); + + ptSps->iExplicitRdpcmEnabledFlag = getOneBit(pvBuf); + + iExtendedPrecisionProcessingFlag = getOneBit(pvBuf); + if (iExtendedPrecisionProcessingFlag) + RPT(RPT_WRN, + "iExtendedPrecisionProcessingFlag not yet implemented\n"); + + ptSps->iIntraSmoothingDisabledFlag = getOneBit(pvBuf); + ptSps->iHighPrecisionOffsetsEnabledFlag = getOneBit(pvBuf); + if (ptSps->iHighPrecisionOffsetsEnabledFlag) + RPT(RPT_WRN, + "iHighPrecisionOffsetsEnabledFlag not yet implemented\n"); + + ptSps->iPersistentRiceAdaptationEnabledFlag = getOneBit(pvBuf); + + iCabacBypassAlignmentEnabledFlag = getOneBit(pvBuf); + if (iCabacBypassAlignmentEnabledFlag) + RPT(RPT_WRN, + "iCabacBypassAlignmentEnabledFlag not yet implemented\n"); + } + } + + ow = &ptSps->tOutputWindow; + if (ow->uiLeftOffset >= INT_MAX - ow->uiRightOffset || + ow->uiTopOffset >= INT_MAX - ow->uiBottomOffset || + ow->uiLeftOffset + ow->uiRightOffset >= ptSps->iWidth || + ow->uiTopOffset + ow->uiBottomOffset >= ptSps->iHeight) { + RPT(RPT_WRN, "Invalid cropping offsets: %u/%u/%u/%u\n", + ow->uiLeftOffset, ow->uiRightOffset, ow->uiTopOffset, ow->uiBottomOffset); + RPT(RPT_WRN, + "Displaying the whole video surface.\n"); + memset(ow, 0, sizeof(*ow)); + memset(&ptSps->tPicConfWin, 0, sizeof(ptSps->tPicConfWin)); + } + + // Inferred parameters + ptSps->uiLog2CtbSize = ptSps->uiLog2MinCbSize + + ptSps->uiLog2DiffMaxMinCodingBlockSize; + ptSps->uiLog2MinPuSize = ptSps->uiLog2MinCbSize - 1; + + if (ptSps->uiLog2CtbSize > HEVC_MAX_LOG2_CTB_SIZE) { + RPT(RPT_ERR, "CTB size out of range: 2^%d\n", ptSps->uiLog2CtbSize); + iRet = -1; + goto exit; + } + if (ptSps->uiLog2CtbSize < 4) { + RPT(RPT_ERR, + "uiLog2CtbSize %d differs from the bounds of any known profile\n", + ptSps->uiLog2CtbSize); + iRet = -1; + goto exit; + } + + ptSps->iCtbWidth = (ptSps->iWidth + (1 << ptSps->uiLog2CtbSize) - 1) >> ptSps->uiLog2CtbSize; + ptSps->iCtbHeight = (ptSps->iHeight + (1 << ptSps->uiLog2CtbSize) - 1) >> ptSps->uiLog2CtbSize; + ptSps->iCtbSize = ptSps->iCtbWidth * ptSps->iCtbHeight; + + ptSps->iMinCbWidth = ptSps->iWidth >> ptSps->uiLog2MinCbSize; + ptSps->iMinCbHeight = ptSps->iHeight >> ptSps->uiLog2MinCbSize; + ptSps->iMinTbWidth = ptSps->iWidth >> ptSps->uiLog2MinTbSize; + ptSps->iMinTbHeight = ptSps->iHeight >> ptSps->uiLog2MinTbSize; + ptSps->iMinPuWidth = ptSps->iWidth >> ptSps->uiLog2MinPuSize; + ptSps->iMinPuHeight = ptSps->iHeight >> ptSps->uiLog2MinPuSize; + ptSps->iTbMask = (1 << (ptSps->uiLog2CtbSize - ptSps->uiLog2MinTbSize)) - 1; + + ptSps->iQpBdOffset = 6 * (ptSps->iBitDepth - 8); + + if (avModUintp2c(ptSps->iWidth, ptSps->uiLog2MinCbSize) || + avModUintp2c(ptSps->iHeight, ptSps->uiLog2MinCbSize)) { + RPT(RPT_ERR, "Invalid coded frame dimensions.\n"); + iRet = -1; + goto exit; + } + + if (ptSps->iMaxTransformHierarchyDepthInter > ptSps->uiLog2CtbSize - ptSps->uiLog2MinTbSize) { + RPT(RPT_ERR, "iMaxTransformHierarchyDepthInter out of range: %d\n", + ptSps->iMaxTransformHierarchyDepthInter); + iRet = -1; + goto exit; + } + if (ptSps->iMaxTransformHierarchyDepthIntra > ptSps->uiLog2CtbSize - ptSps->uiLog2MinTbSize) { + RPT(RPT_ERR, "iMaxTransformHierarchyDepthIntra out of range: %d\n", + ptSps->iMaxTransformHierarchyDepthIntra); + iRet = -1; + goto exit; + } + if (ptSps->uiLog2MaxTrafoSize > FFMIN(ptSps->uiLog2CtbSize, 5)) { + RPT(RPT_ERR, + "max transform block size out of range: %d\n", + ptSps->uiLog2MaxTrafoSize); + iRet = -1; + goto exit; + } + + if (getBitsLeft(pvBuf) < 0) { + RPT(RPT_ERR, + "Overread SPS by %d bits\n", -getBitsLeft(pvBuf)); + iRet = -1; + goto exit; + } + + +exit: + + getBitContextFree(pvBuf); + return iRet; + +} + + +int h265DecVideoParameterSet( void *pvBufSrc, T_HEVCVPS *ptVps ) +{ + int iRet = 0; + int i,j; + int uiVpsId = 0; + + void *pvBuf = NULL; + if(NULL == pvBufSrc || NULL == ptVps) + { + RPT(RPT_ERR,"ERR null pointer\n"); + iRet = -1; + goto exit; + } + + memset((void *)ptVps, 0, sizeof(T_HEVCVPS)); + + pvBuf = deEmulationPrevention(pvBufSrc); + if(NULL == pvBuf) + { + RPT(RPT_ERR,"ERR null pointer\n"); + iRet = -1; + goto exit; + } + + RPT(RPT_DBG, "Decoding VPS\n"); + + uiVpsId = getBits(pvBuf, 4); + if (uiVpsId >= HEVC_MAX_VPS_COUNT) { + RPT(RPT_ERR, "VPS id out of range: %d\n", uiVpsId); + iRet = -1; + goto exit; + } + + if (getBits(pvBuf, 2) != 3) { // vps_reserved_three_2bits + RPT(RPT_ERR, "vps_reserved_three_2bits is not three\n"); + iRet = -1; + goto exit; + } + + ptVps->iVpsMaxLayers = getBits(pvBuf, 6) + 1; + ptVps->iVpsMaxSubLayers = getBits(pvBuf, 3) + 1; + ptVps->u8VpsTemporalIdNestingFlag = getOneBit(pvBuf); + + if (getBits(pvBuf, 16) != 0xffff) { // vps_reserved_ffff_16bits + RPT(RPT_ERR, "vps_reserved_ffff_16bits is not 0xffff\n"); + iRet = -1; + goto exit; + } + + if (ptVps->iVpsMaxSubLayers > HEVC_MAX_SUB_LAYERS) { + RPT(RPT_ERR, "iVpsMaxSubLayers out of range: %d\n", + ptVps->iVpsMaxSubLayers); + iRet = -1; + goto exit; + } + + if (parsePtl(pvBuf, &ptVps->tPtl, ptVps->iVpsMaxSubLayers) < 0){ + iRet = -1; + goto exit; + } + + ptVps->iVpsSubLayerOrderingInfoPresentFlag = getOneBit(pvBuf); + + i = ptVps->iVpsSubLayerOrderingInfoPresentFlag ? 0 : ptVps->iVpsMaxSubLayers - 1; + for (; i < ptVps->iVpsMaxSubLayers; i++) { + ptVps->uiVpsMaxDecPicBuffering[i] = parseUe(pvBuf) + 1; + ptVps->auiVpsNumReorderPics[i] = parseUe(pvBuf); + ptVps->auiVpsMaxLatencyIncrease[i] = parseUe(pvBuf) - 1; + + if (ptVps->uiVpsMaxDecPicBuffering[i] > HEVC_MAX_DPB_SIZE || !ptVps->uiVpsMaxDecPicBuffering[i]) { + RPT(RPT_ERR, "vps_max_dec_pic_buffering_minus1 out of range: %d\n", + ptVps->uiVpsMaxDecPicBuffering[i] - 1); + iRet = -1; + goto exit; + } + if (ptVps->auiVpsNumReorderPics[i] > ptVps->uiVpsMaxDecPicBuffering[i] - 1) { + RPT(RPT_WRN, "vps_max_num_reorder_pics out of range: %d\n", + ptVps->auiVpsNumReorderPics[i]); + } + } + + ptVps->iVpsMaxLayerId = getBits(pvBuf, 6); + ptVps->iVpsNumLayerSets = parseUe(pvBuf) + 1; + if (ptVps->iVpsNumLayerSets < 1 || ptVps->iVpsNumLayerSets > 1024 || + (ptVps->iVpsNumLayerSets - 1LL) * (ptVps->iVpsMaxLayerId + 1LL) > getBitsLeft(pvBuf)) { + RPT(RPT_ERR, "too many layer_id_included_flags\n"); + iRet = -1; + goto exit; + } + + for (i = 1; i < ptVps->iVpsNumLayerSets; i++) + for (j = 0; j <= ptVps->iVpsMaxLayerId; j++) + getBits(pvBuf, 1); // layer_id_included_flag[i][j] + + ptVps->u8VpsTimingInfoPresentFlag = getOneBit(pvBuf); + if (ptVps->u8VpsTimingInfoPresentFlag) { + ptVps->u32VpsNumUnitsInTick = getBits(pvBuf, 32); + ptVps->u32VpsTimeScale = getBits(pvBuf, 32); + ptVps->u8VpsPocProportionalToTimingFlag = getOneBit(pvBuf); + if (ptVps->u8VpsPocProportionalToTimingFlag) + ptVps->iVpsNumTicksPocDiffOne = parseUe(pvBuf) + 1; + ptVps->iVpsNumHrdParameters = parseUe(pvBuf); + if (ptVps->iVpsNumHrdParameters > (unsigned)ptVps->iVpsNumLayerSets) { + RPT(RPT_ERR, + "iVpsNumHrdParameters %d is invalid\n", ptVps->iVpsNumHrdParameters); + iRet = -1; + goto exit; + } + for (i = 0; i < ptVps->iVpsNumHrdParameters; i++) { + int common_inf_present = 1; + + parseUe(pvBuf); // hrd_layer_set_idx + if (i) + common_inf_present = getOneBit(pvBuf); + decodeHrd(pvBuf, common_inf_present, ptVps->iVpsMaxSubLayers); + } + } + getOneBit(pvBuf); /* vps_extension_flag */ + + if (getBitsLeft(pvBuf) < 0) { + RPT(RPT_ERR, + "Overread VPS by %d bits\n", -getBitsLeft(pvBuf)); + + iRet = -1; + goto exit; + } + + +exit: + + getBitContextFree(pvBuf); + return iRet; + +} + +void h264GetWidthHeight(T_SPS *ptSps, int *piWidth, int *piHeight) +{ + // ¿í¸ß¼ÆË㹫ʽ [AUTO-TRANSLATED:e2d61727] + // What is the frame rate + int iCodeWidth = 0; + int iCodedHeight = 0; + iCodeWidth = 16 * ptSps->iMbWidth; + iCodedHeight = 16 * ptSps->iMbHeight; + *piWidth = iCodeWidth - (ptSps->uiCropRight + ptSps->uiCropLeft); + *piHeight = iCodedHeight - (ptSps->uiCropTop + ptSps->uiCropBottom); + if (*piWidth <= 0 || *piHeight <= 0) { + *piWidth = iCodeWidth; + *piHeight = iCodedHeight; + } + + RPT(RPT_DBG, "iCodeWidth:%d, iCodedHeight:%d\n", iCodeWidth, iCodedHeight); + + RPT(RPT_DBG, "*piWidth:%d, *piHeight:%d\n", *piWidth, *piHeight); + + RPT(RPT_DBG, "ptSps->uiCropRight:%d, ptSps->uiCropLeft:%d\n", ptSps->uiCropRight, ptSps->uiCropLeft); + + RPT(RPT_DBG, "ptSps->uiCropTop:%d, ptSps->uiCropBottom:%d\n", ptSps->uiCropTop, ptSps->uiCropBottom); + +} + +int h264GetFormat(T_SPS *ptSps) +{ + return ptSps->iFrameMbsOnlyFlag; +} + + +void h264GeFramerate(T_SPS *ptSps, float *pfFramerate) +{ + + if(ptSps->iTimingInfoPresentFlag) + { + *pfFramerate = (float)ptSps->u32TimeScale / (float)ptSps->u32NumUnitsInTick / 2.0; + }else{ + *pfFramerate = 0; + } + switch((int)*pfFramerate) + { + case 23:// 23.98 + RPT(RPT_DBG, "frame rate:23.98"); + break; + case 24: + RPT(RPT_DBG, "frame rate:24"); + break; + case 25: + RPT(RPT_DBG, "frame rate:25"); + break; + case 29://29.97 + RPT(RPT_DBG, "frame rate:29.97"); + break; + case 30: + RPT(RPT_DBG, "frame rate:30"); + break; + case 50: + RPT(RPT_DBG, "frame rate:50"); + break; + case 59://59.94 + RPT(RPT_DBG, "frame rate:59.94"); + break; + case 60: + RPT(RPT_DBG, "frame rate:60"); + break; + case 6: + RPT(RPT_DBG, "frame rate:6"); + break; + case 8: + RPT(RPT_DBG, "frame rate:8"); + break; + case 12: + RPT(RPT_DBG, "frame rate:12"); + break; + case 15: + RPT(RPT_DBG, "frame rate:15"); + break; + case 10: + RPT(RPT_DBG, "frame rate:10"); + break; + + default: + RPT(RPT_DBG, "frame rate:0"); + break; + } + + return; +} + + + + +void h265GetWidthHeight(T_HEVCSPS *ptSps, int *piWidth, int *piHeight) +{ +#if 1 + int iCodeWidth = 0; + int iCodedHeight = 0; + iCodeWidth = ptSps->iWidth; + iCodedHeight = ptSps->iHeight; + *piWidth = ptSps->iWidth - ptSps->tOutputWindow.uiLeftOffset - ptSps->tOutputWindow.uiRightOffset; + *piHeight = ptSps->iHeight - ptSps->tOutputWindow.uiTopOffset - ptSps->tOutputWindow.uiBottomOffset; + + + RPT(RPT_DBG, "iCodeWidth:%d, iCodedHeight:%d\n", iCodeWidth, iCodedHeight); + + RPT(RPT_DBG, "*piWidth:%d, *piHeight:%d\n", *piWidth, *piHeight); + + RPT(RPT_DBG, "ptSps->tOutputWindow.uiRightOffset:%d, ptSps->tOutputWindow.uiLeftOffset:%d\n", ptSps->tOutputWindow.uiRightOffset, ptSps->tOutputWindow.uiLeftOffset); + + RPT(RPT_DBG, "ptSps->tOutputWindow.uiTopOffset:%d, ptSps->tOutputWindow.uiBottomOffset:%d\n", ptSps->tOutputWindow.uiTopOffset, ptSps->tOutputWindow.uiBottomOffset); +#endif + +} + + + +void h265GeFramerate(T_HEVCVPS *ptVps, T_HEVCSPS *ptSps,float *pfFramerate) +{ + if (ptVps && ptVps->u8VpsTimingInfoPresentFlag) { + *pfFramerate = (float)(ptVps->u32VpsTimeScale) / (float)(ptVps->u32VpsNumUnitsInTick); + + } else if (ptSps && ptSps->tVui.iVuiTimingInfoPresentFlag && ptSps->iVuiPresent) { + *pfFramerate = (float)(ptSps->tVui.u32VuiTimeScale) / (float)(ptSps->tVui.u32VuiNumUnitsInTick); + } + else{ + // vps sps可能不包含帧率 [AUTO-TRANSLATED:15424320] + // vps sps may not contain frame rate + *pfFramerate = 0.0F; + RPT(RPT_WRN, "frame rate:0"); + } +} + +int h265ParsePps(T_GetBitContext *ptGetBitContext, T_HEVC_PPS *ptPps) +{ + int iRet = 0; + + ptPps->pps_pic_parameter_set_id = parseUe(ptGetBitContext); + ptPps->pps_seq_parameter_set_id = parseUe(ptGetBitContext); + ptPps->dependent_slice_segments_enabled_flag = getOneBit(ptGetBitContext); + ptPps->output_flag_present_flag = getOneBit(ptGetBitContext); + ptPps->num_extra_slice_header_bits = getBits(ptGetBitContext, 3); + ptPps->sign_data_hiding_enabled_flag = getOneBit(ptGetBitContext); + ptPps->cabac_init_present_flag = getOneBit(ptGetBitContext); + + ptPps->num_ref_idx_l0_default_active_minus1 = parseUe(ptGetBitContext); + ptPps->num_ref_idx_l1_default_active_minus1 = parseUe(ptGetBitContext); + ptPps->init_qp_minus26 = parseSe(ptGetBitContext); + + ptPps->constrained_intra_pred_flag = getOneBit(ptGetBitContext); + ptPps->transform_skip_enabled_flag = getOneBit(ptGetBitContext); + ptPps->cu_qp_delta_enabled_flag = getOneBit(ptGetBitContext); + + if (ptPps->cu_qp_delta_enabled_flag) { + ptPps->diff_cu_qp_delta_depth = parseUe(ptGetBitContext); + } + ptPps->pps_cb_qp_offset = parseSe(ptGetBitContext); + ptPps->pps_cr_qp_offset = parseSe(ptGetBitContext); + ptPps->pps_slice_chroma_qp_offsets_present_flag = getOneBit(ptGetBitContext); + ptPps->weighted_pred_flag = getOneBit(ptGetBitContext); + ptPps->weighted_bipred_flag = getOneBit(ptGetBitContext); + ptPps->transquant_bypass_enabled_flag = getOneBit(ptGetBitContext); + ptPps->tiles_enabled_flag = getOneBit(ptGetBitContext); + ptPps->entropy_coding_sync_enabled_flag = getOneBit(ptGetBitContext); + return iRet; +} \ No newline at end of file diff --git a/MediaServer/ext-codec/SPSParser.h b/MediaServer/ext-codec/SPSParser.h new file mode 100644 index 0000000..1671d5c --- /dev/null +++ b/MediaServer/ext-codec/SPSParser.h @@ -0,0 +1,526 @@ +#ifndef _SPSPARSER_H_ +#define _SPSPARSER_H_ + +#if defined (__cplusplus) + extern "C" { +#endif + +#define QP_MAX_NUM (51 + 6*6) // The maximum supported qp + +#define HEVC_MAX_SHORT_TERM_RPS_COUNT 64 + +#define T_PROFILE_HEVC_MAIN 1 +#define T_PROFILE_HEVC_MAIN_10 2 +#define T_PROFILE_HEVC_MAIN_STILL_PICTURE 3 +#define T_PROFILE_HEVC_REXT 4 + + + +/** + * Chromaticity coordinates of the source primaries. + */ +enum T_AVColorPrimaries { + AVCOL_PRI_RESERVED0 = 0, + AVCOL_PRI_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 / SMPTE RP177 Annex B + AVCOL_PRI_UNSPECIFIED = 2, + AVCOL_PRI_RESERVED = 3, + AVCOL_PRI_BT470M = 4, ///< also FCC Title 47 Code of Federal Regulations 73.682 (a)(20) + + AVCOL_PRI_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM + AVCOL_PRI_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC + AVCOL_PRI_SMPTE240M = 7, ///< functionally identical to above + AVCOL_PRI_FILM = 8, ///< colour filters using Illuminant C + AVCOL_PRI_BT2020 = 9, ///< ITU-R BT2020 + AVCOL_PRI_NB, ///< Not part of ABI +}; + +/** + * Color Transfer Characteristic. + */ +enum T_AVColorTransferCharacteristic { + AVCOL_TRC_RESERVED0 = 0, + AVCOL_TRC_BT709 = 1, ///< also ITU-R BT1361 + AVCOL_TRC_UNSPECIFIED = 2, + AVCOL_TRC_RESERVED = 3, + AVCOL_TRC_GAMMA22 = 4, ///< also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM + AVCOL_TRC_GAMMA28 = 5, ///< also ITU-R BT470BG + AVCOL_TRC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 or 625 / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC + AVCOL_TRC_SMPTE240M = 7, + AVCOL_TRC_LINEAR = 8, ///< "Linear transfer characteristics" + AVCOL_TRC_LOG = 9, ///< "Logarithmic transfer characteristic (100:1 range)" + AVCOL_TRC_LOG_SQRT = 10, ///< "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)" + AVCOL_TRC_IEC61966_2_4 = 11, ///< IEC 61966-2-4 + AVCOL_TRC_BT1361_ECG = 12, ///< ITU-R BT1361 Extended Colour Gamut + AVCOL_TRC_IEC61966_2_1 = 13, ///< IEC 61966-2-1 (sRGB or sYCC) + AVCOL_TRC_BT2020_10 = 14, ///< ITU-R BT2020 for 10 bit system + AVCOL_TRC_BT2020_12 = 15, ///< ITU-R BT2020 for 12 bit system + AVCOL_TRC_NB, ///< Not part of ABI +}; + +/** + * YUV tColorspace type. + */ +enum T_AVColorSpace { + AVCOL_SPC_RGB = 0, ///< order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB) + AVCOL_SPC_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / SMPTE RP177 Annex B + AVCOL_SPC_UNSPECIFIED = 2, + AVCOL_SPC_RESERVED = 3, + AVCOL_SPC_FCC = 4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20) + AVCOL_SPC_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601 + AVCOL_SPC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC / functionally identical to above + AVCOL_SPC_SMPTE240M = 7, + AVCOL_SPC_YCOCG = 8, ///< Used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16 + AVCOL_SPC_BT2020_NCL = 9, ///< ITU-R BT2020 non-constant luminance system + AVCOL_SPC_BT2020_CL = 10, ///< ITU-R BT2020 constant luminance system + AVCOL_SPC_NB, ///< Not part of ABI +}; + + +enum { + // 7.4.3.1: vps_max_layers_minus1 is in [0, 62]. + HEVC_MAX_LAYERS = 63, + // 7.4.3.1: vps_max_sub_layers_minus1 is in [0, 6]. + HEVC_MAX_SUB_LAYERS = 7, + // 7.4.3.1: vps_num_layer_sets_minus1 is in [0, 1023]. + HEVC_MAX_LAYER_SETS = 1024, + + // 7.4.2.1: vps_video_parameter_set_id is u(4). + HEVC_MAX_VPS_COUNT = 16, + // 7.4.3.2.1: sps_seq_parameter_set_id is in [0, 15]. + HEVC_MAX_SPS_COUNT = 16, + // 7.4.3.3.1: pps_pic_parameter_set_id is in [0, 63]. + HEVC_MAX_PPS_COUNT = 64, + + // A.4.2: MaxDpbSize is bounded above by 16. + HEVC_MAX_DPB_SIZE = 16, + // 7.4.3.1: vps_max_dec_pic_buffering_minus1[i] is in [0, MaxDpbSize - 1]. + HEVC_MAX_REFS = HEVC_MAX_DPB_SIZE, + + // 7.4.3.2.1: num_short_term_ref_pic_sets is in [0, 64]. + HEVC_MAX_SHORT_TERM_REF_PIC_SETS = 64, + // 7.4.3.2.1: num_long_term_ref_pics_sps is in [0, 32]. + HEVC_MAX_LONG_TERM_REF_PICS = 32, + + // A.3: all profiles require that CtbLog2SizeY is in [4, 6]. + HEVC_MIN_LOG2_CTB_SIZE = 4, + HEVC_MAX_LOG2_CTB_SIZE = 6, + + // E.3.2: cpb_cnt_minus1[i] is in [0, 31]. + HEVC_MAX_CPB_CNT = 32, + + // A.4.1: in table A.6 the highest level allows a MaxLumaPs of 35 651 584. + HEVC_MAX_LUMA_PS = 35651584, + // A.4.1: pic_width_in_luma_samples and pic_height_in_luma_samples are + // constrained to be not greater than sqrt(MaxLumaPs * 8). Hence height/ + // width are bounded above by sqrt(8 * 35651584) = 16888.2 samples. + HEVC_MAX_WIDTH = 16888, + HEVC_MAX_HEIGHT = 16888, + + // A.4.1: table A.6 allows at most 22 tile rows for any level. + HEVC_MAX_TILE_ROWS = 22, + // A.4.1: table A.6 allows at most 20 tile columns for any level. + HEVC_MAX_TILE_COLUMNS = 20, + + // 7.4.7.1: in the worst case (tiles_enabled_flag and + // entropy_coding_sync_enabled_flag are both set), entry points can be + // placed at the beginning of every Ctb row in every tile, giving an + // upper bound of (num_tile_columns_minus1 + 1) * PicHeightInCtbsY - 1. + // Only a stream with very high resolution and perverse parameters could + // get near that, though, so set a lower limit here with the maximum + // possible value for 4K video (at most 135 16x16 Ctb rows). + HEVC_MAX_ENTRY_POINT_OFFSETS = HEVC_MAX_TILE_COLUMNS * 135, +}; + + +/** + * rational number numerator/denominator + */ +typedef struct T_AVRational{ + int num; ///< numerator + int den; ///< denominator +} T_AVRational; + + +/*** + * Sequence parameter set + * ¿É²Î¿¼H264±ê×¼µÚ7½ÚºÍ¸½Â¼D E + /*** + * Sequence parameter set + * H.264 sequence parameter set, version 7 and above, D E + + * [AUTO-TRANSLATED:bd590cb8] + */ +#define Extended_SAR 255 + +/** + * Sequence parameter set + */ +typedef struct T_SPS { + unsigned int uiSpsId; + int iProfileIdc; + int iLevelIdc; + int iChromaFormatIdc; + int iTransformBypass; ///< qpprime_y_zero_transform_bypass_flag + int iLog2MaxFrameNum; ///< log2_max_frame_num_minus4 + 4 + int iPocType; ///< pic_order_cnt_type + int iLog2MaxPocLsb; ///< log2_max_pic_order_cnt_lsb_minus4 + int iDeltaPicOrderAlwaysZeroFlag; + int iOffsetForNonRefPic; + int iOffsetForTopToBottomField; + int iPocCycleLength; ///< num_ref_frames_in_pic_order_cnt_cycle + int iRefFrameCount; ///< num_ref_frames + int iGapsInFrameNumAllowedFlag; + int iMbWidth; ///< pic_width_in_mbs_minus1 + 1 + int iMbHeight; ///< pic_height_in_map_units_minus1 + 1 + int iFrameMbsOnlyFlag; + int iMbAff; ///< mb_adaptive_frame_field_flag + int iDirect8x8InferenceFlag; + int iCrop; ///< frame_cropping_flag + + /* those 4 are already in luma samples */ + unsigned int uiCropLeft; ///< frame_cropping_rect_left_offset + unsigned int uiCropRight; ///< frame_cropping_rect_right_offset + unsigned int uiCropTop; ///< frame_cropping_rect_top_offset + unsigned int uiCropBottom; ///< frame_cropping_rect_bottom_offset + int iVuiParametersPresentFlag; + T_AVRational tSar; + int iVideoSignalTypePresentFlag; + int iFullRange; + int iColourDescriptionPresentFlag; + enum T_AVColorPrimaries tColorPrimaries; + enum T_AVColorTransferCharacteristic tColorTrc; + enum T_AVColorSpace tColorspace; + int iTimingInfoPresentFlag; + uint32_t u32NumUnitsInTick; + uint32_t u32TimeScale; + int iFixedFrameRateFlag; + short asOffsetForRefFrame[256]; // FIXME dyn aloc? + int iBitstreamRestrictionFlag; + int iNumReorderFrames; + int iScalingMatrixPresent; + uint8_t aau8ScalingMatrix4[6][16]; + uint8_t aau8ScalingMatrix8[6][64]; + int iNalHrdParametersPresentFlag; + int iVclHrdParametersPresentFlag; + int iPicStructPresentFlag; + int iTimeOffsetLength; + int iCpbCnt; ///< See H.264 E.1.2 + int iInitialCpbRemovalDelayLength; ///< initial_cpb_removal_delay_length_minus1 + 1 + int iCpbRemovalDelayLength; ///< cpb_removal_delay_length_minus1 + 1 + int iDpbOutputDelayLength; ///< dpb_output_delay_length_minus1 + 1 + int iBitDepthLuma; ///< bit_depth_luma_minus8 + 8 + int iBitDepthChroma; ///< bit_depth_chroma_minus8 + 8 + int iResidualColorTransformFlag; ///< residual_colour_transform_flag + int iConstraintSetFlags; ///< constraint_set[0-3]_flag + int iNew; ///< flag to keep track if the decoder context needs re-init due to changed SPS +} T_SPS; + +/** + * Picture parameter set + */ +typedef struct T_PPS { + unsigned int uiSpsId; + int iCabac; ///< entropy_coding_mode_flag + int iPicOrderPresent; ///< pic_order_present_flag + int iSliceGroupCount; ///< num_slice_groups_minus1 + 1 + int iMbSliceGroupMapType; + unsigned int auiRefCount[2]; ///< num_ref_idx_l0/1_active_minus1 + 1 + int iWeightedPred; ///< weighted_pred_flag + int iWeightedBipredIdc; + int iInitQp; ///< pic_init_qp_minus26 + 26 + int iInitQs; ///< pic_init_qs_minus26 + 26 + int aiChromaQpIndexOffset[2]; + int iDeblockingFilterParametersPresent; ///< deblocking_filter_parameters_present_flag + int iConstrainedIntraPred; ///< constrained_intra_pred_flag + int iRedundantPicCntPresent; ///< redundant_pic_cnt_present_flag + int iTransform8x8Mode; ///< transform_8x8_mode_flag + uint8_t aau8ScalingMatrix4[6][16]; + uint8_t aau8ScalingMatrix8[6][64]; + uint8_t u8ChromaQpTable[2][QP_MAX_NUM+1]; ///< pre-scaled (with aiChromaQpIndexOffset) version of qp_table + int iChromaQpDiff; +} T_PPS; + + +typedef struct T_HEVCWindow { + unsigned int uiLeftOffset; + unsigned int uiRightOffset; + unsigned int uiTopOffset; + unsigned int uiBottomOffset; +} T_HEVCWindow; + + +typedef struct T_VUI { + T_AVRational tSar; + + int iOverscanInfoPresentFlag; + int iOverscanAppropriateFlag; + + int iVideoSignalTypePresentFlag; + int iVideoFormat; + int iVideoFullRangeFlag; + int iColourDescriptionPresentFlag; + uint8_t u8ColourPrimaries; + uint8_t u8TransferCharacteristic; + uint8_t u8MatrixCoeffs; + + int iChromaLocInfoPresentFlag; + int iChromaSampleLocTypeTopField; + int iChromaSampleLocTypeBottomField; + int iNeutraChromaIndicationFlag; + + int iFieldSeqFlag; + int iFrameFieldInfoPresentFlag; + + int iDefaultDisplayWindowFlag; + T_HEVCWindow tDefDispWin; + + int iVuiTimingInfoPresentFlag; + uint32_t u32VuiNumUnitsInTick; + uint32_t u32VuiTimeScale; + int iVuiPocProportionalToTimingFlag; + int iVuiNumTicksPocDiffOneMinus1; + int iVuiHrdParametersPresentFlag; + + int iBitstreamRestrictionFlag; + int iTilesFixedStructureFlag; + int iMotionVectorsOverPicBoundariesFlag; + int iRestrictedRefPicListsFlag; + int iMinSpatialSegmentationIdc; + int iMaxBytesPerPicDenom; + int iMaxBitsPerMinCuDenom; + int iLog2MaxMvLengthHorizontal; + int iLog2MaxMvLengthVertical; +} T_VUI; + +typedef struct T_PTLCommon { + uint8_t u8ProfileSpace; + uint8_t u8TierFlag; + uint8_t u8ProfileIdc; + uint8_t au8ProfileCompatibilityFlag[32]; + uint8_t u8LevelIdc; + uint8_t u8ProgressiveSourceFlag; + uint8_t u8InterlacedSourceFlag; + uint8_t u8NonPackedConstraintFlag; + uint8_t u8FrameOnlyConstraintFlag; +} T_PTLCommon; + +typedef struct T_PTL { + T_PTLCommon tGeneralPtl; + T_PTLCommon atSubLayerPtl[HEVC_MAX_SUB_LAYERS]; + + uint8_t au8SubLayerProfilePresentFlag[HEVC_MAX_SUB_LAYERS]; + uint8_t au8SubLayerLevelPresentFlag[HEVC_MAX_SUB_LAYERS]; +} T_PTL; + +typedef struct T_ScalingList { + /* This is a little wasteful, since sizeID 0 only needs 8 coeffs, + * and size ID 3 only has 2 arrays, not 6. */ + uint8_t aaau8Sl[4][6][64]; + uint8_t aau8SlDc[2][6]; +} T_ScalingList; + +typedef struct T_ShortTermRPS { + unsigned int uiNumNegativePics; + int iNumDeltaPocs; + int iRpsIdxNumDeltaPocs; + int32_t au32DeltaPoc[32]; + uint8_t au8Used[32]; +} T_ShortTermRPS; + + +typedef struct T_HEVCVPS { + uint8_t u8VpsTemporalIdNestingFlag; + int iVpsMaxLayers; + int iVpsMaxSubLayers; ///< vps_max_temporal_layers_minus1 + 1 + + T_PTL tPtl; + int iVpsSubLayerOrderingInfoPresentFlag; + unsigned int uiVpsMaxDecPicBuffering[HEVC_MAX_SUB_LAYERS]; + unsigned int auiVpsNumReorderPics[HEVC_MAX_SUB_LAYERS]; + unsigned int auiVpsMaxLatencyIncrease[HEVC_MAX_SUB_LAYERS]; + int iVpsMaxLayerId; + int iVpsNumLayerSets; ///< vps_num_layer_sets_minus1 + 1 + uint8_t u8VpsTimingInfoPresentFlag; + uint32_t u32VpsNumUnitsInTick; + uint32_t u32VpsTimeScale; + uint8_t u8VpsPocProportionalToTimingFlag; + int iVpsNumTicksPocDiffOne; ///< vps_num_ticks_poc_diff_one_minus1 + 1 + int iVpsNumHrdParameters; + +} T_HEVCVPS; + +typedef struct T_HEVCSPS { + unsigned int uiVpsId; + int iChromaFormatIdc; + uint8_t u8SeparateColourPlaneFlag; + + ///< output (i.e. cropped) values + int iIutputWidth, iOutputHeight; + T_HEVCWindow tOutputWindow; + + T_HEVCWindow tPicConfWin; + + int iBitDepth; + int iBitDepthChroma; + int iPixelShift; + + unsigned int uiLog2MaxPocLsb; + int iPcmEnabledFlag; + + int iMaxSubLayers; + struct { + int iMaxDecPicBuffering; + int iNumReorderPics; + int iMaxLatencyIncrease; + } stTemporalLayer[HEVC_MAX_SUB_LAYERS]; + uint8_t u8temporalIdNestingFlag; + + T_VUI tVui; + T_PTL tPtl; + + uint8_t u8ScalingListEnableFlag; + T_ScalingList tScalingList; + + unsigned int uiNbStRps; + T_ShortTermRPS atStRps[HEVC_MAX_SHORT_TERM_RPS_COUNT]; + + uint8_t u8AmpEnabledFlag; + uint8_t u8SaoEnabled; + + uint8_t u8LongTermRefPicsPresentFlag; + uint16_t au16LtRefPicPocLsbSps[32]; + uint8_t au8UsedByCurrPicLtSpsFlag[32]; + uint8_t u8NumLongTermRefPicsSps; + + struct { + uint8_t u8BitDepth; + uint8_t u8BitDepthChroma; + unsigned int uiLog2MinPcmCbSize; + unsigned int uiLog2MaxPcmCbSize; + uint8_t u8LoopFilterDisableFlag; + } pcm; + uint8_t u8SpsTemporalMvpEnabledFlag; + uint8_t u8SpsStrongIntraMmoothingEnableFlag; + + unsigned int uiLog2MinCbSize; + unsigned int uiLog2DiffMaxMinCodingBlockSize; + unsigned int uiLog2MinTbSize; + unsigned int uiLog2MaxTrafoSize; + unsigned int uiLog2CtbSize; + unsigned int uiLog2MinPuSize; + + int iMaxTransformHierarchyDepthInter; + int iMaxTransformHierarchyDepthIntra; + + int iTransformSkipRotationEnabledFlag; + int iTransformSkipContextEnabledFlag; + int iImplicitRdpcmEnabledFlag; + int iExplicitRdpcmEnabledFlag; + int iIntraSmoothingDisabledFlag; + int iHighPrecisionOffsetsEnabledFlag; + int iPersistentRiceAdaptationEnabledFlag; + + ///< coded frame dimension in various units + int iWidth; + int iHeight; + int iCtbWidth; + int iCtbHeight; + int iCtbSize; + int iMinCbWidth; + int iMinCbHeight; + int iMinTbWidth; + int iMinTbHeight; + int iMinPuWidth; + int iMinPuHeight; + int iTbMask; + + int aiHshift[3]; + int aiVshift[3]; + + int iQpBdOffset; + + int iVuiPresent; +}T_HEVCSPS; + +typedef struct { + int pps_pic_parameter_set_id; + int pps_seq_parameter_set_id; + int dependent_slice_segments_enabled_flag; + int output_flag_present_flag; + int num_extra_slice_header_bits; + int sign_data_hiding_enabled_flag; + int cabac_init_present_flag; + int num_ref_idx_l0_default_active_minus1; + int num_ref_idx_l1_default_active_minus1; + int init_qp_minus26; + int constrained_intra_pred_flag; + int transform_skip_enabled_flag; + int cu_qp_delta_enabled_flag; + int diff_cu_qp_delta_depth; + int pps_cb_qp_offset; + int pps_cr_qp_offset; + int pps_slice_chroma_qp_offsets_present_flag; + int weighted_pred_flag; + int weighted_bipred_flag; + int transquant_bypass_enabled_flag; + int tiles_enabled_flag; + int entropy_coding_sync_enabled_flag; + int uniform_spacing_flag; + int loop_filter_across_tiles_enabled_flag; + int pps_loop_filter_across_slices_enabled_flag; + int deblocking_filter_control_present_flag; + int deblocking_filter_override_enabled_flag; + int pps_deblocking_filter_disabled_flag; + int pps_beta_offset_div2; + int pps_tc_offset_div2; + int pps_scaling_list_data_present_flag; + int lists_modification_present_flag; + int log2_parallel_merge_level_minus2; + int slice_segment_header_extension_present_flag; + int pps_extension_present_flag; + int pps_range_extension_flag; + int pps_multilayer_extension_flag; + int pps_3d_extension_flag; + int pps_scc_extension_flag; + int pps_extension_4bits; + + // PPS range extension fields + int log2_max_transform_skip_block_size_minus2; + int cross_component_prediction_enabled_flag; + int chroma_qp_offset_list_enabled_flag; + int diff_cu_chroma_qp_offset_depth; + int chroma_qp_offset_list_len_minus1; + int cb_qp_offset_list[6]; + int cr_qp_offset_list[6]; + int log2_sao_offset_scale_luma; + int log2_sao_offset_scale_chroma; + + // 可以根据需要添加更多字段 [AUTO-TRANSLATED:57b9a7c1] + // You can add more fields as needed +} T_HEVC_PPS; + +typedef struct T_GetBitContext{ + uint8_t *pu8Buf; // buf + int iBufSize; // buf size + int iBitPos; // bit position + int iTotalBit; // bit number + int iCurBitPos; // current bit position +}T_GetBitContext; + +int h265ParsePps(T_GetBitContext *ptGetBitContext, T_HEVC_PPS *ptPps); +int h264DecSeqParameterSet(void *pvBuf, T_SPS *ptSps); +int h265DecSeqParameterSet( void *pvBufSrc, T_HEVCSPS *ptSps ); +int h265DecVideoParameterSet( void *pvBufSrc, T_HEVCVPS *ptVps ); + + +void h264GetWidthHeight(T_SPS *ptSps, int *piWidth, int *piHeight); +void h265GetWidthHeight(T_HEVCSPS *ptSps, int *piWidth, int *piHeight); + +void h264GeFramerate(T_SPS *ptSps, float *pfFramerate); +void h265GeFramerate(T_HEVCVPS *ptVps, T_HEVCSPS *ptSps,float *pfFramerate); + +#if defined (__cplusplus) +} +#endif + +#endif //_SPS_PPS_H_ diff --git a/Server/CMakeLists.txt b/Server/CMakeLists.txt index 7eb298b..6a9792e 100644 --- a/Server/CMakeLists.txt +++ b/Server/CMakeLists.txt @@ -8,7 +8,6 @@ add_executable(Server main.cpp Application.h Application.cpp HttpSession.h HttpSession.cpp Listener.h Listener.cpp - MediaServer.h MediaServer.cpp ResponseUtility.h ResponseUtility.cpp ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp ServiceManager.h @@ -19,18 +18,10 @@ add_executable(Server main.cpp WeChatContext/WeChatSession.h WeChatContext/WeChatSession.cpp ) -target_include_directories(Server - PRIVATE /opt/Libraries/ZLMediaKit/include -) - -target_link_directories(Server - PRIVATE /opt/Libraries/ZLMediaKit/lib -) - target_link_libraries(Server PRIVATE HttpProxy PRIVATE Database - PRIVATE mk_api + PRIVATE MediaServer PRIVATE ${Boost_LIBRARIES} ) diff --git a/Server/MediaServer.cpp b/Server/MediaServer.cpp deleted file mode 100644 index cfba85b..0000000 --- a/Server/MediaServer.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "MediaServer.h" -#include "BoostLog.h" -#include - -MediaServer::MediaServer() { - uint16_t status = mk_rtsp_server_start(554, 0); - if (status == 0) { - LOG(info) << "rtsp server start failed."; - } else { - LOG(info) << "rtsp server start succeed, port: " << status; - } -} \ No newline at end of file diff --git a/Server/MediaServer.h b/Server/MediaServer.h deleted file mode 100644 index b4537f0..0000000 --- a/Server/MediaServer.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __MEDIASERVER_H__ -#define __MEDIASERVER_H__ - -class MediaServer { -public: - MediaServer(); -}; - -#endif // __MEDIASERVER_H__ \ No newline at end of file diff --git a/Server/main.cpp b/Server/main.cpp index 0545b52..cc17408 100644 --- a/Server/main.cpp +++ b/Server/main.cpp @@ -3,6 +3,7 @@ #include "Database.h" #include "IoContext.h" #include "Listener.h" +#include "MediaServer.h" #include "ProxyListener.h" #include "ServiceManager.h" #include "UdpServer.h" @@ -12,7 +13,6 @@ #include #include #include -#include "MediaServer.h" int main(int argc, char const *argv[]) { using namespace Amass; @@ -97,7 +97,7 @@ int main(int argc, char const *argv[]) { auto udpServer = std::make_shared(application->ioContext()); - auto mediaServer = std::make_shared(); + auto mediaServer = std::make_shared(554, false); using namespace boost::asio::ip; auto proxyAddress = make_address(application->getServer()); diff --git a/ToolKit/CMakeLists.txt b/ToolKit/CMakeLists.txt new file mode 100644 index 0000000..ad3c082 --- /dev/null +++ b/ToolKit/CMakeLists.txt @@ -0,0 +1,36 @@ +add_library(ToolKit + Network/Buffer.h Network/Buffer.cpp + Network/BufferSock.h Network/BufferSock.cpp + Network/Server.h Network/Server.cpp + Network/Session.h Network/Session.cpp + Network/Socket.h Network/Socket.cpp + Network/sockutil.h Network/sockutil.cpp + Network/TcpClient.h Network/TcpClient.cpp + Network/TcpServer.h Network/TcpServer.cpp + + Poller/EventPoller.h Poller/EventPoller.cpp + Poller/Pipe.h Poller/Pipe.cpp + Poller/PipeWrap.h Poller/PipeWrap.cpp + Poller/SelectWrap.h Poller/SelectWrap.cpp + Poller/Timer.h Poller/Timer.cpp + + Thread/TaskExecutor.h Thread/TaskExecutor.cpp + Thread/WorkThreadPool.h Thread/WorkThreadPool.cpp + + Util/base64.h Util/base64.cpp + Util/File.h Util/File.cpp + Util/local_time.h Util/local_time.cpp + Util/logger.h Util/logger.cpp + Util/MD5.h Util/MD5.cpp + Util/mini.h Util/mini.cpp + Util/NoticeCenter.h Util/NoticeCenter.cpp + Util/SHA1.h Util/SHA1.cpp + Util/SSLBox.h Util/SSLBox.cpp + Util/SSLUtil.h Util/SSLUtil.cpp + Util/util.h Util/util.cpp + Util/uv_errno.h Util/uv_errno.cpp +) + +target_include_directories(ToolKit + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) \ No newline at end of file diff --git a/ToolKit/Network/Buffer.cpp b/ToolKit/Network/Buffer.cpp new file mode 100644 index 0000000..b9a6b17 --- /dev/null +++ b/ToolKit/Network/Buffer.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "Buffer.h" +#include "Util/onceToken.h" + +namespace toolkit { + +StatisticImp(Buffer) +StatisticImp(BufferRaw) +StatisticImp(BufferLikeString) + +BufferRaw::Ptr BufferRaw::create() { +#if 0 + static ResourcePool packet_pool; + static onceToken token([]() { + packet_pool.setSize(1024); + }); + auto ret = packet_pool.obtain2(); + ret->setSize(0); + return ret; +#else + return Ptr(new BufferRaw); +#endif +} + +}//namespace toolkit diff --git a/ToolKit/Network/Buffer.h b/ToolKit/Network/Buffer.h new file mode 100644 index 0000000..6724519 --- /dev/null +++ b/ToolKit/Network/Buffer.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_BUFFER_H +#define ZLTOOLKIT_BUFFER_H + +#include +#include +#include +#include +#include +#include +#include "Util/util.h" +#include "Util/ResourcePool.h" + +namespace toolkit { + +template struct is_pointer : public std::false_type {}; +template struct is_pointer > : public std::true_type {}; +template struct is_pointer > : public std::true_type {}; +template struct is_pointer : public std::true_type {}; +template struct is_pointer : public std::true_type {}; + +//缓存基类 +class Buffer : public noncopyable { +public: + using Ptr = std::shared_ptr; + + Buffer() = default; + virtual ~Buffer() = default; + + //返回数据长度 + virtual char *data() const = 0; + virtual size_t size() const = 0; + + virtual std::string toString() const { + return std::string(data(), size()); + } + + virtual size_t getCapacity() const { + return size(); + } + +private: + //对象个数统计 + ObjectStatistic _statistic; +}; + +template +class BufferOffset : public Buffer { +public: + using Ptr = std::shared_ptr; + + BufferOffset(C data, size_t offset = 0, size_t len = 0) : _data(std::move(data)) { + setup(offset, len); + } + + ~BufferOffset() override = default; + + char *data() const override { + return const_cast(getPointer(_data)->data()) + _offset; + } + + size_t size() const override { + return _size; + } + + std::string toString() const override { + return std::string(data(), size()); + } + +private: + void setup(size_t offset = 0, size_t size = 0) { + auto max_size = getPointer(_data)->size(); + assert(offset + size <= max_size); + if (!size) { + size = max_size - offset; + } + _size = size; + _offset = offset; + } + + template + static typename std::enable_if<::toolkit::is_pointer::value, const T &>::type + getPointer(const T &data) { + return data; + } + + template + static typename std::enable_if::value, const T *>::type + getPointer(const T &data) { + return &data; + } + +private: + C _data; + size_t _size; + size_t _offset; +}; + +using BufferString = BufferOffset; + +//指针式缓存对象, +class BufferRaw : public Buffer { +public: + using Ptr = std::shared_ptr; + + static Ptr create(); + + ~BufferRaw() override { + if (_data) { + delete[] _data; + } + } + + //在写入数据时请确保内存是否越界 + char *data() const override { + return _data; + } + + //有效数据大小 + size_t size() const override { + return _size; + } + + //分配内存大小 + void setCapacity(size_t capacity) { + if (_data) { + do { + if (capacity > _capacity) { + //请求的内存大于当前内存,那么重新分配 + break; + } + + if (_capacity < 2 * 1024) { + //2K以下,不重复开辟内存,直接复用 + return; + } + + if (2 * capacity > _capacity) { + //如果请求的内存大于当前内存的一半,那么也复用 + return; + } + } while (false); + + delete[] _data; + } + _data = new char[capacity]; + _capacity = capacity; + } + + //设置有效数据大小 + virtual void setSize(size_t size) { + if (size > _capacity) { + throw std::invalid_argument("Buffer::setSize out of range"); + } + _size = size; + } + + //赋值数据 + void assign(const char *data, size_t size = 0) { + if (size <= 0) { + size = strlen(data); + } + setCapacity(size + 1); + memcpy(_data, data, size); + _data[size] = '\0'; + setSize(size); + } + + size_t getCapacity() const override { + return _capacity; + } + +protected: + friend class ResourcePool_l; + + BufferRaw(size_t capacity = 0) { + if (capacity) { + setCapacity(capacity); + } + } + + BufferRaw(const char *data, size_t size = 0) { + assign(data, size); + } + +private: + size_t _size = 0; + size_t _capacity = 0; + char *_data = nullptr; + //对象个数统计 + ObjectStatistic _statistic; +}; + +class BufferLikeString : public Buffer { +public: + ~BufferLikeString() override = default; + + BufferLikeString() { + _erase_head = 0; + _erase_tail = 0; + } + + BufferLikeString(std::string str) { + _str = std::move(str); + _erase_head = 0; + _erase_tail = 0; + } + + BufferLikeString &operator=(std::string str) { + _str = std::move(str); + _erase_head = 0; + _erase_tail = 0; + return *this; + } + + BufferLikeString(const char *str) { + _str = str; + _erase_head = 0; + _erase_tail = 0; + } + + BufferLikeString &operator=(const char *str) { + _str = str; + _erase_head = 0; + _erase_tail = 0; + return *this; + } + + BufferLikeString(BufferLikeString &&that) { + _str = std::move(that._str); + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + that._erase_head = 0; + that._erase_tail = 0; + } + + BufferLikeString &operator=(BufferLikeString &&that) { + _str = std::move(that._str); + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + that._erase_head = 0; + that._erase_tail = 0; + return *this; + } + + BufferLikeString(const BufferLikeString &that) { + _str = that._str; + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + } + + BufferLikeString &operator=(const BufferLikeString &that) { + _str = that._str; + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + return *this; + } + + char *data() const override { + return (char *) _str.data() + _erase_head; + } + + size_t size() const override { + return _str.size() - _erase_tail - _erase_head; + } + + BufferLikeString &erase(size_t pos = 0, size_t n = std::string::npos) { + if (pos == 0) { + //移除前面的数据 + if (n != std::string::npos) { + //移除部分 + if (n > size()) { + //移除太多数据了 + throw std::out_of_range("BufferLikeString::erase out_of_range in head"); + } + //设置起始便宜量 + _erase_head += n; + data()[size()] = '\0'; + return *this; + } + //移除全部数据 + _erase_head = 0; + _erase_tail = _str.size(); + data()[0] = '\0'; + return *this; + } + + if (n == std::string::npos || pos + n >= size()) { + //移除末尾所有数据 + if (pos >= size()) { + //移除太多数据 + throw std::out_of_range("BufferLikeString::erase out_of_range in tail"); + } + _erase_tail += size() - pos; + data()[size()] = '\0'; + return *this; + } + + //移除中间的 + if (pos + n > size()) { + //超过长度限制 + throw std::out_of_range("BufferLikeString::erase out_of_range in middle"); + } + _str.erase(_erase_head + pos, n); + return *this; + } + + BufferLikeString &append(const BufferLikeString &str) { + return append(str.data(), str.size()); + } + + BufferLikeString &append(const std::string &str) { + return append(str.data(), str.size()); + } + + BufferLikeString &append(const char *data) { + return append(data, strlen(data)); + } + + BufferLikeString &append(const char *data, size_t len) { + if (len <= 0) { + return *this; + } + if (_erase_head > _str.capacity() / 2) { + moveData(); + } + if (_erase_tail == 0) { + _str.append(data, len); + return *this; + } + _str.insert(_erase_head + size(), data, len); + return *this; + } + + void push_back(char c) { + if (_erase_tail == 0) { + _str.push_back(c); + return; + } + data()[size()] = c; + --_erase_tail; + data()[size()] = '\0'; + } + + BufferLikeString &insert(size_t pos, const char *s, size_t n) { + _str.insert(_erase_head + pos, s, n); + return *this; + } + + BufferLikeString &assign(const char *data) { + return assign(data, strlen(data)); + } + + BufferLikeString &assign(const char *data, size_t len) { + if (len <= 0) { + return *this; + } + if (data >= _str.data() && data < _str.data() + _str.size()) { + _erase_head = data - _str.data(); + if (data + len > _str.data() + _str.size()) { + throw std::out_of_range("BufferLikeString::assign out_of_range"); + } + _erase_tail = _str.data() + _str.size() - (data + len); + return *this; + } + _str.assign(data, len); + _erase_head = 0; + _erase_tail = 0; + return *this; + } + + void clear() { + _erase_head = 0; + _erase_tail = 0; + _str.clear(); + } + + char &operator[](size_t pos) { + if (pos >= size()) { + throw std::out_of_range("BufferLikeString::operator[] out_of_range"); + } + return data()[pos]; + } + + const char &operator[](size_t pos) const { + return (*const_cast(this))[pos]; + } + + size_t capacity() const { + return _str.capacity(); + } + + void reserve(size_t size) { + _str.reserve(size); + } + + void resize(size_t size, char c = '\0') { + _str.resize(size, c); + _erase_head = 0; + _erase_tail = 0; + } + + bool empty() const { + return size() <= 0; + } + + std::string substr(size_t pos, size_t n = std::string::npos) const { + if (n == std::string::npos) { + //获取末尾所有的 + if (pos >= size()) { + throw std::out_of_range("BufferLikeString::substr out_of_range"); + } + return _str.substr(_erase_head + pos, size() - pos); + } + + //获取部分 + if (pos + n > size()) { + throw std::out_of_range("BufferLikeString::substr out_of_range"); + } + return _str.substr(_erase_head + pos, n); + } + +private: + void moveData() { + if (_erase_head) { + _str.erase(0, _erase_head); + _erase_head = 0; + } + } + +private: + size_t _erase_head; + size_t _erase_tail; + std::string _str; + //对象个数统计 + ObjectStatistic _statistic; +}; + +}//namespace toolkit +#endif //ZLTOOLKIT_BUFFER_H diff --git a/ToolKit/Network/BufferSock.cpp b/ToolKit/Network/BufferSock.cpp new file mode 100644 index 0000000..4bedea1 --- /dev/null +++ b/ToolKit/Network/BufferSock.cpp @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "BufferSock.h" +#include "Util/logger.h" +#include "Util/uv_errno.h" + +#if defined(__linux__) || defined(__linux) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef MSG_WAITFORONE +#define MSG_WAITFORONE 0x10000 +#endif + +#ifndef HAVE_SENDMMSG_API +#include +#include +static inline int sendmmsg(int fd, struct mmsghdr *mmsg, + unsigned vlen, unsigned flags) +{ + return syscall(__NR_sendmmsg, fd, mmsg, vlen, flags); +} +#endif + +#ifndef HAVE_RECVMMSG_API +#include +#include +static inline int recvmmsg(int fd, struct mmsghdr *mmsg, + unsigned vlen, unsigned flags, struct timespec *timeout) +{ + return syscall(__NR_recvmmsg, fd, mmsg, vlen, flags, timeout); +} +#endif + +#endif// defined(__linux__) || defined(__linux) + +namespace toolkit { + +StatisticImp(BufferList) + +/////////////////////////////////////// BufferSock /////////////////////////////////////// + +BufferSock::BufferSock(Buffer::Ptr buffer, struct sockaddr *addr, int addr_len) { + if (addr) { + _addr_len = addr_len ? addr_len : SockUtil::get_sock_len(addr); + memcpy(&_addr, addr, _addr_len); + } + assert(buffer); + _buffer = std::move(buffer); +} + +char *BufferSock::data() const { + return _buffer->data(); +} + +size_t BufferSock::size() const { + return _buffer->size(); +} + +const struct sockaddr *BufferSock::sockaddr() const { + return (struct sockaddr *)&_addr; +} + +socklen_t BufferSock::socklen() const { + return _addr_len; +} + +/////////////////////////////////////// BufferCallBack /////////////////////////////////////// + +class BufferCallBack { +public: + BufferCallBack(List > list, BufferList::SendResult cb) + : _cb(std::move(cb)) + , _pkt_list(std::move(list)) {} + + ~BufferCallBack() { + sendCompleted(false); + } + + void sendCompleted(bool flag) { + if (_cb) { + //全部发送成功或失败回调 + while (!_pkt_list.empty()) { + _cb(_pkt_list.front().first, flag); + _pkt_list.pop_front(); + } + } else { + _pkt_list.clear(); + } + } + + void sendFrontSuccess() { + if (_cb) { + //发送成功回调 + _cb(_pkt_list.front().first, true); + } + _pkt_list.pop_front(); + } + +protected: + BufferList::SendResult _cb; + List > _pkt_list; +}; + +/////////////////////////////////////// BufferSendMsg /////////////////////////////////////// +#if defined(_WIN32) +using SocketBuf = WSABUF; +#else +using SocketBuf = iovec; +#endif + +class BufferSendMsg final : public BufferList, public BufferCallBack { +public: + using SocketBufVec = std::vector; + + BufferSendMsg(List > list, SendResult cb); + ~BufferSendMsg() override = default; + + bool empty() override; + size_t count() override; + ssize_t send(int fd, int flags) override; + +private: + void reOffset(size_t n); + ssize_t send_l(int fd, int flags); + +private: + size_t _iovec_off = 0; + size_t _remain_size = 0; + SocketBufVec _iovec; +}; + +bool BufferSendMsg::empty() { + return _remain_size == 0; +} + +size_t BufferSendMsg::count() { + return _iovec.size() - _iovec_off; +} + +ssize_t BufferSendMsg::send_l(int fd, int flags) { + ssize_t n; +#if !defined(_WIN32) + do { + struct msghdr msg; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = &(_iovec[_iovec_off]); + msg.msg_iovlen = _iovec.size() - _iovec_off; + if (msg.msg_iovlen > IOV_MAX) { + msg.msg_iovlen = IOV_MAX; + } + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = flags; + n = sendmsg(fd, &msg, flags); + } while (-1 == n && UV_EINTR == get_uv_error(true)); +#else + do { + DWORD sent = 0; + n = WSASend(fd, const_cast(&_iovec[0]), static_cast(_iovec.size()), &sent, static_cast(flags), 0, 0); + if (n == SOCKET_ERROR) {return -1;} + n = sent; + } while (n < 0 && UV_ECANCELED == get_uv_error(true)); +#endif + + if (n >= (ssize_t)_remain_size) { + //全部写完了 + _remain_size = 0; + sendCompleted(true); + return n; + } + + if (n > 0) { + //部分发送成功 + reOffset(n); + return n; + } + + //一个字节都未发送 + return n; +} + +ssize_t BufferSendMsg::send(int fd, int flags) { + auto remain_size = _remain_size; + while (_remain_size && send_l(fd, flags) != -1); + + ssize_t sent = remain_size - _remain_size; + if (sent > 0) { + //部分或全部发送成功 + return sent; + } + //一个字节都未发送成功 + return -1; +} + +void BufferSendMsg::reOffset(size_t n) { + _remain_size -= n; + size_t offset = 0; + for (auto i = _iovec_off; i != _iovec.size(); ++i) { + auto &ref = _iovec[i]; +#if !defined(_WIN32) + offset += ref.iov_len; +#else + offset += ref.len; +#endif + if (offset < n) { + //此包发送完毕 + sendFrontSuccess(); + continue; + } + _iovec_off = i; + if (offset == n) { + //这是末尾发送完毕的一个包 + ++_iovec_off; + sendFrontSuccess(); + break; + } + //这是末尾发送部分成功的一个包 + size_t remain = offset - n; +#if !defined(_WIN32) + ref.iov_base = (char *)ref.iov_base + ref.iov_len - remain; + ref.iov_len = remain; +#else + ref.buf = (CHAR *)ref.buf + ref.len - remain; + ref.len = remain; +#endif + break; + } +} + +BufferSendMsg::BufferSendMsg(List> list, SendResult cb) + : BufferCallBack(std::move(list), std::move(cb)) + , _iovec(_pkt_list.size()) { + auto it = _iovec.begin(); + _pkt_list.for_each([&](std::pair &pr) { +#if !defined(_WIN32) + it->iov_base = pr.first->data(); + it->iov_len = pr.first->size(); + _remain_size += it->iov_len; +#else + it->buf = pr.first->data(); + it->len = pr.first->size(); + _remain_size += it->len; +#endif + ++it; + }); +} + +/////////////////////////////////////// BufferSendTo /////////////////////////////////////// +class BufferSendTo final: public BufferList, public BufferCallBack { +public: + BufferSendTo(List > list, SendResult cb, bool is_udp); + ~BufferSendTo() override = default; + + bool empty() override; + size_t count() override; + ssize_t send(int fd, int flags) override; + +private: + bool _is_udp; + size_t _offset = 0; +}; + +BufferSendTo::BufferSendTo(List> list, BufferList::SendResult cb, bool is_udp) + : BufferCallBack(std::move(list), std::move(cb)) + , _is_udp(is_udp) {} + +bool BufferSendTo::empty() { + return _pkt_list.empty(); +} + +size_t BufferSendTo::count() { + return _pkt_list.size(); +} + +static inline BufferSock *getBufferSockPtr(std::pair &pr) { + if (!pr.second) { + return nullptr; + } + return static_cast(pr.first.get()); +} + +ssize_t BufferSendTo::send(int fd, int flags) { + size_t sent = 0; + ssize_t n; + while (!_pkt_list.empty()) { + auto &front = _pkt_list.front(); + auto &buffer = front.first; + if (_is_udp) { + auto ptr = getBufferSockPtr(front); + n = ::sendto(fd, buffer->data() + _offset, buffer->size() - _offset, flags, ptr ? ptr->sockaddr() : nullptr, ptr ? ptr->socklen() : 0); + } else { + n = ::send(fd, buffer->data() + _offset, buffer->size() - _offset, flags); + } + + if (n >= 0) { + assert(n); + _offset += n; + if (_offset == buffer->size()) { + sendFrontSuccess(); + _offset = 0; + } + sent += n; + continue; + } + + //n == -1的情况 + if (get_uv_error(true) == UV_EINTR) { + //被打断,需要继续发送 + continue; + } + //其他原因导致的send返回-1 + break; + } + return sent ? sent : -1; +} + +/////////////////////////////////////// BufferSendMmsg /////////////////////////////////////// + +#if defined(__linux__) || defined(__linux) + +class BufferSendMMsg : public BufferList, public BufferCallBack { +public: + BufferSendMMsg(List > list, SendResult cb); + ~BufferSendMMsg() override = default; + + bool empty() override; + size_t count() override; + ssize_t send(int fd, int flags) override; + +private: + void reOffset(size_t n); + ssize_t send_l(int fd, int flags); + +private: + size_t _remain_size = 0; + std::vector _iovec; + std::vector _hdrvec; +}; + +bool BufferSendMMsg::empty() { + return _remain_size == 0; +} + +size_t BufferSendMMsg::count() { + return _hdrvec.size(); +} + +ssize_t BufferSendMMsg::send_l(int fd, int flags) { + ssize_t n; + do { + n = sendmmsg(fd, &_hdrvec[0], _hdrvec.size(), flags); + } while (-1 == n && UV_EINTR == get_uv_error(true)); + + if (n > 0) { + //部分或全部发送成功 + reOffset(n); + return n; + } + + //一个字节都未发送 + return n; +} + +ssize_t BufferSendMMsg::send(int fd, int flags) { + auto remain_size = _remain_size; + while (_remain_size && send_l(fd, flags) != -1); + ssize_t sent = remain_size - _remain_size; + if (sent > 0) { + //部分或全部发送成功 + return sent; + } + //一个字节都未发送成功 + return -1; +} + +void BufferSendMMsg::reOffset(size_t n) { + for (auto it = _hdrvec.begin(); it != _hdrvec.end();) { + auto &hdr = *it; + auto &io = *(hdr.msg_hdr.msg_iov); + assert(hdr.msg_len <= io.iov_len); + _remain_size -= hdr.msg_len; + if (hdr.msg_len == io.iov_len) { + //这个udp包全部发送成功 + it = _hdrvec.erase(it); + sendFrontSuccess(); + continue; + } + //部分发送成功 + io.iov_base = (char *)io.iov_base + hdr.msg_len; + io.iov_len -= hdr.msg_len; + break; + } +} + +BufferSendMMsg::BufferSendMMsg(List> list, SendResult cb) + : BufferCallBack(std::move(list), std::move(cb)) + , _iovec(_pkt_list.size()) + , _hdrvec(_pkt_list.size()) { + auto i = 0U; + _pkt_list.for_each([&](std::pair &pr) { + auto &io = _iovec[i]; + io.iov_base = pr.first->data(); + io.iov_len = pr.first->size(); + _remain_size += io.iov_len; + + auto ptr = getBufferSockPtr(pr); + auto &mmsg = _hdrvec[i]; + auto &msg = mmsg.msg_hdr; + mmsg.msg_len = 0; + msg.msg_name = ptr ? (void *)ptr->sockaddr() : nullptr; + msg.msg_namelen = ptr ? ptr->socklen() : 0; + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ++i; + }); +} + +#endif //defined(__linux__) || defined(__linux) + + +BufferList::Ptr BufferList::create(List > list, SendResult cb, bool is_udp) { +#if defined(_WIN32) + if (is_udp) { + // sendto/send 方案,待优化 + return std::make_shared(std::move(list), std::move(cb), is_udp); + } + // WSASend方案 + return std::make_shared(std::move(list), std::move(cb)); +#elif defined(__linux__) || defined(__linux) + if (is_udp) { + // sendmmsg方案 + return std::make_shared(std::move(list), std::move(cb)); + } + // sendmsg方案 + return std::make_shared(std::move(list), std::move(cb)); +#else + if (is_udp) { + // sendto/send 方案, 可优化? + return std::make_shared(std::move(list), std::move(cb), is_udp); + } + // sendmsg方案 + return std::make_shared(std::move(list), std::move(cb)); +#endif +} + +#if defined(__linux) || defined(__linux__) +class SocketRecvmmsgBuffer : public SocketRecvBuffer { +public: + SocketRecvmmsgBuffer(size_t count, size_t size) + : _size(size) + , _iovec(count) + , _mmsgs(count) + , _buffers(count) + , _address(count) { + for (auto i = 0u; i < count; ++i) { + auto buf = BufferRaw::create(); + buf->setCapacity(size); + + _buffers[i] = buf; + auto &mmsg = _mmsgs[i]; + auto &addr = _address[i]; + mmsg.msg_len = 0; + mmsg.msg_hdr.msg_name = &addr; + mmsg.msg_hdr.msg_namelen = sizeof(addr); + mmsg.msg_hdr.msg_iov = &_iovec[i]; + mmsg.msg_hdr.msg_iov->iov_base = buf->data(); + mmsg.msg_hdr.msg_iov->iov_len = buf->getCapacity() - 1; + mmsg.msg_hdr.msg_iovlen = 1; + mmsg.msg_hdr.msg_control = nullptr; + mmsg.msg_hdr.msg_controllen = 0; + mmsg.msg_hdr.msg_flags = 0; + } + } + + ssize_t recvFromSocket(int fd, ssize_t &count) override { + for (auto i = 0; i < _last_count; ++i) { + auto &mmsg = _mmsgs[i]; + mmsg.msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); + auto &buf = _buffers[i]; + if (!buf) { + auto raw = BufferRaw::create(); + raw->setCapacity(_size); + buf = raw; + mmsg.msg_hdr.msg_iov->iov_base = buf->data(); + } + } + do { + count = recvmmsg(fd, &_mmsgs[0], _mmsgs.size(), 0, nullptr); + } while (-1 == count && UV_EINTR == get_uv_error(true)); + + _last_count = count; + if (count <= 0) { + return count; + } + + ssize_t nread = 0; + for (auto i = 0; i < count; ++i) { + auto &mmsg = _mmsgs[i]; + nread += mmsg.msg_len; + + auto buf = std::static_pointer_cast(_buffers[i]); + buf->setSize(mmsg.msg_len); + buf->data()[mmsg.msg_len] = '\0'; + } + return nread; + } + + Buffer::Ptr &getBuffer(size_t index) override { return _buffers[index]; } + + struct sockaddr_storage &getAddress(size_t index) override { return _address[index]; } + +private: + size_t _size; + ssize_t _last_count { 0 }; + std::vector _iovec; + std::vector _mmsgs; + std::vector _buffers; + std::vector _address; +}; +#endif + +class SocketRecvFromBuffer : public SocketRecvBuffer { +public: + SocketRecvFromBuffer(size_t size): _size(size) {} + + ssize_t recvFromSocket(int fd, ssize_t &count) override { + ssize_t nread; + socklen_t len = sizeof(_address); + if (!_buffer) { + allocBuffer(); + } + + do { + nread = recvfrom(fd, _buffer->data(), _buffer->getCapacity() - 1, 0, (struct sockaddr *)&_address, &len); + } while (-1 == nread && UV_EINTR == get_uv_error(true)); + + if (nread > 0) { + count = 1; + _buffer->data()[nread] = '\0'; + std::static_pointer_cast(_buffer)->setSize(nread); + } + return nread; + } + + Buffer::Ptr &getBuffer(size_t index) override { return _buffer; } + + struct sockaddr_storage &getAddress(size_t index) override { return _address; } + +private: + void allocBuffer() { + auto buf = BufferRaw::create(); + buf->setCapacity(_size); + _buffer = std::move(buf); + } + +private: + size_t _size; + Buffer::Ptr _buffer; + struct sockaddr_storage _address; +}; + +static constexpr auto kPacketCount = 32; +static constexpr auto kBufferCapacity = 4 * 1024u; + +SocketRecvBuffer::Ptr SocketRecvBuffer::create(bool is_udp) { +#if defined(__linux) || defined(__linux__) + if (is_udp) { + return std::make_shared(kPacketCount, kBufferCapacity); + } +#endif + return std::make_shared(kPacketCount * kBufferCapacity); +} + +} //toolkit diff --git a/ToolKit/Network/BufferSock.h b/ToolKit/Network/BufferSock.h new file mode 100644 index 0000000..d6da444 --- /dev/null +++ b/ToolKit/Network/BufferSock.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_BUFFERSOCK_H +#define ZLTOOLKIT_BUFFERSOCK_H + +#if !defined(_WIN32) +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include "Util/util.h" +#include "Util/List.h" +#include "Util/ResourcePool.h" +#include "sockutil.h" +#include "Buffer.h" + +namespace toolkit { + +#if !defined(IOV_MAX) +#define IOV_MAX 1024 +#endif + +class BufferSock : public Buffer { +public: + using Ptr = std::shared_ptr; + BufferSock(Buffer::Ptr ptr, struct sockaddr *addr = nullptr, int addr_len = 0); + ~BufferSock() override = default; + + char *data() const override; + size_t size() const override; + const struct sockaddr *sockaddr() const; + socklen_t socklen() const; + +private: + int _addr_len = 0; + struct sockaddr_storage _addr; + Buffer::Ptr _buffer; +}; + +class BufferList : public noncopyable { +public: + using Ptr = std::shared_ptr; + using SendResult = std::function; + + BufferList() = default; + virtual ~BufferList() = default; + + virtual bool empty() = 0; + virtual size_t count() = 0; + virtual ssize_t send(int fd, int flags) = 0; + + static Ptr create(List > list, SendResult cb, bool is_udp); + +private: + //对象个数统计 + ObjectStatistic _statistic; +}; + +class SocketRecvBuffer { +public: + using Ptr = std::shared_ptr; + + virtual ~SocketRecvBuffer() = default; + + virtual ssize_t recvFromSocket(int fd, ssize_t &count) = 0; + virtual Buffer::Ptr &getBuffer(size_t index) = 0; + virtual struct sockaddr_storage &getAddress(size_t index) = 0; + + static Ptr create(bool is_udp); +}; + +} +#endif //ZLTOOLKIT_BUFFERSOCK_H diff --git a/ToolKit/Network/Server.cpp b/ToolKit/Network/Server.cpp new file mode 100644 index 0000000..155a3c9 --- /dev/null +++ b/ToolKit/Network/Server.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Server.h" + +using namespace std; + +namespace toolkit { + +Server::Server(EventPoller::Ptr poller) { + _poller = poller ? std::move(poller) : EventPollerPool::Instance().getPoller(); +} + +//////////////////////////////////////////////////////////////////////////////////// + +SessionHelper::SessionHelper(const std::weak_ptr &server, Session::Ptr session, std::string cls) { + _server = server; + _session = std::move(session); + _cls = std::move(cls); + //记录session至全局的map,方便后面管理 + _session_map = SessionMap::Instance().shared_from_this(); + _identifier = _session->getIdentifier(); + _session_map->add(_identifier, _session); +} + +SessionHelper::~SessionHelper() { + if (!_server.lock()) { + //务必通知Session已从TcpServer脱离 + _session->onError(SockException(Err_other, "Server shutdown")); + } + //从全局map移除相关记录 + _session_map->del(_identifier); +} + +const Session::Ptr &SessionHelper::session() const { + return _session; +} + +const std::string &SessionHelper::className() const { + return _cls; +} + +//////////////////////////////////////////////////////////////////////////////////// + +bool SessionMap::add(const string &tag, const Session::Ptr &session) { + lock_guard lck(_mtx_session); + return _map_session.emplace(tag, session).second; +} + +bool SessionMap::del(const string &tag) { + lock_guard lck(_mtx_session); + return _map_session.erase(tag); +} + +Session::Ptr SessionMap::get(const string &tag) { + lock_guard lck(_mtx_session); + auto it = _map_session.find(tag); + if (it == _map_session.end()) { + return nullptr; + } + return it->second.lock(); +} + +void SessionMap::for_each_session(const function &cb) { + lock_guard lck(_mtx_session); + for (auto it = _map_session.begin(); it != _map_session.end();) { + auto session = it->second.lock(); + if (!session) { + it = _map_session.erase(it); + continue; + } + cb(it->first, session); + ++it; + } +} + +} // namespace toolkit \ No newline at end of file diff --git a/ToolKit/Network/Server.h b/ToolKit/Network/Server.h new file mode 100644 index 0000000..12e49c6 --- /dev/null +++ b/ToolKit/Network/Server.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_SERVER_H +#define ZLTOOLKIT_SERVER_H + +#include +#include "Util/mini.h" +#include "Session.h" + +namespace toolkit { + +// 全局的 Session 记录对象, 方便后面管理 +// 线程安全的 +class SessionMap : public std::enable_shared_from_this { +public: + friend class SessionHelper; + using Ptr = std::shared_ptr; + + //单例 + static SessionMap &Instance(); + ~SessionMap() = default; + + //获取Session + Session::Ptr get(const std::string &tag); + void for_each_session(const std::function &cb); + +private: + SessionMap() = default; + + //移除Session + bool del(const std::string &tag); + //添加Session + bool add(const std::string &tag, const Session::Ptr &session); + +private: + std::mutex _mtx_session; + std::unordered_map > _map_session; +}; + +class Server; + +class SessionHelper { +public: + bool enable = true; + + using Ptr = std::shared_ptr; + + SessionHelper(const std::weak_ptr &server, Session::Ptr session, std::string cls); + ~SessionHelper(); + + const Session::Ptr &session() const; + const std::string &className() const; + +private: + std::string _cls; + std::string _identifier; + Session::Ptr _session; + SessionMap::Ptr _session_map; + std::weak_ptr _server; +}; + +// server 基类, 暂时仅用于剥离 SessionHelper 对 TcpServer 的依赖 +// 后续将 TCP 与 UDP 服务通用部分加到这里. +class Server : public std::enable_shared_from_this, public mINI { +public: + using Ptr = std::shared_ptr; + + explicit Server(EventPoller::Ptr poller = nullptr); + virtual ~Server() = default; + +protected: + EventPoller::Ptr _poller; +}; + +} // namespace toolkit + +#endif // ZLTOOLKIT_SERVER_H \ No newline at end of file diff --git a/ToolKit/Network/Session.cpp b/ToolKit/Network/Session.cpp new file mode 100644 index 0000000..cad48a6 --- /dev/null +++ b/ToolKit/Network/Session.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "Session.h" + +using namespace std; + +namespace toolkit { + +class TcpSession : public Session {}; +class UdpSession : public Session {}; + +StatisticImp(UdpSession) +StatisticImp(TcpSession) + +Session::Session(const Socket::Ptr &sock) : SocketHelper(sock) { + if (sock->sockType() == SockNum::Sock_TCP) { + _statistic_tcp.reset(new ObjectStatistic); + } else { + _statistic_udp.reset(new ObjectStatistic); + } +} + +string Session::getIdentifier() const { + if (_id.empty()) { + static atomic s_session_index{0}; + _id = to_string(++s_session_index) + '-' + to_string(getSock()->rawFD()); + } + return _id; +} + +} // namespace toolkit diff --git a/ToolKit/Network/Session.h b/ToolKit/Network/Session.h new file mode 100644 index 0000000..3b8a971 --- /dev/null +++ b/ToolKit/Network/Session.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_SESSION_H +#define ZLTOOLKIT_SESSION_H + +#include +#include "Socket.h" +#include "Util/util.h" +#include "Util/SSLBox.h" + +namespace toolkit { + +// 会话, 用于存储一对客户端与服务端间的关系 +class Server; +class TcpSession; +class UdpSession; + +class Session : public SocketHelper { +public: + using Ptr = std::shared_ptr; + + Session(const Socket::Ptr &sock); + ~Session() override = default; + + /** + * 在创建 Session 后, Server 会把自身的配置参数通过该函数传递给 Session + * @param server, 服务器对象 + */ + virtual void attachServer(const Server &server) {} + + /** + * 作为该 Session 的唯一标识符 + * @return 唯一标识符 + */ + std::string getIdentifier() const override; + +private: + mutable std::string _id; + std::unique_ptr > _statistic_tcp; + std::unique_ptr > _statistic_udp; +}; + +// 通过该模板可以让TCP服务器快速支持TLS +template +class SessionWithSSL : public SessionType { +public: + template + SessionWithSSL(ArgsType &&...args) + : SessionType(std::forward(args)...) { + _ssl_box.setOnEncData([&](const Buffer::Ptr &buf) { public_send(buf); }); + _ssl_box.setOnDecData([&](const Buffer::Ptr &buf) { public_onRecv(buf); }); + } + + ~SessionWithSSL() override { _ssl_box.flush(); } + + void onRecv(const Buffer::Ptr &buf) override { _ssl_box.onRecv(buf); } + + // 添加public_onRecv和public_send函数是解决较低版本gcc一个lambad中不能访问protected或private方法的bug + inline void public_onRecv(const Buffer::Ptr &buf) { SessionType::onRecv(buf); } + inline void public_send(const Buffer::Ptr &buf) { SessionType::send(buf); } + + bool overSsl() const override { return true; } + +protected: + ssize_t send(Buffer::Ptr buf) override { + auto size = buf->size(); + _ssl_box.onSend(std::move(buf)); + return size; + } + +private: + SSL_Box _ssl_box; +}; + +} // namespace toolkit + +#endif // ZLTOOLKIT_SESSION_H \ No newline at end of file diff --git a/ToolKit/Network/Socket.cpp b/ToolKit/Network/Socket.cpp new file mode 100644 index 0000000..64a038f --- /dev/null +++ b/ToolKit/Network/Socket.cpp @@ -0,0 +1,1049 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "sockutil.h" +#include "Socket.h" +#include "Util/util.h" +#include "Util/logger.h" +#include "Util/uv_errno.h" +#include "Thread/semaphore.h" +#include "Poller/EventPoller.h" +#include "Thread/WorkThreadPool.h" +using namespace std; + +#define LOCK_GUARD(mtx) lock_guard lck(mtx) + +namespace toolkit { + +StatisticImp(Socket) + +static SockException toSockException(int error) { + switch (error) { + case 0: + case UV_EAGAIN: return SockException(Err_success, "success"); + case UV_ECONNREFUSED: return SockException(Err_refused, uv_strerror(error), error); + case UV_ETIMEDOUT: return SockException(Err_timeout, uv_strerror(error), error); + case UV_ECONNRESET: return SockException(Err_reset, uv_strerror(error), error); + default: return SockException(Err_other, uv_strerror(error), error); + } +} + +static SockException getSockErr(int sock, bool try_errno = true) { + int error = 0, len = sizeof(int); + getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, (socklen_t *)&len); + if (error == 0) { + if (try_errno) { + error = get_uv_error(true); + } + } else { + error = uv_translate_posix_error(error); + } + return toSockException(error); +} + +Socket::Ptr Socket::createSocket(const EventPoller::Ptr &poller_in, bool enable_mutex) { + auto poller = poller_in ? poller_in : EventPollerPool::Instance().getPoller(); + std::weak_ptr weak_poller = poller; + return Socket::Ptr(new Socket(poller, enable_mutex), [weak_poller](Socket *ptr) { + if (auto poller = weak_poller.lock()) { + poller->async([ptr]() { delete ptr; }); + } else { + delete ptr; + } + }); +} + +Socket::Socket(EventPoller::Ptr poller, bool enable_mutex) + : _poller(std::move(poller)) + , _mtx_sock_fd(enable_mutex) + , _mtx_event(enable_mutex) + , _mtx_send_buf_waiting(enable_mutex) + , _mtx_send_buf_sending(enable_mutex) { + setOnRead(nullptr); + setOnErr(nullptr); + setOnAccept(nullptr); + setOnFlush(nullptr); + setOnBeforeAccept(nullptr); + setOnSendResult(nullptr); +} + +Socket::~Socket() { + closeSock(); +} + +void Socket::setOnRead(onReadCB cb) { + onMultiReadCB cb2; + if (cb) { + cb2 = [cb](Buffer::Ptr *buf, struct sockaddr_storage *addr, size_t count) { + for (auto i = 0u; i < count; ++i) { + cb(buf[i], (struct sockaddr *)(addr + i), sizeof(struct sockaddr_storage)); + } + }; + } + setOnMultiRead(std::move(cb2)); +} + +void Socket::setOnMultiRead(onMultiReadCB cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_multi_read = std::move(cb); + } else { + _on_multi_read = [](Buffer::Ptr *buf, struct sockaddr_storage *addr, size_t count) { + for (auto i = 0u; i < count; ++i) { + WarnL << "Socket not set read callback, data ignored: " << buf[i]->size(); + } + }; + } +} + +void Socket::setOnErr(onErrCB cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_err = std::move(cb); + } else { + _on_err = [](const SockException &err) { WarnL << "Socket not set err callback, err: " << err; }; + } +} + +void Socket::setOnAccept(onAcceptCB cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_accept = std::move(cb); + } else { + _on_accept = [](Socket::Ptr &sock, shared_ptr &complete) { WarnL << "Socket not set accept callback, peer fd: " << sock->rawFD(); }; + } +} + +void Socket::setOnFlush(onFlush cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_flush = std::move(cb); + } else { + _on_flush = []() { return true; }; + } +} + +void Socket::setOnBeforeAccept(onCreateSocket cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_before_accept = std::move(cb); + } else { + _on_before_accept = [](const EventPoller::Ptr &poller) { return nullptr; }; + } +} + +void Socket::setOnSendResult(onSendResult cb) { + LOCK_GUARD(_mtx_event); + _send_result = std::move(cb); +} + +void Socket::connect(const string &url, uint16_t port, const onErrCB &con_cb_in, float timeout_sec, const string &local_ip, uint16_t local_port) { + weak_ptr weak_self = shared_from_this(); + // 因为涉及到异步回调,所以在poller线程中执行确保线程安全 + _poller->async([=] { + if (auto strong_self = weak_self.lock()) { + strong_self->connect_l(url, port, con_cb_in, timeout_sec, local_ip, local_port); + } + }); +} + +void Socket::connect_l(const string &url, uint16_t port, const onErrCB &con_cb_in, float timeout_sec, const string &local_ip, uint16_t local_port) { + // 重置当前socket + closeSock(); + + weak_ptr weak_self = shared_from_this(); + auto con_cb = [con_cb_in, weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->_async_con_cb = nullptr; + strong_self->_con_timer = nullptr; + if (err) { + strong_self->setSock(nullptr); + } + con_cb_in(err); + }; + + auto async_con_cb = std::make_shared>([weak_self, con_cb](const SockNum::Ptr &sock) { + auto strong_self = weak_self.lock(); + if (!sock || !strong_self) { + con_cb(SockException(Err_dns, get_uv_errmsg(true))); + return; + } + + // 监听该socket是否可写,可写表明已经连接服务器成功 + int result = strong_self->_poller->addEvent(sock->rawFd(), EventPoller::Event_Write | EventPoller::Event_Error, [weak_self, sock, con_cb](int event) { + if (auto strong_self = weak_self.lock()) { + strong_self->onConnected(sock, con_cb); + } + }); + + if (result == -1) { + con_cb(SockException(Err_other, std::string("add event to poller failed when start connect:") + get_uv_errmsg())); + } else { + // 先创建SockFD对象,防止SockNum由于未执行delEvent无法析构 + strong_self->setSock(sock); + } + }); + + // 连接超时定时器 + _con_timer = std::make_shared(timeout_sec,[weak_self, con_cb]() { + con_cb(SockException(Err_timeout, uv_strerror(UV_ETIMEDOUT))); + return false; + }, _poller); + + if (isIP(url.data())) { + auto fd = SockUtil::connect(url.data(), port, true, local_ip.data(), local_port); + (*async_con_cb)(fd == -1 ? nullptr : std::make_shared(fd, SockNum::Sock_TCP)); + } else { + auto poller = _poller; + weak_ptr> weak_task = async_con_cb; + WorkThreadPool::Instance().getExecutor()->async([url, port, local_ip, local_port, weak_task, poller]() { + // 阻塞式dns解析放在后台线程执行 + int fd = SockUtil::connect(url.data(), port, true, local_ip.data(), local_port); + auto sock = fd == -1 ? nullptr : std::make_shared(fd, SockNum::Sock_TCP); + poller->async([sock, weak_task]() { + if (auto strong_task = weak_task.lock()) { + (*strong_task)(sock); + } + }); + }); + _async_con_cb = async_con_cb; + } +} + +void Socket::onConnected(const SockNum::Ptr &sock, const onErrCB &cb) { + auto err = getSockErr(sock->rawFd(), false); + if (err) { + // 连接失败 + cb(err); + return; + } + + // 更新地址信息 + setSock(sock); + // 先删除之前的可写事件监听 + _poller->delEvent(sock->rawFd(), [sock](bool) {}); + if (!attachEvent(sock)) { + // 连接失败 + cb(SockException(Err_other, "add event to poller failed when connected")); + return; + } + + { + LOCK_GUARD(_mtx_sock_fd); + if (_sock_fd) { + _sock_fd->setConnected(); + } + } + // 连接成功 + cb(err); +} + +bool Socket::attachEvent(const SockNum::Ptr &sock) { + weak_ptr weak_self = shared_from_this(); + if (sock->type() == SockNum::Sock_TCP_Server) { + // tcp服务器 + auto result = _poller->addEvent(sock->rawFd(), EventPoller::Event_Read | EventPoller::Event_Error, [weak_self, sock](int event) { + if (auto strong_self = weak_self.lock()) { + strong_self->onAccept(sock, event); + } + }); + return -1 != result; + } + + // tcp客户端或udp + auto read_buffer = _poller->getSharedBuffer(sock->type() == SockNum::Sock_UDP); + auto result = _poller->addEvent(sock->rawFd(), EventPoller::Event_Read | EventPoller::Event_Error | EventPoller::Event_Write, [weak_self, sock, read_buffer](int event) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + if (event & EventPoller::Event_Read) { + strong_self->onRead(sock, read_buffer); + } + if (event & EventPoller::Event_Write) { + strong_self->onWriteAble(sock); + } + if (event & EventPoller::Event_Error) { + strong_self->emitErr(getSockErr(sock->rawFd())); + } + }); + + return -1 != result; +} + +ssize_t Socket::onRead(const SockNum::Ptr &sock, const SocketRecvBuffer::Ptr &buffer) noexcept { + ssize_t ret = 0, nread = 0, count = 0; + + while (_enable_recv) { + nread = buffer->recvFromSocket(sock->rawFd(), count); + if (nread == 0) { + if (sock->type() == SockNum::Sock_TCP) { + emitErr(SockException(Err_eof, "end of file")); + } else { + WarnL << "Recv eof on udp socket[" << sock->rawFd() << "]"; + } + return ret; + } + + if (nread == -1) { + auto err = get_uv_error(true); + if (err != UV_EAGAIN) { + if (sock->type() == SockNum::Sock_TCP) { + emitErr(toSockException(err)); + } else { + WarnL << "Recv err on udp socket[" << sock->rawFd() << "]: " << uv_strerror(err); + } + } + return ret; + } + + ret += nread; + if (_enable_speed) { + // 更新接收速率 + _recv_speed += nread; + } + + auto &buf = buffer->getBuffer(0); + auto &addr = buffer->getAddress(0); + try { + // 此处捕获异常,目的是防止数据未读尽,epoll边沿触发失效的问题 + LOCK_GUARD(_mtx_event); + _on_multi_read(&buf, &addr, count); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_read: " << ex.what(); + } + } + return 0; +} + +bool Socket::emitErr(const SockException &err) noexcept { + if (_err_emit) { + return true; + } + _err_emit = true; + weak_ptr weak_self = shared_from_this(); + _poller->async([weak_self, err]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + LOCK_GUARD(strong_self->_mtx_event); + try { + strong_self->_on_err(err); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_err: " << ex.what(); + } + // 延后关闭socket,只移除其io事件,防止Session对象析构时获取fd相关信息失败 + strong_self->closeSock(false); + }); + return true; +} + +ssize_t Socket::send(const char *buf, size_t size, struct sockaddr *addr, socklen_t addr_len, bool try_flush) { + if (size <= 0) { + size = strlen(buf); + if (!size) { + return 0; + } + } + auto ptr = BufferRaw::create(); + ptr->assign(buf, size); + return send(std::move(ptr), addr, addr_len, try_flush); +} + +ssize_t Socket::send(string buf, struct sockaddr *addr, socklen_t addr_len, bool try_flush) { + return send(std::make_shared(std::move(buf)), addr, addr_len, try_flush); +} + +ssize_t Socket::send(Buffer::Ptr buf, struct sockaddr *addr, socklen_t addr_len, bool try_flush) { + if (!addr) { + if (!_udp_send_dst) { + return send_l(std::move(buf), false, try_flush); + } + // 本次发送未指定目标地址,但是目标定制已通过bindPeerAddr指定 + addr = (struct sockaddr *)_udp_send_dst.get(); + addr_len = SockUtil::get_sock_len(addr); + } + return send_l(std::make_shared(std::move(buf), addr, addr_len), true, try_flush); +} + +ssize_t Socket::send_l(Buffer::Ptr buf, bool is_buf_sock, bool try_flush) { + auto size = buf ? buf->size() : 0; + if (!size) { + return 0; + } + + { + LOCK_GUARD(_mtx_send_buf_waiting); + _send_buf_waiting.emplace_back(std::move(buf), is_buf_sock); + } + + if (try_flush) { + if (flushAll()) { + return -1; + } + } + + return size; +} + +int Socket::flushAll() { + LOCK_GUARD(_mtx_sock_fd); + + if (!_sock_fd) { + // 如果已断开连接或者发送超时 + return -1; + } + if (_sendable) { + // 该socket可写 + return flushData(_sock_fd->sockNum(), false) ? 0 : -1; + } + + // 该socket不可写,判断发送超时 + if (_send_flush_ticker.elapsedTime() > _max_send_buffer_ms) { + // 如果发送列队中最老的数据距今超过超时时间限制,那么就断开socket连接 + emitErr(SockException(Err_other, "socket send timeout")); + return -1; + } + return 0; +} + +void Socket::onFlushed() { + bool flag; + { + LOCK_GUARD(_mtx_event); + flag = _on_flush(); + } + if (!flag) { + setOnFlush(nullptr); + } +} + +void Socket::closeSock(bool close_fd) { + _sendable = true; + _enable_recv = true; + _enable_speed = false; + _con_timer = nullptr; + _async_con_cb = nullptr; + _send_flush_ticker.resetTime(); + + { + LOCK_GUARD(_mtx_send_buf_waiting); + _send_buf_waiting.clear(); + } + + { + LOCK_GUARD(_mtx_send_buf_sending); + _send_buf_sending.clear(); + } + + { + LOCK_GUARD(_mtx_sock_fd); + if (close_fd) { + _err_emit = false; + _sock_fd = nullptr; + } else if (_sock_fd) { + _sock_fd->delEvent(); + } + } +} + +size_t Socket::getSendBufferCount() { + size_t ret = 0; + { + LOCK_GUARD(_mtx_send_buf_waiting); + ret += _send_buf_waiting.size(); + } + + { + LOCK_GUARD(_mtx_send_buf_sending); + _send_buf_sending.for_each([&](BufferList::Ptr &buf) { ret += buf->count(); }); + } + return ret; +} + +uint64_t Socket::elapsedTimeAfterFlushed() { + return _send_flush_ticker.elapsedTime(); +} + +int Socket::getRecvSpeed() { + _enable_speed = true; + return _recv_speed.getSpeed(); +} + +int Socket::getSendSpeed() { + _enable_speed = true; + return _send_speed.getSpeed(); +} + +bool Socket::listen(uint16_t port, const string &local_ip, int backlog) { + closeSock(); + int fd = SockUtil::listen(port, local_ip.data(), backlog); + if (fd == -1) { + return false; + } + return fromSock_l(std::make_shared(fd, SockNum::Sock_TCP_Server)); +} + +bool Socket::bindUdpSock(uint16_t port, const string &local_ip, bool enable_reuse) { + closeSock(); + int fd = SockUtil::bindUdpSock(port, local_ip.data(), enable_reuse); + if (fd == -1) { + return false; + } + return fromSock_l(std::make_shared(fd, SockNum::Sock_UDP)); +} + +bool Socket::fromSock(int fd, SockNum::SockType type) { + closeSock(); + SockUtil::setNoSigpipe(fd); + SockUtil::setNoBlocked(fd); + SockUtil::setCloExec(fd); + return fromSock_l(std::make_shared(fd, type)); +} + +bool Socket::fromSock_l(SockNum::Ptr sock) { + if (!attachEvent(sock)) { + return false; + } + setSock(std::move(sock)); + return true; +} + +int Socket::onAccept(const SockNum::Ptr &sock, int event) noexcept { + int fd; + struct sockaddr_storage peer_addr; + socklen_t addr_len = sizeof(peer_addr); + while (true) { + if (event & EventPoller::Event_Read) { + do { + fd = (int)accept(sock->rawFd(), (struct sockaddr *)&peer_addr, &addr_len); + } while (-1 == fd && UV_EINTR == get_uv_error(true)); + + if (fd == -1) { + // accept失败 + int err = get_uv_error(true); + if (err == UV_EAGAIN) { + // 没有新连接 + return 0; + } + auto ex = toSockException(err); + // emitErr(ex); https://github.com/ZLMediaKit/ZLMediaKit/issues/2946 + ErrorL << "Accept socket failed: " << ex.what(); + // 可能打开的文件描述符太多了:UV_EMFILE/UV_ENFILE +#if (defined(HAS_EPOLL) && !defined(_WIN32)) || defined(HAS_KQUEUE) + // 边缘触发,还需要手动再触发accept事件, + // wepoll, Edge-triggered (`EPOLLET`) mode isn't supported. + std::weak_ptr weak_self = shared_from_this(); + _poller->doDelayTask(100, [weak_self, sock]() { + if (auto strong_self = weak_self.lock()) { + // 100ms后再处理accept事件,说不定已经有空闲的fd + strong_self->onAccept(sock, EventPoller::Event_Read); + } + return 0; + }); + // 暂时不处理accept事件,等待100ms后手动触发onAccept(只有EAGAIN读空后才能通过epoll再次触发事件) + return -1; +#else + // 水平触发;休眠10ms,防止无谓的accept失败 + this_thread::sleep_for(std::chrono::milliseconds(10)); + // 暂时不处理accept事件,由于是水平触发,下次还会再次自动进入onAccept函数 + return -1; +#endif + } + + SockUtil::setNoSigpipe(fd); + SockUtil::setNoBlocked(fd); + SockUtil::setNoDelay(fd); + SockUtil::setSendBuf(fd); + SockUtil::setRecvBuf(fd); + SockUtil::setCloseWait(fd); + SockUtil::setCloExec(fd); + + Socket::Ptr peer_sock; + try { + // 此处捕获异常,目的是防止socket未accept尽,epoll边沿触发失效的问题 + LOCK_GUARD(_mtx_event); + // 拦截Socket对象的构造 + peer_sock = _on_before_accept(_poller); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_before_accept: " << ex.what(); + close(fd); + continue; + } + + if (!peer_sock) { + // 此处是默认构造行为,也就是子Socket共用父Socket的poll线程并且关闭互斥锁 + peer_sock = Socket::createSocket(_poller, false); + } + + auto sock = std::make_shared(fd, SockNum::Sock_TCP); + // 设置好fd,以备在onAccept事件中可以正常访问该fd + peer_sock->setSock(sock); + // 赋值peer ip,防止在执行setSock时,fd已经被reset断开 + memcpy(&peer_sock->_peer_addr, &peer_addr, addr_len); + + shared_ptr completed(nullptr, [peer_sock, sock](void *) { + try { + // 然后把该fd加入poll监听(确保先触发onAccept事件然后再触发onRead等事件) + if (!peer_sock->attachEvent(sock)) { + // 加入poll监听失败,触发onErr事件,通知该Socket无效 + peer_sock->emitErr(SockException(Err_eof, "add event to poller failed when accept a socket")); + } + } catch (std::exception &ex) { + ErrorL << "Exception occurred: " << ex.what(); + } + }); + + try { + // 此处捕获异常,目的是防止socket未accept尽,epoll边沿触发失效的问题 + LOCK_GUARD(_mtx_event); + // 先触发onAccept事件,此时应该监听该Socket的onRead等事件 + _on_accept(peer_sock, completed); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_accept: " << ex.what(); + continue; + } + } + + if (event & EventPoller::Event_Error) { + auto ex = getSockErr(sock->rawFd()); + emitErr(ex); + ErrorL << "TCP listener occurred a err: " << ex.what(); + return -1; + } + } +} + +void Socket::setSock(SockNum::Ptr sock) { + LOCK_GUARD(_mtx_sock_fd); + if (sock) { + _sock_fd = std::make_shared(std::move(sock), _poller); + SockUtil::get_sock_local_addr(_sock_fd->rawFd(), _local_addr); + SockUtil::get_sock_peer_addr(_sock_fd->rawFd(), _peer_addr); + } else { + _sock_fd = nullptr; + } +} + +string Socket::get_local_ip() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return ""; + } + return SockUtil::inet_ntoa((struct sockaddr *)&_local_addr); +} + +uint16_t Socket::get_local_port() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return 0; + } + return SockUtil::inet_port((struct sockaddr *)&_local_addr); +} + +string Socket::get_peer_ip() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return ""; + } + if (_udp_send_dst) { + return SockUtil::inet_ntoa((struct sockaddr *)_udp_send_dst.get()); + } + return SockUtil::inet_ntoa((struct sockaddr *)&_peer_addr); +} + +uint16_t Socket::get_peer_port() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return 0; + } + if (_udp_send_dst) { + return SockUtil::inet_port((struct sockaddr *)_udp_send_dst.get()); + } + return SockUtil::inet_port((struct sockaddr *)&_peer_addr); +} + +string Socket::getIdentifier() const { + static string class_name = "Socket: "; + return class_name + to_string(reinterpret_cast(this)); +} + +bool Socket::flushData(const SockNum::Ptr &sock, bool poller_thread) { + decltype(_send_buf_sending) send_buf_sending_tmp; + { + // 转移出二级缓存 + LOCK_GUARD(_mtx_send_buf_sending); + if (!_send_buf_sending.empty()) { + send_buf_sending_tmp.swap(_send_buf_sending); + } + } + + if (send_buf_sending_tmp.empty()) { + _send_flush_ticker.resetTime(); + do { + { + // 二级发送缓存为空,那么我们接着消费一级缓存中的数据 + LOCK_GUARD(_mtx_send_buf_waiting); + if (!_send_buf_waiting.empty()) { + // 把一级缓中数数据放置到二级缓存中并清空 + LOCK_GUARD(_mtx_event); + auto send_result = _enable_speed ? [this](const Buffer::Ptr &buffer, bool send_success) { + if (send_success) { + //更新发送速率 + _send_speed += buffer->size(); + } + LOCK_GUARD(_mtx_event); + if (_send_result) { + _send_result(buffer, send_success); + } + } : _send_result; + send_buf_sending_tmp.emplace_back(BufferList::create(std::move(_send_buf_waiting), std::move(send_result), sock->type() == SockNum::Sock_UDP)); + break; + } + } + // 如果一级缓存也为空,那么说明所有数据均写入socket了 + if (poller_thread) { + // poller线程触发该函数,那么该socket应该已经加入了可写事件的监听; + // 那么在数据列队清空的情况下,我们需要关闭监听以免触发无意义的事件回调 + stopWriteAbleEvent(sock); + onFlushed(); + } + return true; + } while (false); + } + + while (!send_buf_sending_tmp.empty()) { + auto &packet = send_buf_sending_tmp.front(); + auto n = packet->send(sock->rawFd(), _sock_flags); + if (n > 0) { + // 全部或部分发送成功 + if (packet->empty()) { + // 全部发送成功 + send_buf_sending_tmp.pop_front(); + continue; + } + // 部分发送成功 + if (!poller_thread) { + // 如果该函数是poller线程触发的,那么该socket应该已经加入了可写事件的监听,所以我们不需要再次加入监听 + startWriteAbleEvent(sock); + } + break; + } + + // 一个都没发送成功 + int err = get_uv_error(true); + if (err == UV_EAGAIN) { + // 等待下一次发送 + if (!poller_thread) { + // 如果该函数是poller线程触发的,那么该socket应该已经加入了可写事件的监听,所以我们不需要再次加入监听 + startWriteAbleEvent(sock); + } + break; + } + + // 其他错误代码,发生异常 + if (sock->type() == SockNum::Sock_UDP) { + // udp发送异常,把数据丢弃 + send_buf_sending_tmp.pop_front(); + WarnL << "Send udp socket[" << sock << "] failed, data ignored: " << uv_strerror(err); + continue; + } + // tcp发送失败时,触发异常 + emitErr(toSockException(err)); + return false; + } + + // 回滚未发送完毕的数据 + if (!send_buf_sending_tmp.empty()) { + // 有剩余数据 + LOCK_GUARD(_mtx_send_buf_sending); + send_buf_sending_tmp.swap(_send_buf_sending); + _send_buf_sending.append(send_buf_sending_tmp); + // 二级缓存未全部发送完毕,说明该socket不可写,直接返回 + return true; + } + + // 二级缓存已经全部发送完毕,说明该socket还可写,我们尝试继续写 + // 如果是poller线程,我们尝试再次写一次(因为可能其他线程调用了send函数又有新数据了) + return poller_thread ? flushData(sock, poller_thread) : true; +} + +void Socket::onWriteAble(const SockNum::Ptr &sock) { + bool empty_waiting; + bool empty_sending; + { + LOCK_GUARD(_mtx_send_buf_waiting); + empty_waiting = _send_buf_waiting.empty(); + } + + { + LOCK_GUARD(_mtx_send_buf_sending); + empty_sending = _send_buf_sending.empty(); + } + + if (empty_waiting && empty_sending) { + // 数据已经清空了,我们停止监听可写事件 + stopWriteAbleEvent(sock); + } else { + // socket可写,我们尝试发送剩余的数据 + flushData(sock, true); + } +} + +void Socket::startWriteAbleEvent(const SockNum::Ptr &sock) { + // 开始监听socket可写事件 + _sendable = false; + int flag = _enable_recv ? EventPoller::Event_Read : 0; + _poller->modifyEvent(sock->rawFd(), flag | EventPoller::Event_Error | EventPoller::Event_Write, [sock](bool) {}); +} + +void Socket::stopWriteAbleEvent(const SockNum::Ptr &sock) { + // 停止监听socket可写事件 + _sendable = true; + int flag = _enable_recv ? EventPoller::Event_Read : 0; + _poller->modifyEvent(sock->rawFd(), flag | EventPoller::Event_Error, [sock](bool) {}); +} + +void Socket::enableRecv(bool enabled) { + if (_enable_recv == enabled) { + return; + } + _enable_recv = enabled; + int read_flag = _enable_recv ? EventPoller::Event_Read : 0; + // 可写时,不监听可写事件 + int send_flag = _sendable ? 0 : EventPoller::Event_Write; + _poller->modifyEvent(rawFD(), read_flag | send_flag | EventPoller::Event_Error); +} + +int Socket::rawFD() const { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return -1; + } + return _sock_fd->rawFd(); +} + +bool Socket::alive() const { + LOCK_GUARD(_mtx_sock_fd); + return _sock_fd && !_err_emit; +} + +SockNum::SockType Socket::sockType() const { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return SockNum::Sock_Invalid; + } + return _sock_fd->type(); +} + +void Socket::setSendTimeOutSecond(uint32_t second) { + _max_send_buffer_ms = second * 1000; +} + +bool Socket::isSocketBusy() const { + return !_sendable.load(); +} + +const EventPoller::Ptr &Socket::getPoller() const { + return _poller; +} + +bool Socket::cloneSocket(const Socket &other) { + closeSock(); + SockNum::Ptr sock; + { + LOCK_GUARD(other._mtx_sock_fd); + if (!other._sock_fd) { + WarnL << "sockfd of src socket is null"; + return false; + } + sock = other._sock_fd->sockNum(); + } + return fromSock_l(sock); +} + +bool Socket::bindPeerAddr(const struct sockaddr *dst_addr, socklen_t addr_len, bool soft_bind) { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return false; + } + if (_sock_fd->type() != SockNum::Sock_UDP) { + return false; + } + addr_len = addr_len ? addr_len : SockUtil::get_sock_len(dst_addr); + if (soft_bind) { + // 软绑定,只保存地址 + _udp_send_dst = std::make_shared(); + memcpy(_udp_send_dst.get(), dst_addr, addr_len); + } else { + // 硬绑定后,取消软绑定,防止memcpy目标地址的性能损失 + _udp_send_dst = nullptr; + if (-1 == ::connect(_sock_fd->rawFd(), dst_addr, addr_len)) { + WarnL << "Connect socket to peer address failed: " << SockUtil::inet_ntoa(dst_addr); + return false; + } + memcpy(&_peer_addr, dst_addr, addr_len); + } + return true; +} + +void Socket::setSendFlags(int flags) { + _sock_flags = flags; +} + +///////////////SockSender/////////////////// + +SockSender &SockSender::operator<<(const char *buf) { + send(buf); + return *this; +} + +SockSender &SockSender::operator<<(string buf) { + send(std::move(buf)); + return *this; +} + +SockSender &SockSender::operator<<(Buffer::Ptr buf) { + send(std::move(buf)); + return *this; +} + +ssize_t SockSender::send(string buf) { + return send(std::make_shared(std::move(buf))); +} + +ssize_t SockSender::send(const char *buf, size_t size) { + auto buffer = BufferRaw::create(); + buffer->assign(buf, size); + return send(std::move(buffer)); +} + +///////////////SocketHelper/////////////////// + +SocketHelper::SocketHelper(const Socket::Ptr &sock) { + setSock(sock); + setOnCreateSocket(nullptr); +} + +void SocketHelper::setPoller(const EventPoller::Ptr &poller) { + _poller = poller; +} + +void SocketHelper::setSock(const Socket::Ptr &sock) { + _sock = sock; + if (_sock) { + _poller = _sock->getPoller(); + } +} + +const EventPoller::Ptr &SocketHelper::getPoller() const { + assert(_poller); + return _poller; +} + +const Socket::Ptr &SocketHelper::getSock() const { + return _sock; +} + +int SocketHelper::flushAll() { + if (!_sock) { + return -1; + } + return _sock->flushAll(); +} + +ssize_t SocketHelper::send(Buffer::Ptr buf) { + if (!_sock) { + return -1; + } + return _sock->send(std::move(buf), nullptr, 0, _try_flush); +} + +void SocketHelper::shutdown(const SockException &ex) { + if (_sock) { + _sock->emitErr(ex); + } +} + +void SocketHelper::safeShutdown(const SockException &ex) { + std::weak_ptr weak_self = shared_from_this(); + async_first([weak_self, ex]() { + if (auto strong_self = weak_self.lock()) { + strong_self->shutdown(ex); + } + }); +} + +string SocketHelper::get_local_ip() { + return _sock ? _sock->get_local_ip() : ""; +} + +uint16_t SocketHelper::get_local_port() { + return _sock ? _sock->get_local_port() : 0; +} + +string SocketHelper::get_peer_ip() { + return _sock ? _sock->get_peer_ip() : ""; +} + +uint16_t SocketHelper::get_peer_port() { + return _sock ? _sock->get_peer_port() : 0; +} + +bool SocketHelper::isSocketBusy() const { + if (!_sock) { + return true; + } + return _sock->isSocketBusy(); +} + +Task::Ptr SocketHelper::async(TaskIn task, bool may_sync) { + return _poller->async(std::move(task), may_sync); +} + +Task::Ptr SocketHelper::async_first(TaskIn task, bool may_sync) { + return _poller->async_first(std::move(task), may_sync); +} + +void SocketHelper::setSendFlushFlag(bool try_flush) { + _try_flush = try_flush; +} + +void SocketHelper::setSendFlags(int flags) { + if (!_sock) { + return; + } + _sock->setSendFlags(flags); +} + +void SocketHelper::setOnCreateSocket(Socket::onCreateSocket cb) { + if (cb) { + _on_create_socket = std::move(cb); + } else { + _on_create_socket = [](const EventPoller::Ptr &poller) { return Socket::createSocket(poller, false); }; + } +} + +Socket::Ptr SocketHelper::createSocket() { + return _on_create_socket(_poller); +} + +std::ostream &operator<<(std::ostream &ost, const SockException &err) { + ost << err.getErrCode() << "(" << err.what() << ")"; + return ost; +} + +} // namespace toolkit diff --git a/ToolKit/Network/Socket.h b/ToolKit/Network/Socket.h new file mode 100644 index 0000000..2155eeb --- /dev/null +++ b/ToolKit/Network/Socket.h @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef NETWORK_SOCKET_H +#define NETWORK_SOCKET_H + +#include +#include +#include +#include +#include +#include +#include "Util/SpeedStatistic.h" +#include "sockutil.h" +#include "Poller/Timer.h" +#include "Poller/EventPoller.h" +#include "BufferSock.h" + +namespace toolkit { + +#if defined(MSG_NOSIGNAL) +#define FLAG_NOSIGNAL MSG_NOSIGNAL +#else +#define FLAG_NOSIGNAL 0 +#endif //MSG_NOSIGNAL + +#if defined(MSG_MORE) +#define FLAG_MORE MSG_MORE +#else +#define FLAG_MORE 0 +#endif //MSG_MORE + +#if defined(MSG_DONTWAIT) +#define FLAG_DONTWAIT MSG_DONTWAIT +#else +#define FLAG_DONTWAIT 0 +#endif //MSG_DONTWAIT + +//默认的socket flags:不触发SIGPIPE,非阻塞发送 +#define SOCKET_DEFAULE_FLAGS (FLAG_NOSIGNAL | FLAG_DONTWAIT ) + +//发送超时时间,如果在规定时间内一直没有发送数据成功,那么将触发onErr事件 +#define SEND_TIME_OUT_SEC 10 + +//错误类型枚举 +typedef enum { + Err_success = 0, //成功 success + Err_eof, //eof + Err_timeout, //超时 socket timeout + Err_refused,//连接被拒绝 socket refused + Err_reset,//连接被重置 socket reset + Err_dns,//dns解析失败 dns resolve failed + Err_shutdown,//主动关闭 socket shutdown + Err_other = 0xFF,//其他错误 other error +} ErrCode; + +//错误信息类 +class SockException : public std::exception { +public: + SockException(ErrCode code = Err_success, const std::string &msg = "", int custom_code = 0) { + _msg = msg; + _code = code; + _custom_code = custom_code; + } + + //重置错误 + void reset(ErrCode code, const std::string &msg, int custom_code = 0) { + _msg = msg; + _code = code; + _custom_code = custom_code; + } + + //错误提示 + const char *what() const noexcept override { + return _msg.c_str(); + } + + //错误代码 + ErrCode getErrCode() const { + return _code; + } + + //用户自定义错误代码 + int getCustomCode() const { + return _custom_code; + } + + //判断是否真的有错 + operator bool() const { + return _code != Err_success; + } + +private: + ErrCode _code; + int _custom_code = 0; + std::string _msg; +}; + +//std::cout等输出流可以直接输出SockException对象 +std::ostream &operator<<(std::ostream &ost, const SockException &err); + +class SockNum { +public: + using Ptr = std::shared_ptr; + + typedef enum { + Sock_Invalid = -1, + Sock_TCP = 0, + Sock_UDP = 1, + Sock_TCP_Server = 2 + } SockType; + + SockNum(int fd, SockType type) { + _fd = fd; + _type = type; + } + + ~SockNum() { +#if defined (OS_IPHONE) + unsetSocketOfIOS(_fd); +#endif //OS_IPHONE + // 停止socket收发能力 + #if defined(_WIN32) + ::shutdown(_fd, SD_BOTH); + #else + ::shutdown(_fd, SHUT_RDWR); + #endif + close(_fd); + } + + int rawFd() const { + return _fd; + } + + SockType type() { + return _type; + } + + void setConnected() { +#if defined (OS_IPHONE) + setSocketOfIOS(_fd); +#endif //OS_IPHONE + } + +#if defined (OS_IPHONE) +private: + void *readStream=nullptr; + void *writeStream=nullptr; + bool setSocketOfIOS(int socket); + void unsetSocketOfIOS(int socket); +#endif //OS_IPHONE + +private: + int _fd; + SockType _type; +}; + +//socket 文件描述符的包装 +//在析构时自动溢出监听并close套接字 +//防止描述符溢出 +class SockFD : public noncopyable { +public: + using Ptr = std::shared_ptr; + + /** + * 创建一个fd对象 + * @param num 文件描述符,int数字 + * @param poller 事件监听器 + */ + SockFD(SockNum::Ptr num, const EventPoller::Ptr &poller) { + _num = std::move(num); + _poller = poller; + } + + /** + * 复制一个fd对象 + * @param that 源对象 + * @param poller 事件监听器 + */ + SockFD(const SockFD &that, const EventPoller::Ptr &poller) { + _num = that._num; + _poller = poller; + if (_poller == that._poller) { + throw std::invalid_argument("Copy a SockFD with same poller"); + } + } + + ~SockFD() { delEvent(); } + + void delEvent() { + if (_poller) { + auto num = _num; + // 移除io事件成功后再close fd + _poller->delEvent(num->rawFd(), [num](bool) {}); + _poller = nullptr; + } + } + + void setConnected() { + _num->setConnected(); + } + + int rawFd() const { + return _num->rawFd(); + } + + const SockNum::Ptr& sockNum() const { + return _num; + } + + SockNum::SockType type() { + return _num->type(); + } + + const EventPoller::Ptr& getPoller() const { + return _poller; + } + +private: + SockNum::Ptr _num; + EventPoller::Ptr _poller; +}; + +template +class MutexWrapper { +public: + MutexWrapper(bool enable) { + _enable = enable; + } + + ~MutexWrapper() = default; + + inline void lock() { + if (_enable) { + _mtx.lock(); + } + } + + inline void unlock() { + if (_enable) { + _mtx.unlock(); + } + } + +private: + bool _enable; + Mtx _mtx; +}; + +class SockInfo { +public: + SockInfo() = default; + virtual ~SockInfo() = default; + + //获取本机ip + virtual std::string get_local_ip() = 0; + //获取本机端口号 + virtual uint16_t get_local_port() = 0; + //获取对方ip + virtual std::string get_peer_ip() = 0; + //获取对方端口号 + virtual uint16_t get_peer_port() = 0; + //获取标识符 + virtual std::string getIdentifier() const { return ""; } +}; + +#define TraceP(ptr) TraceL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define DebugP(ptr) DebugL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define InfoP(ptr) InfoL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define WarnP(ptr) WarnL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define ErrorP(ptr) ErrorL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " + +//异步IO Socket对象,包括tcp客户端、服务器和udp套接字 +class Socket : public std::enable_shared_from_this, public noncopyable, public SockInfo { +public: + using Ptr = std::shared_ptr; + //接收数据回调 + using onReadCB = std::function; + using onMultiReadCB = std::function; + + //发生错误回调 + using onErrCB = std::function; + //tcp监听接收到连接请求 + using onAcceptCB = std::function &complete)>; + //socket发送缓存清空事件,返回true代表下次继续监听该事件,否则停止 + using onFlush = std::function; + //在接收到连接请求前,拦截Socket默认生成方式 + using onCreateSocket = std::function; + //发送buffer成功与否回调 + using onSendResult = BufferList::SendResult; + + /** + * 构造socket对象,尚未有实质操作 + * @param poller 绑定的poller线程 + * @param enable_mutex 是否启用互斥锁(接口是否线程安全) + */ + static Ptr createSocket(const EventPoller::Ptr &poller = nullptr, bool enable_mutex = true); + ~Socket() override; + + /** + * 创建tcp客户端并异步连接服务器 + * @param url 目标服务器ip或域名 + * @param port 目标服务器端口 + * @param con_cb 结果回调 + * @param timeout_sec 超时时间 + * @param local_ip 绑定本地网卡ip + * @param local_port 绑定本地网卡端口号 + */ + void connect(const std::string &url, uint16_t port, const onErrCB &con_cb, float timeout_sec = 5, const std::string &local_ip = "::", uint16_t local_port = 0); + + /** + * 创建tcp监听服务器 + * @param port 监听端口,0则随机 + * @param local_ip 监听的网卡ip + * @param backlog tcp最大积压数 + * @return 是否成功 + */ + bool listen(uint16_t port, const std::string &local_ip = "::", int backlog = 1024); + + /** + * 创建udp套接字,udp是无连接的,所以可以作为服务器和客户端 + * @param port 绑定的端口为0则随机 + * @param local_ip 绑定的网卡ip + * @return 是否成功 + */ + bool bindUdpSock(uint16_t port, const std::string &local_ip = "::", bool enable_reuse = true); + + /** + * 包装外部fd,本对象负责close fd + * 内部会设置fd为NoBlocked,NoSigpipe,CloExec + * 其他设置需要自行使用SockUtil进行设置 + */ + bool fromSock(int fd, SockNum::SockType type); + + /** + * 从另外一个Socket克隆 + * 目的是一个socket可以被多个poller对象监听,提高性能或实现Socket归属线程的迁移 + * @param other 原始的socket对象 + * @return 是否成功 + */ + bool cloneSocket(const Socket &other); + + ////////////设置事件回调//////////// + + /** + * 设置数据接收回调,tcp或udp客户端有效 + * @param cb 回调对象 + */ + void setOnRead(onReadCB cb); + void setOnMultiRead(onMultiReadCB cb); + + /** + * 设置异常事件(包括eof等)回调 + * @param cb 回调对象 + */ + void setOnErr(onErrCB cb); + + /** + * 设置tcp监听接收到连接回调 + * @param cb 回调对象 + */ + void setOnAccept(onAcceptCB cb); + + /** + * 设置socket写缓存清空事件回调 + * 通过该回调可以实现发送流控 + * @param cb 回调对象 + */ + void setOnFlush(onFlush cb); + + /** + * 设置accept时,socket构造事件回调 + * @param cb 回调 + */ + void setOnBeforeAccept(onCreateSocket cb); + + /** + * 设置发送buffer结果回调 + * @param cb 回调 + */ + void setOnSendResult(onSendResult cb); + + ////////////发送数据相关接口//////////// + + /** + * 发送数据指针 + * @param buf 数据指针 + * @param size 数据长度 + * @param addr 目标地址 + * @param addr_len 目标地址长度 + * @param try_flush 是否尝试写socket + * @return -1代表失败(socket无效),0代表数据长度为0,否则返回数据长度 + */ + ssize_t send(const char *buf, size_t size = 0, struct sockaddr *addr = nullptr, socklen_t addr_len = 0, bool try_flush = true); + + /** + * 发送string + */ + ssize_t send(std::string buf, struct sockaddr *addr = nullptr, socklen_t addr_len = 0, bool try_flush = true); + + /** + * 发送Buffer对象,Socket对象发送数据的统一出口 + * socket对象发送数据的统一出口 + */ + ssize_t send(Buffer::Ptr buf, struct sockaddr *addr = nullptr, socklen_t addr_len = 0, bool try_flush = true); + + /** + * 尝试将所有数据写socket + * @return -1代表失败(socket无效或者发送超时),0代表成功? + */ + int flushAll(); + + /** + * 关闭socket且触发onErr回调,onErr回调将在poller线程中进行 + * @param err 错误原因 + * @return 是否成功触发onErr回调 + */ + bool emitErr(const SockException &err) noexcept; + + /** + * 关闭或开启数据接收 + * @param enabled 是否开启 + */ + void enableRecv(bool enabled); + + /** + * 获取裸文件描述符,请勿进行close操作(因为Socket对象会管理其生命周期) + * @return 文件描述符 + */ + int rawFD() const; + + /** + * tcp客户端是否处于连接状态 + * 支持Sock_TCP类型socket + */ + bool alive() const; + + /** + * 返回socket类型 + */ + SockNum::SockType sockType() const; + + /** + * 设置发送超时主动断开时间;默认10秒 + * @param second 发送超时数据,单位秒 + */ + void setSendTimeOutSecond(uint32_t second); + + /** + * 套接字是否忙,如果套接字写缓存已满则返回true + * @return 套接字是否忙 + */ + bool isSocketBusy() const; + + /** + * 获取poller线程对象 + * @return poller线程对象 + */ + const EventPoller::Ptr &getPoller() const; + + /** + * 绑定udp 目标地址,后续发送时就不用再单独指定了 + * @param dst_addr 目标地址 + * @param addr_len 目标地址长度 + * @param soft_bind 是否软绑定,软绑定时不调用udp connect接口,只保存目标地址信息,发送时再传递到sendto函数 + * @return 是否成功 + */ + bool bindPeerAddr(const struct sockaddr *dst_addr, socklen_t addr_len = 0, bool soft_bind = false); + + /** + * 设置发送flags + * @param flags 发送的flag + */ + void setSendFlags(int flags = SOCKET_DEFAULE_FLAGS); + + /** + * 关闭套接字 + * @param close_fd 是否关闭fd还是只移除io事件监听 + */ + void closeSock(bool close_fd = true); + + /** + * 获取发送缓存包个数(不是字节数) + */ + size_t getSendBufferCount(); + + /** + * 获取上次socket发送缓存清空至今的毫秒数,单位毫秒 + */ + uint64_t elapsedTimeAfterFlushed(); + + /** + * 获取接收速率,单位bytes/s + */ + int getRecvSpeed(); + + /** + * 获取发送速率,单位bytes/s + */ + int getSendSpeed(); + + ////////////SockInfo override//////////// + std::string get_local_ip() override; + uint16_t get_local_port() override; + std::string get_peer_ip() override; + uint16_t get_peer_port() override; + std::string getIdentifier() const override; + +private: + Socket(EventPoller::Ptr poller, bool enable_mutex = true); + + void setSock(SockNum::Ptr sock); + int onAccept(const SockNum::Ptr &sock, int event) noexcept; + ssize_t onRead(const SockNum::Ptr &sock, const SocketRecvBuffer::Ptr &buffer) noexcept; + void onWriteAble(const SockNum::Ptr &sock); + void onConnected(const SockNum::Ptr &sock, const onErrCB &cb); + void onFlushed(); + void startWriteAbleEvent(const SockNum::Ptr &sock); + void stopWriteAbleEvent(const SockNum::Ptr &sock); + bool flushData(const SockNum::Ptr &sock, bool poller_thread); + bool attachEvent(const SockNum::Ptr &sock); + ssize_t send_l(Buffer::Ptr buf, bool is_buf_sock, bool try_flush = true); + void connect_l(const std::string &url, uint16_t port, const onErrCB &con_cb_in, float timeout_sec, const std::string &local_ip, uint16_t local_port); + bool fromSock_l(SockNum::Ptr sock); + +private: + // send socket时的flag + int _sock_flags = SOCKET_DEFAULE_FLAGS; + // 最大发送缓存,单位毫秒,距上次发送缓存清空时间不能超过该参数 + uint32_t _max_send_buffer_ms = SEND_TIME_OUT_SEC * 1000; + // 控制是否接收监听socket可读事件,关闭后可用于流量控制 + std::atomic _enable_recv { true }; + // 标记该socket是否可写,socket写缓存满了就不可写 + std::atomic _sendable { true }; + // 是否已经触发err回调了 + bool _err_emit = false; + // 是否启用网速统计 + bool _enable_speed = false; + // udp发送目标地址 + std::shared_ptr _udp_send_dst; + + // 接收速率统计 + BytesSpeed _recv_speed; + // 发送速率统计 + BytesSpeed _send_speed; + + // tcp连接超时定时器 + Timer::Ptr _con_timer; + // tcp连接结果回调对象 + std::shared_ptr _async_con_cb; + + // 记录上次发送缓存(包括socket写缓存、应用层缓存)清空的计时器 + Ticker _send_flush_ticker; + // socket fd的抽象类 + SockFD::Ptr _sock_fd; + // 本socket绑定的poller线程,事件触发于此线程 + EventPoller::Ptr _poller; + // 跨线程访问_sock_fd时需要上锁 + mutable MutexWrapper _mtx_sock_fd; + + // socket异常事件(比如说断开) + onErrCB _on_err; + // 收到数据事件 + onMultiReadCB _on_multi_read; + // socket缓存清空事件(可用于发送流速控制) + onFlush _on_flush; + // tcp监听收到accept请求事件 + onAcceptCB _on_accept; + // tcp监听收到accept请求,自定义创建peer Socket事件(可以控制子Socket绑定到其他poller线程) + onCreateSocket _on_before_accept; + // 设置上述回调函数的锁 + MutexWrapper _mtx_event; + + // 一级发送缓存, socket可写时,会把一级缓存批量送入到二级缓存 + List> _send_buf_waiting; + // 一级发送缓存锁 + MutexWrapper _mtx_send_buf_waiting; + // 二级发送缓存, socket可写时,会把二级缓存批量写入到socket + List _send_buf_sending; + // 二级发送缓存锁 + MutexWrapper _mtx_send_buf_sending; + // 发送buffer结果回调 + BufferList::SendResult _send_result; + // 对象个数统计 + ObjectStatistic _statistic; + + // 链接缓存地址,防止tcp reset 导致无法获取对端的地址 + struct sockaddr_storage _local_addr; + struct sockaddr_storage _peer_addr; +}; + +class SockSender { +public: + SockSender() = default; + virtual ~SockSender() = default; + virtual ssize_t send(Buffer::Ptr buf) = 0; + virtual void shutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")) = 0; + + //发送char * + SockSender &operator << (const char *buf); + //发送字符串 + SockSender &operator << (std::string buf); + //发送Buffer对象 + SockSender &operator << (Buffer::Ptr buf); + + //发送其他类型是数据 + template + SockSender &operator << (T &&buf) { + std::ostringstream ss; + ss << std::forward(buf); + send(ss.str()); + return *this; + } + + ssize_t send(std::string buf); + ssize_t send(const char *buf, size_t size = 0); +}; + +//Socket对象的包装类 +class SocketHelper : public SockSender, public SockInfo, public TaskExecutorInterface, public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + SocketHelper(const Socket::Ptr &sock); + ~SocketHelper() override = default; + + ///////////////////// Socket util std::functions ///////////////////// + /** + * 获取poller线程 + */ + const EventPoller::Ptr& getPoller() const; + + /** + * 设置批量发送标记,用于提升性能 + * @param try_flush 批量发送标记 + */ + void setSendFlushFlag(bool try_flush); + + /** + * 设置socket发送flags + * @param flags socket发送flags + */ + void setSendFlags(int flags); + + /** + * 套接字是否忙,如果套接字写缓存已满则返回true + */ + bool isSocketBusy() const; + + /** + * 设置Socket创建器,自定义Socket创建方式 + * @param cb 创建器 + */ + void setOnCreateSocket(Socket::onCreateSocket cb); + + /** + * 创建socket对象 + */ + Socket::Ptr createSocket(); + + /** + * 获取socket对象 + */ + const Socket::Ptr &getSock() const; + + /** + * 尝试将所有数据写socket + * @return -1代表失败(socket无效或者发送超时),0代表成功? + */ + int flushAll(); + + /** + * 是否ssl加密 + */ + virtual bool overSsl() const { return false; } + + ///////////////////// SockInfo override ///////////////////// + std::string get_local_ip() override; + uint16_t get_local_port() override; + std::string get_peer_ip() override; + uint16_t get_peer_port() override; + + ///////////////////// TaskExecutorInterface override ///////////////////// + /** + * 任务切换到所属poller线程执行 + * @param task 任务 + * @param may_sync 是否运行同步执行任务 + */ + Task::Ptr async(TaskIn task, bool may_sync = true) override; + Task::Ptr async_first(TaskIn task, bool may_sync = true) override; + + ///////////////////// SockSender override ///////////////////// + + /** + * 使能 SockSender 其他未被重写的send重载函数 + */ + using SockSender::send; + + /** + * 统一发送数据的出口 + */ + ssize_t send(Buffer::Ptr buf) override; + + /** + * 触发onErr事件 + */ + void shutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")) override; + + /** + * 线程安全的脱离 Server 并触发 onError 事件 + * @param ex 触发 onError 事件的原因 + */ + void safeShutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")); + + ///////////////////// event functions ///////////////////// + /** + * 接收数据入口 + * @param buf 数据,可以重复使用内存区,不可被缓存使用 + */ + virtual void onRecv(const Buffer::Ptr &buf) = 0; + + /** + * 收到 eof 或其他导致脱离 Server 事件的回调 + * 收到该事件时, 该对象一般将立即被销毁 + * @param err 原因 + */ + virtual void onError(const SockException &err) = 0; + + /** + * 数据全部发送完毕后回调 + */ + virtual void onFlush() {} + + /** + * 每隔一段时间触发, 用来做超时管理 + */ + virtual void onManager() = 0; + +protected: + void setPoller(const EventPoller::Ptr &poller); + void setSock(const Socket::Ptr &sock); + +private: + bool _try_flush = true; + Socket::Ptr _sock; + EventPoller::Ptr _poller; + Socket::onCreateSocket _on_create_socket; +}; + +} // namespace toolkit +#endif /* NETWORK_SOCKET_H */ diff --git a/ToolKit/Network/TcpClient.cpp b/ToolKit/Network/TcpClient.cpp new file mode 100644 index 0000000..b2d292f --- /dev/null +++ b/ToolKit/Network/TcpClient.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "TcpClient.h" + +using namespace std; + +namespace toolkit { + +StatisticImp(TcpClient) + +TcpClient::TcpClient(const EventPoller::Ptr &poller) : SocketHelper(nullptr) { + setPoller(poller ? poller : EventPollerPool::Instance().getPoller()); + setOnCreateSocket([](const EventPoller::Ptr &poller) { + //TCP客户端默认开启互斥锁 + return Socket::createSocket(poller, true); + }); +} + +TcpClient::~TcpClient() { + TraceL << "~" << TcpClient::getIdentifier(); +} + +void TcpClient::shutdown(const SockException &ex) { + _timer.reset(); + SocketHelper::shutdown(ex); +} + +bool TcpClient::alive() const { + if (_timer) { + //连接中或已连接 + return true; + } + //在websocket client(zlmediakit)相关代码中, + //_timer一直为空,但是socket fd有效,alive状态也应该返回true + auto sock = getSock(); + return sock && sock->alive(); +} + +void TcpClient::setNetAdapter(const string &local_ip) { + _net_adapter = local_ip; +} + +void TcpClient::startConnect(const string &url, uint16_t port, float timeout_sec, uint16_t local_port) { + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _timer = std::make_shared(2.0f, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManager(); + return true; + }, getPoller()); + + setSock(createSocket()); + + auto sock_ptr = getSock().get(); + sock_ptr->setOnErr([weak_self, sock_ptr](const SockException &ex) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (sock_ptr != strong_self->getSock().get()) { + //已经重连socket,上次的socket的事件忽略掉 + return; + } + strong_self->_timer.reset(); + TraceL << strong_self->getIdentifier() << " on err: " << ex; + strong_self->onError(ex); + }); + + TraceL << getIdentifier() << " start connect " << url << ":" << port; + sock_ptr->connect(url, port, [weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->onSockConnect(err); + } + }, timeout_sec, _net_adapter, local_port); +} + +void TcpClient::onSockConnect(const SockException &ex) { + TraceL << getIdentifier() << " connect result: " << ex; + if (ex) { + //连接失败 + _timer.reset(); + onConnect(ex); + return; + } + + auto sock_ptr = getSock().get(); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + sock_ptr->setOnFlush([weak_self, sock_ptr]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + if (sock_ptr != strong_self->getSock().get()) { + //已经重连socket,上传socket的事件忽略掉 + return false; + } + strong_self->onFlush(); + return true; + }); + + sock_ptr->setOnRead([weak_self, sock_ptr](const Buffer::Ptr &pBuf, struct sockaddr *, int) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (sock_ptr != strong_self->getSock().get()) { + //已经重连socket,上传socket的事件忽略掉 + return; + } + try { + strong_self->onRecv(pBuf); + } catch (std::exception &ex) { + strong_self->shutdown(SockException(Err_other, ex.what())); + } + }); + + onConnect(ex); +} + +std::string TcpClient::getIdentifier() const { + if (_id.empty()) { + static atomic s_index { 0 }; + _id = toolkit::demangle(typeid(*this).name()) + "-" + to_string(++s_index); + } + return _id; +} + +} /* namespace toolkit */ diff --git a/ToolKit/Network/TcpClient.h b/ToolKit/Network/TcpClient.h new file mode 100644 index 0000000..f470f4a --- /dev/null +++ b/ToolKit/Network/TcpClient.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef NETWORK_TCPCLIENT_H +#define NETWORK_TCPCLIENT_H + +#include +#include "Socket.h" +#include "Util/SSLBox.h" + +namespace toolkit { + +//Tcp客户端,Socket对象默认开始互斥锁 +class TcpClient : public SocketHelper { +public: + using Ptr = std::shared_ptr; + TcpClient(const EventPoller::Ptr &poller = nullptr); + ~TcpClient() override; + + /** + * 开始连接tcp服务器 + * @param url 服务器ip或域名 + * @param port 服务器端口 + * @param timeout_sec 超时时间,单位秒 + * @param local_port 本地端口 + */ + virtual void startConnect(const std::string &url, uint16_t port, float timeout_sec = 5, uint16_t local_port = 0); + + /** + * 通过代理开始连接tcp服务器 + * @param url 服务器ip或域名 + * @proxy_host 代理ip + * @proxy_port 代理端口 + * @param timeout_sec 超时时间,单位秒 + * @param local_port 本地端口 + */ + virtual void startConnectWithProxy(const std::string &url, const std::string &proxy_host, uint16_t proxy_port, float timeout_sec = 5, uint16_t local_port = 0){}; + + /** + * 主动断开连接 + * @param ex 触发onErr事件时的参数 + */ + void shutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")) override; + + /** + * 连接中或已连接返回true,断开连接时返回false + */ + virtual bool alive() const; + + /** + * 设置网卡适配器,使用该网卡与服务器通信 + * @param local_ip 本地网卡ip + */ + virtual void setNetAdapter(const std::string &local_ip); + + /** + * 唯一标识 + */ + std::string getIdentifier() const override; + +protected: + /** + * 连接服务器结果回调 + * @param ex 成功与否 + */ + virtual void onConnect(const SockException &ex) = 0; + + /** + * tcp连接成功后每2秒触发一次该事件 + */ + void onManager() override {} + +private: + void onSockConnect(const SockException &ex); + +private: + mutable std::string _id; + std::string _net_adapter = "::"; + std::shared_ptr _timer; + //对象个数统计 + ObjectStatistic _statistic; +}; + +//用于实现TLS客户端的模板对象 +template +class TcpClientWithSSL : public TcpClientType { +public: + using Ptr = std::shared_ptr; + + template + TcpClientWithSSL(ArgsType &&...args):TcpClientType(std::forward(args)...) {} + + ~TcpClientWithSSL() override { + if (_ssl_box) { + _ssl_box->flush(); + } + } + + void onRecv(const Buffer::Ptr &buf) override { + if (_ssl_box) { + _ssl_box->onRecv(buf); + } else { + TcpClientType::onRecv(buf); + } + } + + // 使能其他未被重写的send函数 + using TcpClientType::send; + + ssize_t send(Buffer::Ptr buf) override { + if (_ssl_box) { + auto size = buf->size(); + _ssl_box->onSend(std::move(buf)); + return size; + } + return TcpClientType::send(std::move(buf)); + } + + //添加public_onRecv和public_send函数是解决较低版本gcc一个lambad中不能访问protected或private方法的bug + inline void public_onRecv(const Buffer::Ptr &buf) { + TcpClientType::onRecv(buf); + } + + inline void public_send(const Buffer::Ptr &buf) { + TcpClientType::send(buf); + } + + void startConnect(const std::string &url, uint16_t port, float timeout_sec = 5, uint16_t local_port = 0) override { + _host = url; + TcpClientType::startConnect(url, port, timeout_sec, local_port); + } + void startConnectWithProxy(const std::string &url, const std::string &proxy_host, uint16_t proxy_port, float timeout_sec = 5, uint16_t local_port = 0) override { + _host = url; + TcpClientType::startConnect(proxy_host, proxy_port, timeout_sec, local_port); + } + + bool overSsl() const override { return (bool)_ssl_box; } + +protected: + void onConnect(const SockException &ex) override { + if (!ex) { + _ssl_box = std::make_shared(false); + _ssl_box->setOnDecData([this](const Buffer::Ptr &buf) { + public_onRecv(buf); + }); + _ssl_box->setOnEncData([this](const Buffer::Ptr &buf) { + public_send(buf); + }); + + if (!isIP(_host.data())) { + //设置ssl域名 + _ssl_box->setHost(_host.data()); + } + } + TcpClientType::onConnect(ex); + } + /** + * 重置ssl, 主要为了解决一些302跳转时http与https的转换 + */ + void setDoNotUseSSL() { + _ssl_box.reset(); + } +private: + std::string _host; + std::shared_ptr _ssl_box; +}; + +} /* namespace toolkit */ +#endif /* NETWORK_TCPCLIENT_H */ diff --git a/ToolKit/Network/TcpServer.cpp b/ToolKit/Network/TcpServer.cpp new file mode 100644 index 0000000..3d91474 --- /dev/null +++ b/ToolKit/Network/TcpServer.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "TcpServer.h" +#include "Util/uv_errno.h" +#include "Util/onceToken.h" + +using namespace std; + +namespace toolkit { + +INSTANCE_IMP(SessionMap) +StatisticImp(TcpServer) + +TcpServer::TcpServer(const EventPoller::Ptr &poller) : Server(poller) { + _multi_poller = !poller; + setOnCreateSocket(nullptr); +} + +void TcpServer::setupEvent() { + _socket = createSocket(_poller); + weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _socket->setOnBeforeAccept([weak_self](const EventPoller::Ptr &poller) -> Socket::Ptr { + if (auto strong_self = weak_self.lock()) { + return strong_self->onBeforeAcceptConnection(poller); + } + return nullptr; + }); + _socket->setOnAccept([weak_self](Socket::Ptr &sock, shared_ptr &complete) { + if (auto strong_self = weak_self.lock()) { + auto ptr = sock->getPoller().get(); + auto server = strong_self->getServer(ptr); + ptr->async([server, sock, complete]() { + //该tcp客户端派发给对应线程的TcpServer服务器 + server->onAcceptConnection(sock); + }); + } + }); +} + +TcpServer::~TcpServer() { + if (_main_server && _socket && _socket->rawFD() != -1) { + InfoL << "Close tcp server [" << _socket->get_local_ip() << "]: " << _socket->get_local_port(); + } + _timer.reset(); + //先关闭socket监听,防止收到新的连接 + _socket.reset(); + _session_map.clear(); + _cloned_server.clear(); +} + +uint16_t TcpServer::getPort() { + if (!_socket) { + return 0; + } + return _socket->get_local_port(); +} + +void TcpServer::setOnCreateSocket(Socket::onCreateSocket cb) { + if (cb) { + _on_create_socket = std::move(cb); + } else { + _on_create_socket = [](const EventPoller::Ptr &poller) { + return Socket::createSocket(poller, false); + }; + } + for (auto &pr : _cloned_server) { + pr.second->setOnCreateSocket(cb); + } +} + +TcpServer::Ptr TcpServer::onCreatServer(const EventPoller::Ptr &poller) { + return Ptr(new TcpServer(poller), [poller](TcpServer *ptr) { poller->async([ptr]() { delete ptr; }); }); +} + +Socket::Ptr TcpServer::onBeforeAcceptConnection(const EventPoller::Ptr &poller) { + assert(_poller->isCurrentThread()); + //此处改成自定义获取poller对象,防止负载不均衡 + return createSocket(_multi_poller ? EventPollerPool::Instance().getPoller(false) : _poller); +} + +void TcpServer::cloneFrom(const TcpServer &that) { + if (!that._socket) { + throw std::invalid_argument("TcpServer::cloneFrom other with null socket"); + } + setupEvent(); + _main_server = false; + _on_create_socket = that._on_create_socket; + _session_alloc = that._session_alloc; + weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _timer = std::make_shared(2.0f, [weak_self]() -> bool { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManagerSession(); + return true; + }, _poller); + this->mINI::operator=(that); + _parent = static_pointer_cast(const_cast(that).shared_from_this()); +} + +// 接收到客户端连接请求 +Session::Ptr TcpServer::onAcceptConnection(const Socket::Ptr &sock) { + assert(_poller->isCurrentThread()); + weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + //创建一个Session;这里实现创建不同的服务会话实例 + auto helper = _session_alloc(std::static_pointer_cast(shared_from_this()), sock); + auto session = helper->session(); + //把本服务器的配置传递给Session + session->attachServer(*this); + + //_session_map::emplace肯定能成功 + auto success = _session_map.emplace(helper.get(), helper).second; + assert(success == true); + + weak_ptr weak_session = session; + //会话接收数据事件 + sock->setOnRead([weak_session](const Buffer::Ptr &buf, struct sockaddr *, int) { + //获取会话强应用 + auto strong_session = weak_session.lock(); + if (!strong_session) { + return; + } + try { + strong_session->onRecv(buf); + } catch (SockException &ex) { + strong_session->shutdown(ex); + } catch (exception &ex) { + strong_session->shutdown(SockException(Err_shutdown, ex.what())); + } + }); + + SessionHelper *ptr = helper.get(); + auto cls = ptr->className(); + //会话接收到错误事件 + sock->setOnErr([weak_self, weak_session, ptr, cls](const SockException &err) { + //在本函数作用域结束时移除会话对象 + //目的是确保移除会话前执行其onError函数 + //同时避免其onError函数抛异常时没有移除会话对象 + onceToken token(nullptr, [&]() { + //移除掉会话 + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + assert(strong_self->_poller->isCurrentThread()); + if (!strong_self->_is_on_manager) { + //该事件不是onManager时触发的,直接操作map + strong_self->_session_map.erase(ptr); + } else { + //遍历map时不能直接删除元素 + strong_self->_poller->async([weak_self, ptr]() { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->_session_map.erase(ptr); + } + }, false); + } + }); + + //获取会话强应用 + auto strong_session = weak_session.lock(); + if (strong_session) { + //触发onError事件回调 + TraceP(strong_session) << cls << " on err: " << err; + strong_session->onError(err); + } + }); + return session; +} + +void TcpServer::start_l(uint16_t port, const std::string &host, uint32_t backlog) { + setupEvent(); + + //新建一个定时器定时管理这些tcp会话 + weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _timer = std::make_shared(2.0f, [weak_self]() -> bool { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManagerSession(); + return true; + }, _poller); + + if (_multi_poller) { + EventPollerPool::Instance().for_each([&](const TaskExecutor::Ptr &executor) { + EventPoller::Ptr poller = static_pointer_cast(executor); + if (poller == _poller) { + return; + } + auto &serverRef = _cloned_server[poller.get()]; + if (!serverRef) { + serverRef = onCreatServer(poller); + } + if (serverRef) { + serverRef->cloneFrom(*this); + } + }); + } + + if (!_socket->listen(port, host.c_str(), backlog)) { + // 创建tcp监听失败,可能是由于端口占用或权限问题 + string err = (StrPrinter << "Listen on " << host << " " << port << " failed: " << get_uv_errmsg(true)); + throw std::runtime_error(err); + } + for (auto &pr: _cloned_server) { + // 启动子Server + pr.second->_socket->cloneSocket(*_socket); + } + + InfoL << "TCP server listening on [" << host << "]: " << port; +} + +void TcpServer::onManagerSession() { + assert(_poller->isCurrentThread()); + + onceToken token([&]() { + _is_on_manager = true; + }, [&]() { + _is_on_manager = false; + }); + + for (auto &pr : _session_map) { + //遍历时,可能触发onErr事件(也会操作_session_map) + try { + pr.second->session()->onManager(); + } catch (exception &ex) { + WarnL << ex.what(); + } + } +} + +Socket::Ptr TcpServer::createSocket(const EventPoller::Ptr &poller) { + return _on_create_socket(poller); +} + +TcpServer::Ptr TcpServer::getServer(const EventPoller *poller) const { + auto parent = _parent.lock(); + auto &ref = parent ? parent->_cloned_server : _cloned_server; + auto it = ref.find(poller); + if (it != ref.end()) { + //派发到cloned server + return it->second; + } + //派发到parent server + return static_pointer_cast(parent ? parent : const_cast(this)->shared_from_this()); +} + +Session::Ptr TcpServer::createSession(const Socket::Ptr &sock) { + return getServer(sock->getPoller().get())->onAcceptConnection(sock); +} + +} /* namespace toolkit */ + diff --git a/ToolKit/Network/TcpServer.h b/ToolKit/Network/TcpServer.h new file mode 100644 index 0000000..11d0d89 --- /dev/null +++ b/ToolKit/Network/TcpServer.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TCPSERVER_TCPSERVER_H +#define TCPSERVER_TCPSERVER_H + +#include +#include +#include +#include "Server.h" +#include "Session.h" +#include "Poller/Timer.h" +#include "Util/util.h" + +namespace toolkit { + +//TCP服务器,可配置的;配置通过Session::attachServer方法传递给会话对象 +class TcpServer : public Server { +public: + using Ptr = std::shared_ptr; + + /** + * 创建tcp服务器,listen fd的accept事件会加入到所有的poller线程中监听 + * 在调用TcpServer::start函数时,内部会创建多个子TcpServer对象, + * 这些子TcpServer对象通过Socket对象克隆的方式在多个poller线程中监听同一个listen fd + * 这样这个TCP服务器将会通过抢占式accept的方式把客户端均匀的分布到不同的poller线程 + * 通过该方式能实现客户端负载均衡以及提高连接接收速度 + */ + explicit TcpServer(const EventPoller::Ptr &poller = nullptr); + ~TcpServer() override; + + /** + * @brief 开始tcp server + * @param port 本机端口,0则随机 + * @param host 监听网卡ip + * @param backlog tcp listen backlog + */ + template + void start(uint16_t port, const std::string &host = "::", uint32_t backlog = 1024, const std::function &)> &cb = nullptr) { + static std::string cls_name = toolkit::demangle(typeid(SessionType).name()); + // Session创建器,通过它创建不同类型的服务器 + _session_alloc = [cb](const TcpServer::Ptr &server, const Socket::Ptr &sock) { + auto session = std::shared_ptr(new SessionType(sock), [](SessionType *ptr) { + TraceP(static_cast(ptr)) << "~" << cls_name; + delete ptr; + }); + if (cb) { + cb(session); + } + TraceP(static_cast(session.get())) << cls_name; + session->setOnCreateSocket(server->_on_create_socket); + return std::make_shared(server, std::move(session), cls_name); + }; + start_l(port, host, backlog); + } + + /** + * @brief 获取服务器监听端口号, 服务器可以选择监听随机端口 + */ + uint16_t getPort(); + + /** + * @brief 自定义socket构建行为 + */ + void setOnCreateSocket(Socket::onCreateSocket cb); + + /** + * 根据socket对象创建Session对象 + * 需要确保在socket归属poller线程执行本函数 + */ + Session::Ptr createSession(const Socket::Ptr &socket); + +protected: + virtual void cloneFrom(const TcpServer &that); + virtual TcpServer::Ptr onCreatServer(const EventPoller::Ptr &poller); + + virtual Session::Ptr onAcceptConnection(const Socket::Ptr &sock); + virtual Socket::Ptr onBeforeAcceptConnection(const EventPoller::Ptr &poller); + +private: + void onManagerSession(); + Socket::Ptr createSocket(const EventPoller::Ptr &poller); + void start_l(uint16_t port, const std::string &host, uint32_t backlog); + Ptr getServer(const EventPoller *) const; + void setupEvent(); + +private: + bool _multi_poller; + bool _is_on_manager = false; + bool _main_server = true; + std::weak_ptr _parent; + Socket::Ptr _socket; + std::shared_ptr _timer; + Socket::onCreateSocket _on_create_socket; + std::unordered_map _session_map; + std::function _session_alloc; + std::unordered_map _cloned_server; + //对象个数统计 + ObjectStatistic _statistic; +}; + +} /* namespace toolkit */ +#endif /* TCPSERVER_TCPSERVER_H */ diff --git a/ToolKit/Network/sockutil.cpp b/ToolKit/Network/sockutil.cpp new file mode 100644 index 0000000..7778e2a --- /dev/null +++ b/ToolKit/Network/sockutil.cpp @@ -0,0 +1,1140 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ +#include +#include +#include +#include +#include +#include +#include +#include "sockutil.h" +#include "Util/util.h" +#include "Util/logger.h" +#include "Util/uv_errno.h" +#include "Util/onceToken.h" +#if defined (__APPLE__) +#include +#include +#endif +using namespace std; + +namespace toolkit { + +#if defined(_WIN32) +static onceToken g_token([]() { + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + WSAStartup(wVersionRequested, &wsaData); +}, []() { + WSACleanup(); +}); +int ioctl(int fd, long cmd, u_long *ptr) { + return ioctlsocket(fd, cmd, ptr); +} +int close(int fd) { + return closesocket(fd); +} +#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + struct sockaddr_storage ss; + unsigned long s = size; + + ZeroMemory(&ss, sizeof(ss)); + ss.ss_family = af; + + switch (af) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; + break; + default: + return NULL; + } + /* cannot direclty use &size because of strict aliasing rules */ + return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0) ? dst : NULL; +} +int inet_pton(int af, const char *src, void *dst) { + struct sockaddr_storage ss; + int size = sizeof(ss); + char src_copy[INET6_ADDRSTRLEN + 1]; + + ZeroMemory(&ss, sizeof(ss)); + /* stupid non-const API */ + strncpy(src_copy, src, INET6_ADDRSTRLEN + 1); + src_copy[INET6_ADDRSTRLEN] = 0; + + if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { + switch (af) { + case AF_INET: + *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + } + return 0; +} +#endif +#endif // defined(_WIN32) + +static inline string my_inet_ntop(int af, const void *addr) { + string ret; + ret.resize(128); + if (!inet_ntop(af, const_cast(addr), (char *) ret.data(), ret.size())) { + ret.clear(); + } else { + ret.resize(strlen(ret.data())); + } + return ret; +} + +static inline bool support_ipv6_l() { + auto fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (fd == -1) { + return false; + } + close(fd); + return true; +} + +bool SockUtil::support_ipv6() { + static auto flag = support_ipv6_l(); + return flag; +} + +string SockUtil::inet_ntoa(const struct in_addr &addr) { + return my_inet_ntop(AF_INET, &addr); +} + +std::string SockUtil::inet_ntoa(const struct in6_addr &addr) { + return my_inet_ntop(AF_INET6, &addr); +} + +std::string SockUtil::inet_ntoa(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET: return SockUtil::inet_ntoa(((struct sockaddr_in *)addr)->sin_addr); + case AF_INET6: { + if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)addr)->sin6_addr)) { + struct in_addr addr4; + memcpy(&addr4, 12 + (char *)&(((struct sockaddr_in6 *)addr)->sin6_addr), 4); + return SockUtil::inet_ntoa(addr4); + } + return SockUtil::inet_ntoa(((struct sockaddr_in6 *)addr)->sin6_addr); + } + default: return ""; + } +} + +uint16_t SockUtil::inet_port(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET: return ntohs(((struct sockaddr_in *)addr)->sin_port); + case AF_INET6: return ntohs(((struct sockaddr_in6 *)addr)->sin6_port); + default: return 0; + } +} + +int SockUtil::setCloseWait(int fd, int second) { + linger m_sLinger; + //在调用closesocket()时还有数据未发送完,允许等待 + // 若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭 + m_sLinger.l_onoff = (second > 0); + m_sLinger.l_linger = second; //设置等待时间为x秒 + int ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &m_sLinger, sizeof(linger)); + if (ret == -1) { +#ifndef _WIN32 + TraceL << "setsockopt SO_LINGER failed"; +#endif + } + return ret; +} + +int SockUtil::setNoDelay(int fd, bool on) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt TCP_NODELAY failed"; + } + return ret; +} + +int SockUtil::setReuseable(int fd, bool on, bool reuse_port) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_REUSEADDR failed"; + return ret; + } +#if defined(SO_REUSEPORT) + if (reuse_port) { + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_REUSEPORT failed"; + } + } +#endif + return ret; +} + +int SockUtil::setBroadcast(int fd, bool on) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_BROADCAST failed"; + } + return ret; +} + +int SockUtil::setKeepAlive(int fd, bool on, int interval, int idle, int times) { + // Enable/disable the keep-alive option + int opt = on ? 1 : 0; + int ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_KEEPALIVE failed"; + } +#if !defined(_WIN32) +#if !defined(SOL_TCP) && defined(IPPROTO_TCP) + #define SOL_TCP IPPROTO_TCP +#endif +#if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE) + #define TCP_KEEPIDLE TCP_KEEPALIVE +#endif + // Set the keep-alive parameters + if (on && interval > 0 && ret != -1) { + ret = setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (char *) &idle, static_cast(sizeof(idle))); + if (ret == -1) { + TraceL << "setsockopt TCP_KEEPIDLE failed"; + } + ret = setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (char *) &interval, static_cast(sizeof(interval))); + if (ret == -1) { + TraceL << "setsockopt TCP_KEEPINTVL failed"; + } + ret = setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (char *) ×, static_cast(sizeof(times))); + if (ret == -1) { + TraceL << "setsockopt TCP_KEEPCNT failed"; + } + } +#endif + return ret; +} + +int SockUtil::setCloExec(int fd, bool on) { +#if !defined(_WIN32) + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + TraceL << "fcntl F_GETFD failed"; + return -1; + } + if (on) { + flags |= FD_CLOEXEC; + } else { + int cloexec = FD_CLOEXEC; + flags &= ~cloexec; + } + int ret = fcntl(fd, F_SETFD, flags); + if (ret == -1) { + TraceL << "fcntl F_SETFD failed"; + return -1; + } + return ret; +#else + return -1; +#endif +} + +int SockUtil::setNoSigpipe(int fd) { +#if defined(SO_NOSIGPIPE) + int set = 1; + auto ret = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (char *) &set, sizeof(int)); + if (ret == -1) { + TraceL << "setsockopt SO_NOSIGPIPE failed"; + } + return ret; +#else + return -1; +#endif +} + +int SockUtil::setNoBlocked(int fd, bool noblock) { +#if defined(_WIN32) + unsigned long ul = noblock; +#else + int ul = noblock; +#endif //defined(_WIN32) + int ret = ioctl(fd, FIONBIO, &ul); //设置为非阻塞模式 + if (ret == -1) { + TraceL << "ioctl FIONBIO failed"; + } + + return ret; +} + +int SockUtil::setRecvBuf(int fd, int size) { + if (size <= 0) { + // use the system default value + return 0; + } + int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size)); + if (ret == -1) { + TraceL << "setsockopt SO_RCVBUF failed"; + } + return ret; +} + +int SockUtil::setSendBuf(int fd, int size) { + if (size <= 0) { + return 0; + } + int ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &size, sizeof(size)); + if (ret == -1) { + TraceL << "setsockopt SO_SNDBUF failed"; + } + return ret; +} + +class DnsCache { +public: + static DnsCache &Instance() { + static DnsCache instance; + return instance; + } + + bool getDomainIP(const char *host, sockaddr_storage &storage, int ai_family = AF_INET, + int ai_socktype = SOCK_STREAM, int ai_protocol = IPPROTO_TCP, int expire_sec = 60) { + try { + storage = SockUtil::make_sockaddr(host, 0); + return true; + } catch (...) { + auto item = getCacheDomainIP(host, expire_sec); + if (!item) { + item = getSystemDomainIP(host); + if (item) { + setCacheDomainIP(host, item); + } + } + if (item) { + auto addr = getPerferredAddress(item.get(), ai_family, ai_socktype, ai_protocol); + memcpy(&storage, addr->ai_addr, addr->ai_addrlen); + } + return (bool)item; + } + } + +private: + class DnsItem { + public: + std::shared_ptr addr_info; + time_t create_time; + }; + + std::shared_ptr getCacheDomainIP(const char *host, int expireSec) { + lock_guard lck(_mtx); + auto it = _dns_cache.find(host); + if (it == _dns_cache.end()) { + //没有记录 + return nullptr; + } + if (it->second.create_time + expireSec < time(nullptr)) { + //已过期 + _dns_cache.erase(it); + return nullptr; + } + return it->second.addr_info; + } + + void setCacheDomainIP(const char *host, std::shared_ptr addr) { + lock_guard lck(_mtx); + DnsItem item; + item.addr_info = std::move(addr); + item.create_time = time(nullptr); + _dns_cache[host] = std::move(item); + } + + std::shared_ptr getSystemDomainIP(const char *host) { + struct addrinfo *answer = nullptr; + //阻塞式dns解析,可能被打断 + int ret = -1; + do { + ret = getaddrinfo(host, nullptr, nullptr, &answer); + } while (ret == -1 && get_uv_error(true) == UV_EINTR); + + if (!answer) { + WarnL << "getaddrinfo failed: " << host; + return nullptr; + } + return std::shared_ptr(answer, freeaddrinfo); + } + + struct addrinfo *getPerferredAddress(struct addrinfo *answer, int ai_family, int ai_socktype, int ai_protocol) { + auto ptr = answer; + while (ptr) { + if (ptr->ai_family == ai_family && ptr->ai_socktype == ai_socktype && ptr->ai_protocol == ai_protocol) { + return ptr; + } + ptr = ptr->ai_next; + } + return answer; + } + +private: + mutex _mtx; + unordered_map _dns_cache; +}; + +bool SockUtil::getDomainIP(const char *host, uint16_t port, struct sockaddr_storage &addr, + int ai_family, int ai_socktype, int ai_protocol, int expire_sec) { + bool flag = DnsCache::Instance().getDomainIP(host, addr, ai_family, ai_socktype, ai_protocol, expire_sec); + if (flag) { + switch (addr.ss_family ) { + case AF_INET : ((sockaddr_in *) &addr)->sin_port = htons(port); break; + case AF_INET6 : ((sockaddr_in6 *) &addr)->sin6_port = htons(port); break; + default: break; + } + } + return flag; +} + +static int set_ipv6_only(int fd, bool flag) { + int opt = flag; + int ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof opt); + if (ret == -1) { + TraceL << "setsockopt IPV6_V6ONLY failed"; + } + return ret; +} + +static int bind_sock6(int fd, const char *ifr_ip, uint16_t port) { + set_ipv6_only(fd, false); + struct sockaddr_in6 addr; + bzero(&addr, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + if (1 != inet_pton(AF_INET6, ifr_ip, &(addr.sin6_addr))) { + if (strcmp(ifr_ip, "0.0.0.0")) { + WarnL << "inet_pton to ipv6 address failed: " << ifr_ip; + } + addr.sin6_addr = IN6ADDR_ANY_INIT; + } + if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { + WarnL << "Bind socket failed: " << get_uv_errmsg(true); + return -1; + } + return 0; +} + +static int bind_sock4(int fd, const char *ifr_ip, uint16_t port) { + struct sockaddr_in addr; + bzero(&addr, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (1 != inet_pton(AF_INET, ifr_ip, &(addr.sin_addr))) { + if (strcmp(ifr_ip, "::")) { + WarnL << "inet_pton to ipv4 address failed: " << ifr_ip; + } + addr.sin_addr.s_addr = INADDR_ANY; + } + if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { + WarnL << "Bind socket failed: " << get_uv_errmsg(true); + return -1; + } + return 0; +} + +static int bind_sock(int fd, const char *ifr_ip, uint16_t port, int family) { + switch (family) { + case AF_INET: return bind_sock4(fd, ifr_ip, port); + case AF_INET6: return bind_sock6(fd, ifr_ip, port); + default: return -1; + } +} + +int SockUtil::connect(const char *host, uint16_t port, bool async, const char *local_ip, uint16_t local_port) { + sockaddr_storage addr; + //优先使用ipv4地址 + if (!getDomainIP(host, port, addr, AF_INET, SOCK_STREAM, IPPROTO_TCP)) { + //dns解析失败 + return -1; + } + + int sockfd = (int) socket(addr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) { + WarnL << "Create socket failed: " << host; + return -1; + } + + setReuseable(sockfd); + setNoSigpipe(sockfd); + setNoBlocked(sockfd, async); + setNoDelay(sockfd); + setSendBuf(sockfd); + setRecvBuf(sockfd); + setCloseWait(sockfd); + setCloExec(sockfd); + + if (bind_sock(sockfd, local_ip, local_port, addr.ss_family) == -1) { + close(sockfd); + return -1; + } + + if (::connect(sockfd, (sockaddr *) &addr, get_sock_len((sockaddr *)&addr)) == 0) { + //同步连接成功 + return sockfd; + } + if (async && get_uv_error(true) == UV_EAGAIN) { + //异步连接成功 + return sockfd; + } + WarnL << "Connect socket to " << host << " " << port << " failed: " << get_uv_errmsg(true); + close(sockfd); + return -1; +} + +int SockUtil::listen(const uint16_t port, const char *local_ip, int back_log) { + int fd = -1; + int family = support_ipv6() ? (is_ipv4(local_ip) ? AF_INET : AF_INET6) : AF_INET; + if ((fd = (int)socket(family, SOCK_STREAM, IPPROTO_TCP)) == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return -1; + } + + setReuseable(fd, true, false); + setNoBlocked(fd); + setCloExec(fd); + + if (bind_sock(fd, local_ip, port, family) == -1) { + close(fd); + return -1; + } + + //开始监听 + if (::listen(fd, back_log) == -1) { + WarnL << "Listen socket failed: " << get_uv_errmsg(true); + close(fd); + return -1; + } + + return fd; +} + +int SockUtil::getSockError(int fd) { + int opt; + socklen_t optLen = static_cast(sizeof(opt)); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &opt, &optLen) < 0) { + return get_uv_error(true); + } else { + return uv_translate_posix_error(opt); + } +} + +using getsockname_type = decltype(getsockname); + +static bool get_socket_addr(int fd, struct sockaddr_storage &addr, getsockname_type func) { + socklen_t addr_len = sizeof(addr); + if (-1 == func(fd, (struct sockaddr *)&addr, &addr_len)) { + return false; + } + return true; +} + +bool SockUtil::get_sock_local_addr(int fd, struct sockaddr_storage &addr) { + return get_socket_addr(fd, addr, getsockname); +} + +bool SockUtil::get_sock_peer_addr(int fd, struct sockaddr_storage &addr) { + return get_socket_addr(fd, addr, getpeername); +} + +static string get_socket_ip(int fd, getsockname_type func) { + struct sockaddr_storage addr; + if (!get_socket_addr(fd, addr, func)) { + return ""; + } + return SockUtil::inet_ntoa((struct sockaddr *)&addr); +} + +static uint16_t get_socket_port(int fd, getsockname_type func) { + struct sockaddr_storage addr; + if (!get_socket_addr(fd, addr, func)) { + return 0; + } + return SockUtil::inet_port((struct sockaddr *)&addr); +} + +string SockUtil::get_local_ip(int fd) { + return get_socket_ip(fd, getsockname); +} + +string SockUtil::get_peer_ip(int fd) { + return get_socket_ip(fd, getpeername); +} + +uint16_t SockUtil::get_local_port(int fd) { + return get_socket_port(fd, getsockname); +} + +uint16_t SockUtil::get_peer_port(int fd) { + return get_socket_port(fd, getpeername); +} + +#if defined(__APPLE__) +template +void for_each_netAdapter_apple(FUN &&fun) { //type: struct ifaddrs * + struct ifaddrs *interfaces = nullptr; + struct ifaddrs *adapter = nullptr; + if (getifaddrs(&interfaces) == 0) { + adapter = interfaces; + while (adapter) { + if (adapter->ifa_addr->sa_family == AF_INET) { + if (fun(adapter)) { + break; + } + } + adapter = adapter->ifa_next; + } + freeifaddrs(interfaces); + } +} +#endif //defined(__APPLE__) + +#if defined(_WIN32) +template +void for_each_netAdapter_win32(FUN && fun) { //type: PIP_ADAPTER_INFO + unsigned long nSize = sizeof(IP_ADAPTER_INFO); + PIP_ADAPTER_INFO adapterList = (PIP_ADAPTER_INFO)new char[nSize]; + int nRet = GetAdaptersInfo(adapterList, &nSize); + if (ERROR_BUFFER_OVERFLOW == nRet) { + delete[] adapterList; + adapterList = (PIP_ADAPTER_INFO)new char[nSize]; + nRet = GetAdaptersInfo(adapterList, &nSize); + } + auto adapterPtr = adapterList; + while (adapterPtr && ERROR_SUCCESS == nRet) { + if (fun(adapterPtr)) { + break; + } + adapterPtr = adapterPtr->Next; + } + //释放内存空间 + delete[] adapterList; +} +#endif //defined(_WIN32) + +#if !defined(_WIN32) && !defined(__APPLE__) +template +void for_each_netAdapter_posix(FUN &&fun){ //type: struct ifreq * + struct ifconf ifconf; + char buf[1024 * 10]; + //初始化ifconf + ifconf.ifc_len = sizeof(buf); + ifconf.ifc_buf = buf; + int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return; + } + if (-1 == ioctl(sockfd, SIOCGIFCONF, &ifconf)) { //获取所有接口信息 + WarnL << "ioctl SIOCGIFCONF failed: " << get_uv_errmsg(true); + close(sockfd); + return; + } + close(sockfd); + //接下来一个一个的获取IP地址 + struct ifreq * adapter = (struct ifreq*) buf; + for (int i = (ifconf.ifc_len / sizeof(struct ifreq)); i > 0; --i,++adapter) { + if(fun(adapter)){ + break; + } + } +} +#endif //!defined(_WIN32) && !defined(__APPLE__) + +bool check_ip(string &address, const string &ip) { + if (ip != "127.0.0.1" && ip != "0.0.0.0") { + /*获取一个有效IP*/ + address = ip; + uint32_t addressInNetworkOrder = htonl(inet_addr(ip.data())); + if (/*(addressInNetworkOrder >= 0x0A000000 && addressInNetworkOrder < 0x0E000000) ||*/ + (addressInNetworkOrder >= 0xAC100000 && addressInNetworkOrder < 0xAC200000) || + (addressInNetworkOrder >= 0xC0A80000 && addressInNetworkOrder < 0xC0A90000)) { + //A类私有IP地址: + //10.0.0.0~10.255.255.255 + //B类私有IP地址: + //172.16.0.0~172.31.255.255 + //C类私有IP地址: + //192.168.0.0~192.168.255.255 + //如果是私有地址 说明在nat内部 + + /* 优先采用局域网地址,该地址很可能是wifi地址 + * 一般来说,无线路由器分配的地址段是BC类私有ip地址 + * 而A类地址多用于蜂窝移动网络 + */ + return true; + } + } + return false; +} + +string SockUtil::get_local_ip() { +#if defined(__APPLE__) + string address = "127.0.0.1"; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + string ip = SockUtil::inet_ntoa(adapter->ifa_addr); + if (strstr(adapter->ifa_name, "docker")) { + return false; + } + return check_ip(address, ip); + }); + return address; +#elif defined(_WIN32) + string address = "127.0.0.1"; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr) { + string ip = ipAddr->IpAddress.String; + if (strstr(adapter->AdapterName, "docker")) { + return false; + } + if(check_ip(address,ip)){ + return true; + } + ipAddr = ipAddr->Next; + } + return false; + }); + return address; +#else + string address = "127.0.0.1"; + for_each_netAdapter_posix([&](struct ifreq *adapter){ + string ip = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + if (strstr(adapter->ifr_name, "docker")) { + return false; + } + return check_ip(address,ip); + }); + return address; +#endif +} + +vector > SockUtil::getInterfaceList() { + vector > ret; +#if defined(__APPLE__) + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + map obj; + obj["ip"] = SockUtil::inet_ntoa(adapter->ifa_addr); + obj["name"] = adapter->ifa_name; + ret.emplace_back(std::move(obj)); + return false; + }); +#elif defined(_WIN32) + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr) { + map obj; + obj["ip"] = ipAddr->IpAddress.String; + obj["name"] = adapter->AdapterName; + ret.emplace_back(std::move(obj)); + ipAddr = ipAddr->Next; + } + return false; + }); +#else + for_each_netAdapter_posix([&](struct ifreq *adapter){ + map obj; + obj["ip"] = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + obj["name"] = adapter->ifr_name; + ret.emplace_back(std::move(obj)); + return false; + }); +#endif + return ret; +} + +int SockUtil::bindUdpSock(const uint16_t port, const char *local_ip, bool enable_reuse) { + int fd = -1; + int family = support_ipv6() ? (is_ipv4(local_ip) ? AF_INET : AF_INET6) : AF_INET; + if ((fd = (int)socket(family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return -1; + } + if (enable_reuse) { + setReuseable(fd); + } + setNoSigpipe(fd); + setNoBlocked(fd); + setSendBuf(fd); + setRecvBuf(fd); + setCloseWait(fd); + setCloExec(fd); + + if (bind_sock(fd, local_ip, port, family) == -1) { + close(fd); + return -1; + } + return fd; +} + +int SockUtil::dissolveUdpSock(int fd) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (-1 == getsockname(fd, (struct sockaddr *)&addr, &addr_len)) { + return -1; + } + addr.ss_family = AF_UNSPEC; + if (-1 == ::connect(fd, (struct sockaddr *)&addr, addr_len) && get_uv_error() != UV_EAFNOSUPPORT) { + // mac/ios时返回EAFNOSUPPORT错误 + WarnL << "Connect socket AF_UNSPEC failed: " << get_uv_errmsg(true); + return -1; + } + return 0; +} + +string SockUtil::get_ifr_ip(const char *if_name) { +#if defined(__APPLE__) + string ret; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + if (strcmp(adapter->ifa_name, if_name) == 0) { + ret = SockUtil::inet_ntoa(adapter->ifa_addr); + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr){ + if (strcmp(if_name,adapter->AdapterName) == 0){ + //ip匹配到了 + ret.assign(ipAddr->IpAddress.String); + return true; + } + ipAddr = ipAddr->Next; + } + return false; + }); + return ret; +#else + string ret; + for_each_netAdapter_posix([&](struct ifreq *adapter){ + if(strcmp(adapter->ifr_name,if_name) == 0) { + ret = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + return true; + } + return false; + }); + return ret; +#endif +} + +string SockUtil::get_ifr_name(const char *local_ip) { +#if defined(__APPLE__) + string ret = "en0"; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + string ip = SockUtil::inet_ntoa(adapter->ifa_addr); + if (ip == local_ip) { + ret = adapter->ifa_name; + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret = "en0"; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr){ + if (strcmp(local_ip,ipAddr->IpAddress.String) == 0){ + //ip匹配到了 + ret.assign(adapter->AdapterName); + return true; + } + ipAddr = ipAddr->Next; + } + return false; + }); + return ret; +#else + string ret = "en0"; + for_each_netAdapter_posix([&](struct ifreq *adapter){ + string ip = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + if(ip == local_ip) { + ret = adapter->ifr_name; + return true; + } + return false; + }); + return ret; +#endif +} + +string SockUtil::get_ifr_mask(const char *if_name) { +#if defined(__APPLE__) + string ret; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + if (strcmp(if_name, adapter->ifa_name) == 0) { + ret = SockUtil::inet_ntoa(adapter->ifa_netmask); + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + if (strcmp(if_name,adapter->AdapterName) == 0){ + //找到了该网卡 + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + //获取第一个ip的子网掩码 + ret.assign(ipAddr->IpMask.String); + return true; + } + return false; + }); + return ret; +#else + int fd; + struct ifreq ifr_mask; + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return ""; + } + memset(&ifr_mask, 0, sizeof(ifr_mask)); + strncpy(ifr_mask.ifr_name, if_name, sizeof(ifr_mask.ifr_name) - 1); + if ((ioctl(fd, SIOCGIFNETMASK, &ifr_mask)) < 0) { + WarnL << "ioctl SIOCGIFNETMASK on " << if_name << " failed: " << get_uv_errmsg(true); + close(fd); + return ""; + } + close(fd); + return SockUtil::inet_ntoa(&(ifr_mask.ifr_netmask)); +#endif // defined(_WIN32) +} + +string SockUtil::get_ifr_brdaddr(const char *if_name) { +#if defined(__APPLE__) + string ret; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + if (strcmp(if_name, adapter->ifa_name) == 0) { + ret = SockUtil::inet_ntoa(adapter->ifa_broadaddr); + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + if (strcmp(if_name, adapter->AdapterName) == 0) { + //找到该网卡 + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + in_addr broadcast; + broadcast.S_un.S_addr = (inet_addr(ipAddr->IpAddress.String) & inet_addr(ipAddr->IpMask.String)) | (~inet_addr(ipAddr->IpMask.String)); + ret = SockUtil::inet_ntoa(broadcast); + return true; + } + return false; + }); + return ret; +#else + int fd; + struct ifreq ifr_mask; + fd = socket( AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return ""; + } + memset(&ifr_mask, 0, sizeof(ifr_mask)); + strncpy(ifr_mask.ifr_name, if_name, sizeof(ifr_mask.ifr_name) - 1); + if ((ioctl(fd, SIOCGIFBRDADDR, &ifr_mask)) < 0) { + WarnL << "ioctl SIOCGIFBRDADDR failed: " << get_uv_errmsg(true); + close(fd); + return ""; + } + close(fd); + return SockUtil::inet_ntoa(&(ifr_mask.ifr_broadaddr)); +#endif +} + +#define ip_addr_netcmp(addr1, addr2, mask) (((addr1) & (mask)) == ((addr2) & (mask))) + +bool SockUtil::in_same_lan(const char *myIp, const char *dstIp) { + string mask = get_ifr_mask(get_ifr_name(myIp).data()); + return ip_addr_netcmp(inet_addr(myIp), inet_addr(dstIp), inet_addr(mask.data())); +} + +static void clearMulticastAllSocketOption(int socket) { +#if defined(IP_MULTICAST_ALL) + // This option is defined in modern versions of Linux to overcome a bug in the Linux kernel's default behavior. + // When set to 0, it ensures that we receive only packets that were sent to the specified IP multicast address, + // even if some other process on the same system has joined a different multicast group with the same port number. + int multicastAll = 0; + (void)setsockopt(socket, IPPROTO_IP, IP_MULTICAST_ALL, (void*)&multicastAll, sizeof multicastAll); + // Ignore the call's result. Should it fail, we'll still receive packets (just perhaps more than intended) +#endif +} + +int SockUtil::setMultiTTL(int fd, uint8_t ttl) { + int ret = -1; +#if defined(IP_MULTICAST_TTL) + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &ttl, sizeof(ttl)); + if (ret == -1) { + TraceL << "setsockopt IP_MULTICAST_TTL failed"; + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::setMultiIF(int fd, const char *local_ip) { + int ret = -1; +#if defined(IP_MULTICAST_IF) + struct in_addr addr; + addr.s_addr = inet_addr(local_ip); + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char *) &addr, sizeof(addr)); + if (ret == -1) { + TraceL << "setsockopt IP_MULTICAST_IF failed"; + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::setMultiLOOP(int fd, bool accept) { + int ret = -1; +#if defined(IP_MULTICAST_LOOP) + uint8_t loop = accept; + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop, sizeof(loop)); + if (ret == -1) { + TraceL << "setsockopt IP_MULTICAST_LOOP failed"; + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::joinMultiAddr(int fd, const char *addr, const char *local_ip) { + int ret = -1; +#if defined(IP_ADD_MEMBERSHIP) + struct ip_mreq imr; + imr.imr_multiaddr.s_addr = inet_addr(addr); + imr.imr_interface.s_addr = inet_addr(local_ip); + ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq)); + if (ret == -1) { + TraceL << "setsockopt IP_ADD_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::leaveMultiAddr(int fd, const char *addr, const char *local_ip) { + int ret = -1; +#if defined(IP_DROP_MEMBERSHIP) + struct ip_mreq imr; + imr.imr_multiaddr.s_addr = inet_addr(addr); + imr.imr_interface.s_addr = inet_addr(local_ip); + ret = setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq)); + if (ret == -1) { + TraceL << "setsockopt IP_DROP_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +template +static inline void write4Byte(A &&a, B &&b) { + memcpy(&a, &b, sizeof(a)); +} + +int SockUtil::joinMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip) { + int ret = -1; +#if defined(IP_ADD_SOURCE_MEMBERSHIP) + struct ip_mreq_source imr; + + write4Byte(imr.imr_multiaddr, inet_addr(addr)); + write4Byte(imr.imr_sourceaddr, inet_addr(src_ip)); + write4Byte(imr.imr_interface, inet_addr(local_ip)); + + ret = setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq_source)); + if (ret == -1) { + TraceL << "setsockopt IP_ADD_SOURCE_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::leaveMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip) { + int ret = -1; +#if defined(IP_DROP_SOURCE_MEMBERSHIP) + struct ip_mreq_source imr; + + write4Byte(imr.imr_multiaddr, inet_addr(addr)); + write4Byte(imr.imr_sourceaddr, inet_addr(src_ip)); + write4Byte(imr.imr_interface, inet_addr(local_ip)); + + ret = setsockopt(fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq_source)); + if (ret == -1) { + TraceL << "setsockopt IP_DROP_SOURCE_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +bool SockUtil::is_ipv4(const char *host) { + struct in_addr addr; + return 1 == inet_pton(AF_INET, host, &addr); +} + +bool SockUtil::is_ipv6(const char *host) { + struct in6_addr addr; + return 1 == inet_pton(AF_INET6, host, &addr); +} + +socklen_t SockUtil::get_sock_len(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET : return sizeof(sockaddr_in); + case AF_INET6 : return sizeof(sockaddr_in6); + default: assert(0); return 0; + } +} + +struct sockaddr_storage SockUtil::make_sockaddr(const char *host, uint16_t port) { + struct sockaddr_storage storage; + bzero(&storage, sizeof(storage)); + + struct in_addr addr; + struct in6_addr addr6; + if (1 == inet_pton(AF_INET, host, &addr)) { + // host是ipv4 + reinterpret_cast(storage).sin_addr = addr; + reinterpret_cast(storage).sin_family = AF_INET; + reinterpret_cast(storage).sin_port = htons(port); + return storage; + } + if (1 == inet_pton(AF_INET6, host, &addr6)) { + // host是ipv6 + reinterpret_cast(storage).sin6_addr = addr6; + reinterpret_cast(storage).sin6_family = AF_INET6; + reinterpret_cast(storage).sin6_port = htons(port); + return storage; + } + throw std::invalid_argument(string("Not ip address: ") + host); +} + +} // namespace toolkit diff --git a/ToolKit/Network/sockutil.h b/ToolKit/Network/sockutil.h new file mode 100644 index 0000000..9f7af5c --- /dev/null +++ b/ToolKit/Network/sockutil.h @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef NETWORK_SOCKUTIL_H +#define NETWORK_SOCKUTIL_H + +#if defined(_WIN32) +#include +#include +#include +#pragma comment (lib, "Ws2_32.lib") +#pragma comment(lib,"Iphlpapi.lib") +#else +#include +#include +#include +#include +#include +#include +#include +#endif // defined(_WIN32) + +#include +#include +#include +#include +#include + +namespace toolkit { + +#if defined(_WIN32) +#ifndef socklen_t +#define socklen_t int +#endif //!socklen_t +int ioctl(int fd, long cmd, u_long *ptr); +int close(int fd); +#endif // defined(_WIN32) + +#if !defined(SOCKET_DEFAULT_BUF_SIZE) +#define SOCKET_DEFAULT_BUF_SIZE (256 * 1024) +#else +#if SOCKET_DEFAULT_BUF_SIZE == 0 && !defined(__linux__) +// just for linux, because in some high-throughput environments, +// kernel control is more efficient and reasonable than program +// settings. For example, refer to cloudflare's blog +#undef SOCKET_DEFAULT_BUF_SIZE +#define SOCKET_DEFAULT_BUF_SIZE (256 * 1024) +#endif +#endif +#define TCP_KEEPALIVE_INTERVAL 30 +#define TCP_KEEPALIVE_PROBE_TIMES 9 +#define TCP_KEEPALIVE_TIME 120 + +//套接字工具类,封装了socket、网络的一些基本操作 +class SockUtil { +public: + /** + * 创建tcp客户端套接字并连接服务器 + * @param host 服务器ip或域名 + * @param port 服务器端口号 + * @param async 是否异步连接 + * @param local_ip 绑定的本地网卡ip + * @param local_port 绑定的本地端口号 + * @return -1代表失败,其他为socket fd号 + */ + static int connect(const char *host, uint16_t port, bool async = true, const char *local_ip = "::", uint16_t local_port = 0); + + /** + * 创建tcp监听套接字 + * @param port 监听的本地端口 + * @param local_ip 绑定的本地网卡ip + * @param back_log accept列队长度 + * @return -1代表失败,其他为socket fd号 + */ + static int listen(const uint16_t port, const char *local_ip = "::", int back_log = 1024); + + /** + * 创建udp套接字 + * @param port 监听的本地端口 + * @param local_ip 绑定的本地网卡ip + * @param enable_reuse 是否允许重复bind端口 + * @return -1代表失败,其他为socket fd号 + */ + static int bindUdpSock(const uint16_t port, const char *local_ip = "::", bool enable_reuse = true); + + /** + * @brief 解除与 sock 相关的绑定关系 + * @param sock, socket fd 号 + * @return 0 成功, -1 失败 + */ + static int dissolveUdpSock(int sock); + + /** + * 开启TCP_NODELAY,降低TCP交互延时 + * @param fd socket fd号 + * @param on 是否开启 + * @return 0代表成功,-1为失败 + */ + static int setNoDelay(int fd, bool on = true); + + /** + * 写socket不触发SIG_PIPE信号(貌似只有mac有效) + * @param fd socket fd号 + * @return 0代表成功,-1为失败 + */ + static int setNoSigpipe(int fd); + + /** + * 设置读写socket是否阻塞 + * @param fd socket fd号 + * @param noblock 是否阻塞 + * @return 0代表成功,-1为失败 + */ + static int setNoBlocked(int fd, bool noblock = true); + + /** + * 设置socket接收缓存,默认貌似8K左右,一般有设置上限 + * 可以通过配置内核配置文件调整 + * @param fd socket fd号 + * @param size 接收缓存大小 + * @return 0代表成功,-1为失败 + */ + static int setRecvBuf(int fd, int size = SOCKET_DEFAULT_BUF_SIZE); + + /** + * 设置socket接收缓存,默认貌似8K左右,一般有设置上限 + * 可以通过配置内核配置文件调整 + * @param fd socket fd号 + * @param size 接收缓存大小 + * @return 0代表成功,-1为失败 + */ + static int setSendBuf(int fd, int size = SOCKET_DEFAULT_BUF_SIZE); + + /** + * 设置后续可绑定复用端口(处于TIME_WAITE状态) + * @param fd socket fd号 + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setReuseable(int fd, bool on = true, bool reuse_port = true); + + /** + * 运行发送或接收udp广播信息 + * @param fd socket fd号 + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setBroadcast(int fd, bool on = true); + + /** + * 是否开启TCP KeepAlive特性 + * @param fd socket fd号 + * @param on 是否开启该特性 + * @param idle keepalive空闲时间 + * @param interval keepalive探测时间间隔 + * @param times keepalive探测次数 + * @return 0代表成功,-1为失败 + */ + static int setKeepAlive(int fd, bool on = true, int interval = TCP_KEEPALIVE_INTERVAL, int idle = TCP_KEEPALIVE_TIME, int times = TCP_KEEPALIVE_PROBE_TIMES); + + /** + * 是否开启FD_CLOEXEC特性(多进程相关) + * @param fd fd号,不一定是socket + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setCloExec(int fd, bool on = true); + + /** + * 开启SO_LINGER特性 + * @param sock socket fd号 + * @param second 内核等待关闭socket超时时间,单位秒 + * @return 0代表成功,-1为失败 + */ + static int setCloseWait(int sock, int second = 0); + + /** + * dns解析 + * @param host 域名或ip + * @param port 端口号 + * @param addr sockaddr结构体 + * @return 是否成功 + */ + static bool getDomainIP(const char *host, uint16_t port, struct sockaddr_storage &addr, int ai_family = AF_INET, + int ai_socktype = SOCK_STREAM, int ai_protocol = IPPROTO_TCP, int expire_sec = 60); + + /** + * 设置组播ttl + * @param sock socket fd号 + * @param ttl ttl值 + * @return 0代表成功,-1为失败 + */ + static int setMultiTTL(int sock, uint8_t ttl = 64); + + /** + * 设置组播发送网卡 + * @param sock socket fd号 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int setMultiIF(int sock, const char *local_ip); + + /** + * 设置是否接收本机发出的组播包 + * @param fd socket fd号 + * @param acc 是否接收 + * @return 0代表成功,-1为失败 + */ + static int setMultiLOOP(int fd, bool acc = false); + + /** + * 加入组播 + * @param fd socket fd号 + * @param addr 组播地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int joinMultiAddr(int fd, const char *addr, const char *local_ip = "0.0.0.0"); + + /** + * 退出组播 + * @param fd socket fd号 + * @param addr 组播地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int leaveMultiAddr(int fd, const char *addr, const char *local_ip = "0.0.0.0"); + + /** + * 加入组播并只接受该源端的组播数据 + * @param sock socket fd号 + * @param addr 组播地址 + * @param src_ip 数据源端地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int joinMultiAddrFilter(int sock, const char *addr, const char *src_ip, const char *local_ip = "0.0.0.0"); + + /** + * 退出组播 + * @param fd socket fd号 + * @param addr 组播地址 + * @param src_ip 数据源端地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int leaveMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip = "0.0.0.0"); + + /** + * 获取该socket当前发生的错误 + * @param fd socket fd号 + * @return 错误代码 + */ + static int getSockError(int fd); + + /** + * 获取网卡列表 + * @return vector > + */ + static std::vector> getInterfaceList(); + + /** + * 获取本机默认网卡ip + */ + static std::string get_local_ip(); + + /** + * 获取该socket绑定的本地ip + * @param sock socket fd号 + */ + static std::string get_local_ip(int sock); + + /** + * 获取该socket绑定的本地端口 + * @param sock socket fd号 + */ + static uint16_t get_local_port(int sock); + + /** + * 获取该socket绑定的远端ip + * @param sock socket fd号 + */ + static std::string get_peer_ip(int sock); + + /** + * 获取该socket绑定的远端端口 + * @param sock socket fd号 + */ + static uint16_t get_peer_port(int sock); + + static bool support_ipv6(); + /** + * 线程安全的in_addr转ip字符串 + */ + static std::string inet_ntoa(const struct in_addr &addr); + static std::string inet_ntoa(const struct in6_addr &addr); + static std::string inet_ntoa(const struct sockaddr *addr); + static uint16_t inet_port(const struct sockaddr *addr); + static struct sockaddr_storage make_sockaddr(const char *ip, uint16_t port); + static socklen_t get_sock_len(const struct sockaddr *addr); + static bool get_sock_local_addr(int fd, struct sockaddr_storage &addr); + static bool get_sock_peer_addr(int fd, struct sockaddr_storage &addr); + + /** + * 获取网卡ip + * @param if_name 网卡名 + */ + static std::string get_ifr_ip(const char *if_name); + + /** + * 获取网卡名 + * @param local_op 网卡ip + */ + static std::string get_ifr_name(const char *local_op); + + /** + * 根据网卡名获取子网掩码 + * @param if_name 网卡名 + */ + static std::string get_ifr_mask(const char *if_name); + + /** + * 根据网卡名获取广播地址 + * @param if_name 网卡名 + */ + static std::string get_ifr_brdaddr(const char *if_name); + + /** + * 判断两个ip是否为同一网段 + * @param src_ip 我的ip + * @param dts_ip 对方ip + */ + static bool in_same_lan(const char *src_ip, const char *dts_ip); + + /** + * 判断是否为ipv4地址 + */ + static bool is_ipv4(const char *str); + + /** + * 判断是否为ipv6地址 + */ + static bool is_ipv6(const char *str); +}; + +} // namespace toolkit +#endif // !NETWORK_SOCKUTIL_H diff --git a/ToolKit/Poller/EventPoller.cpp b/ToolKit/Poller/EventPoller.cpp new file mode 100644 index 0000000..54d26f8 --- /dev/null +++ b/ToolKit/Poller/EventPoller.cpp @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "SelectWrap.h" +#include "EventPoller.h" +#include "Util/util.h" +#include "Util/uv_errno.h" +#include "Util/TimeTicker.h" +#include "Util/NoticeCenter.h" +#include "Network/sockutil.h" + +#if defined(HAS_EPOLL) +#include + +#if !defined(EPOLLEXCLUSIVE) +#define EPOLLEXCLUSIVE 0 +#endif + +#define EPOLL_SIZE 1024 + +//防止epoll惊群 +#ifndef EPOLLEXCLUSIVE +#define EPOLLEXCLUSIVE 0 +#endif + +#define toEpoll(event) (((event) & Event_Read) ? EPOLLIN : 0) \ + | (((event) & Event_Write) ? EPOLLOUT : 0) \ + | (((event) & Event_Error) ? (EPOLLHUP | EPOLLERR) : 0) \ + | (((event) & Event_LT) ? 0 : EPOLLET) + +#define toPoller(epoll_event) (((epoll_event) & (EPOLLIN | EPOLLRDNORM | EPOLLHUP)) ? Event_Read : 0) \ + | (((epoll_event) & (EPOLLOUT | EPOLLWRNORM)) ? Event_Write : 0) \ + | (((epoll_event) & EPOLLHUP) ? Event_Error : 0) \ + | (((epoll_event) & EPOLLERR) ? Event_Error : 0) +#define create_event() epoll_create(EPOLL_SIZE) +#endif //HAS_EPOLL + +#if defined(HAS_KQUEUE) +#include +#define KEVENT_SIZE 1024 +#define create_event() kqueue() +#endif // HAS_KQUEUE + +using namespace std; + +namespace toolkit { + +EventPoller &EventPoller::Instance() { + return *(EventPollerPool::Instance().getFirstPoller()); +} + +void EventPoller::addEventPipe() { + SockUtil::setNoBlocked(_pipe.readFD()); + SockUtil::setNoBlocked(_pipe.writeFD()); + + // 添加内部管道事件 + if (addEvent(_pipe.readFD(), EventPoller::Event_Read, [this](int event) { onPipeEvent(); }) == -1) { + throw std::runtime_error("Add pipe fd to poller failed"); + } +} + +EventPoller::EventPoller(std::string name) { +#if defined(HAS_EPOLL) || defined(HAS_KQUEUE) + _event_fd = create_event(); + if (_event_fd == -1) { + throw runtime_error(StrPrinter << "Create event fd failed: " << get_uv_errmsg()); + } + SockUtil::setCloExec(_event_fd); +#endif //HAS_EPOLL + + _name = std::move(name); + _logger = Logger::Instance().shared_from_this(); + addEventPipe(); +} + +void EventPoller::shutdown() { + async_l([]() { + throw ExitException(); + }, false, true); + + if (_loop_thread) { + //防止作为子进程时崩溃 + try { _loop_thread->join(); } catch (...) { _loop_thread->detach(); } + delete _loop_thread; + _loop_thread = nullptr; + } +} + +EventPoller::~EventPoller() { + shutdown(); + +#if defined(HAS_EPOLL) || defined(HAS_KQUEUE) + if (_event_fd != -1) { + close(_event_fd); + _event_fd = -1; + } +#endif + + //退出前清理管道中的数据 + onPipeEvent(true); + InfoL << getThreadName(); +} + +int EventPoller::addEvent(int fd, int event, PollEventCB cb) { + TimeTicker(); + if (!cb) { + WarnL << "PollEventCB is empty"; + return -1; + } + + if (isCurrentThread()) { +#if defined(HAS_EPOLL) + struct epoll_event ev = {0}; + ev.events = toEpoll(event) ; + ev.data.fd = fd; + int ret = epoll_ctl(_event_fd, EPOLL_CTL_ADD, fd, &ev); + if (ret != -1) { + _event_map.emplace(fd, std::make_shared(std::move(cb))); + } + return ret; +#elif defined(HAS_KQUEUE) + struct kevent kev[2]; + int index = 0; + if (event & Event_Read) { + EV_SET(&kev[index++], fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr); + } + if (event & Event_Write) { + EV_SET(&kev[index++], fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, nullptr); + } + int ret = kevent(_event_fd, kev, index, nullptr, 0, nullptr); + if (ret != -1) { + _event_map.emplace(fd, std::make_shared(std::move(cb))); + } + return ret; +#else +#ifndef _WIN32 + // win32平台,socket套接字不等于文件描述符,所以可能不适用这个限制 + if (fd >= FD_SETSIZE) { + WarnL << "select() can not watch fd bigger than " << FD_SETSIZE; + return -1; + } +#endif + auto record = std::make_shared(); + record->fd = fd; + record->event = event; + record->call_back = std::move(cb); + _event_map.emplace(fd, record); + return 0; +#endif + } + + async([this, fd, event, cb]() mutable { + addEvent(fd, event, std::move(cb)); + }); + return 0; +} + +int EventPoller::delEvent(int fd, PollCompleteCB cb) { + TimeTicker(); + if (!cb) { + cb = [](bool success) {}; + } + + if (isCurrentThread()) { +#if defined(HAS_EPOLL) + int ret = -1; + if (_event_map.erase(fd)) { + _event_cache_expired.emplace(fd); + ret = epoll_ctl(_event_fd, EPOLL_CTL_DEL, fd, nullptr); + } + cb(ret != -1); + return ret; +#elif defined(HAS_KQUEUE) + int ret = -1; + if (_event_map.erase(fd)) { + _event_cache_expired.emplace(fd); + struct kevent kev[2]; + int index = 0; + EV_SET(&kev[index++], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + EV_SET(&kev[index++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + ret = kevent(_event_fd, kev, index, nullptr, 0, nullptr); + } + cb(ret != -1); + return ret; +#else + int ret = -1; + if (_event_map.erase(fd)) { + _event_cache_expired.emplace(fd); + ret = 0; + } + cb(ret != -1); + return ret; +#endif //HAS_EPOLL + } + + //跨线程操作 + async([this, fd, cb]() mutable { + delEvent(fd, std::move(cb)); + }); + return 0; +} + +int EventPoller::modifyEvent(int fd, int event, PollCompleteCB cb) { + TimeTicker(); + if (!cb) { + cb = [](bool success) {}; + } + if (isCurrentThread()) { +#if defined(HAS_EPOLL) + struct epoll_event ev = { 0 }; + ev.events = toEpoll(event); + ev.data.fd = fd; + auto ret = epoll_ctl(_event_fd, EPOLL_CTL_MOD, fd, &ev); + cb(ret != -1); + return ret; +#elif defined(HAS_KQUEUE) + struct kevent kev[2]; + int index = 0; + EV_SET(&kev[index++], fd, EVFILT_READ, event & Event_Read ? EV_ADD | EV_CLEAR : EV_DELETE, 0, 0, nullptr); + EV_SET(&kev[index++], fd, EVFILT_WRITE, event & Event_Write ? EV_ADD | EV_CLEAR : EV_DELETE, 0, 0, nullptr); + int ret = kevent(_event_fd, kev, index, nullptr, 0, nullptr); + cb(ret != -1); + return ret; +#else + auto it = _event_map.find(fd); + if (it != _event_map.end()) { + it->second->event = event; + } + cb(it != _event_map.end()); + return it != _event_map.end() ? 0 : -1; +#endif // HAS_EPOLL + } + async([this, fd, event, cb]() mutable { + modifyEvent(fd, event, std::move(cb)); + }); + return 0; +} + +Task::Ptr EventPoller::async(TaskIn task, bool may_sync) { + return async_l(std::move(task), may_sync, false); +} + +Task::Ptr EventPoller::async_first(TaskIn task, bool may_sync) { + return async_l(std::move(task), may_sync, true); +} + +Task::Ptr EventPoller::async_l(TaskIn task, bool may_sync, bool first) { + TimeTicker(); + if (may_sync && isCurrentThread()) { + task(); + return nullptr; + } + + auto ret = std::make_shared(std::move(task)); + { + lock_guard lck(_mtx_task); + if (first) { + _list_task.emplace_front(ret); + } else { + _list_task.emplace_back(ret); + } + } + //写数据到管道,唤醒主线程 + _pipe.write("", 1); + return ret; +} + +bool EventPoller::isCurrentThread() { + return !_loop_thread || _loop_thread->get_id() == this_thread::get_id(); +} + +inline void EventPoller::onPipeEvent(bool flush) { + char buf[1024]; + int err = 0; + if (!flush) { + for (;;) { + if ((err = _pipe.read(buf, sizeof(buf))) > 0) { + // 读到管道数据,继续读,直到读空为止 + continue; + } + if (err == 0 || get_uv_error(true) != UV_EAGAIN) { + // 收到eof或非EAGAIN(无更多数据)错误,说明管道无效了,重新打开管道 + ErrorL << "Invalid pipe fd of event poller, reopen it"; + delEvent(_pipe.readFD()); + _pipe.reOpen(); + addEventPipe(); + } + break; + } + } + + decltype(_list_task) _list_swap; + { + lock_guard lck(_mtx_task); + _list_swap.swap(_list_task); + } + + _list_swap.for_each([&](const Task::Ptr &task) { + try { + (*task)(); + } catch (ExitException &) { + _exit_flag = true; + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do async task: " << ex.what(); + } + }); +} + +SocketRecvBuffer::Ptr EventPoller::getSharedBuffer(bool is_udp) { +#if !defined(__linux) && !defined(__linux__) + // 非Linux平台下,tcp和udp共享recvfrom方案,使用同一个buffer + is_udp = 0; +#endif + auto ret = _shared_buffer[is_udp].lock(); + if (!ret) { + ret = SocketRecvBuffer::create(is_udp); + _shared_buffer[is_udp] = ret; + } + return ret; +} + +thread::id EventPoller::getThreadId() const { + return _loop_thread ? _loop_thread->get_id() : thread::id(); +} + +const std::string& EventPoller::getThreadName() const { + return _name; +} + +static thread_local std::weak_ptr s_current_poller; + +// static +EventPoller::Ptr EventPoller::getCurrentPoller() { + return s_current_poller.lock(); +} + +void EventPoller::runLoop(bool blocked, bool ref_self) { + if (blocked) { + if (ref_self) { + s_current_poller = shared_from_this(); + } + _sem_run_started.post(); + _exit_flag = false; + uint64_t minDelay; +#if defined(HAS_EPOLL) + struct epoll_event events[EPOLL_SIZE]; + while (!_exit_flag) { + minDelay = getMinDelay(); + startSleep();//用于统计当前线程负载情况 + int ret = epoll_wait(_event_fd, events, EPOLL_SIZE, minDelay ? minDelay : -1); + sleepWakeUp();//用于统计当前线程负载情况 + if (ret <= 0) { + //超时或被打断 + continue; + } + + _event_cache_expired.clear(); + + for (int i = 0; i < ret; ++i) { + struct epoll_event &ev = events[i]; + int fd = ev.data.fd; + if (_event_cache_expired.count(fd)) { + //event cache refresh + continue; + } + + auto it = _event_map.find(fd); + if (it == _event_map.end()) { + epoll_ctl(_event_fd, EPOLL_CTL_DEL, fd, nullptr); + continue; + } + auto cb = it->second; + try { + (*cb)(toPoller(ev.events)); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do event task: " << ex.what(); + } + } + } +#elif defined(HAS_KQUEUE) + struct kevent kevents[KEVENT_SIZE]; + while (!_exit_flag) { + minDelay = getMinDelay(); + struct timespec timeout = { (long)minDelay / 1000, (long)minDelay % 1000 * 1000000 }; + + startSleep(); + int ret = kevent(_event_fd, nullptr, 0, kevents, KEVENT_SIZE, minDelay ? &timeout : nullptr); + sleepWakeUp(); + if (ret <= 0) { + continue; + } + + _event_cache_expired.clear(); + + for (int i = 0; i < ret; ++i) { + auto &kev = kevents[i]; + auto fd = kev.ident; + if (_event_cache_expired.count(fd)) { + // event cache refresh + continue; + } + + auto it = _event_map.find(fd); + if (it == _event_map.end()) { + EV_SET(&kev, fd, kev.filter, EV_DELETE, 0, 0, nullptr); + kevent(_event_fd, &kev, 1, nullptr, 0, nullptr); + continue; + } + auto cb = it->second; + int event = 0; + switch (kev.filter) { + case EVFILT_READ: event = Event_Read; break; + case EVFILT_WRITE: event = Event_Write; break; + default: WarnL << "unknown kevent filter: " << kev.filter; break; + } + + try { + (*cb)(event); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do event task: " << ex.what(); + } + } + } +#else + int ret, max_fd; + FdSet set_read, set_write, set_err; + List callback_list; + struct timeval tv; + while (!_exit_flag) { + //定时器事件中可能操作_event_map + minDelay = getMinDelay(); + tv.tv_sec = (decltype(tv.tv_sec)) (minDelay / 1000); + tv.tv_usec = 1000 * (minDelay % 1000); + + set_read.fdZero(); + set_write.fdZero(); + set_err.fdZero(); + max_fd = 0; + for (auto &pr : _event_map) { + if (pr.first > max_fd) { + max_fd = pr.first; + } + if (pr.second->event & Event_Read) { + set_read.fdSet(pr.first);//监听管道可读事件 + } + if (pr.second->event & Event_Write) { + set_write.fdSet(pr.first);//监听管道可写事件 + } + if (pr.second->event & Event_Error) { + set_err.fdSet(pr.first);//监听管道错误事件 + } + } + + startSleep();//用于统计当前线程负载情况 + ret = zl_select(max_fd + 1, &set_read, &set_write, &set_err, minDelay ? &tv : nullptr); + sleepWakeUp();//用于统计当前线程负载情况 + + if (ret <= 0) { + //超时或被打断 + continue; + } + + _event_cache_expired.clear(); + + //收集select事件类型 + for (auto &pr : _event_map) { + int event = 0; + if (set_read.isSet(pr.first)) { + event |= Event_Read; + } + if (set_write.isSet(pr.first)) { + event |= Event_Write; + } + if (set_err.isSet(pr.first)) { + event |= Event_Error; + } + if (event != 0) { + pr.second->attach = event; + callback_list.emplace_back(pr.second); + } + } + + callback_list.for_each([&](Poll_Record::Ptr &record) { + if (_event_cache_expired.count(record->fd)) { + //event cache refresh + return; + } + + try { + record->call_back(record->attach); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do event task: " << ex.what(); + } + }); + callback_list.clear(); + } +#endif //HAS_EPOLL + } else { + _loop_thread = new thread(&EventPoller::runLoop, this, true, ref_self); + _sem_run_started.wait(); + } +} + +uint64_t EventPoller::flushDelayTask(uint64_t now_time) { + decltype(_delay_task_map) task_copy; + task_copy.swap(_delay_task_map); + + for (auto it = task_copy.begin(); it != task_copy.end() && it->first <= now_time; it = task_copy.erase(it)) { + //已到期的任务 + try { + auto next_delay = (*(it->second))(); + if (next_delay) { + //可重复任务,更新时间截止线 + _delay_task_map.emplace(next_delay + now_time, std::move(it->second)); + } + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do delay task: " << ex.what(); + } + } + + task_copy.insert(_delay_task_map.begin(), _delay_task_map.end()); + task_copy.swap(_delay_task_map); + + auto it = _delay_task_map.begin(); + if (it == _delay_task_map.end()) { + //没有剩余的定时器了 + return 0; + } + //最近一个定时器的执行延时 + return it->first - now_time; +} + +uint64_t EventPoller::getMinDelay() { + auto it = _delay_task_map.begin(); + if (it == _delay_task_map.end()) { + //没有剩余的定时器了 + return 0; + } + auto now = getCurrentMillisecond(); + if (it->first > now) { + //所有任务尚未到期 + return it->first - now; + } + //执行已到期的任务并刷新休眠延时 + return flushDelayTask(now); +} + +EventPoller::DelayTask::Ptr EventPoller::doDelayTask(uint64_t delay_ms, function task) { + DelayTask::Ptr ret = std::make_shared(std::move(task)); + auto time_line = getCurrentMillisecond() + delay_ms; + async_first([time_line, ret, this]() { + //异步执行的目的是刷新select或epoll的休眠时间 + _delay_task_map.emplace(time_line, ret); + }); + return ret; +} + + +/////////////////////////////////////////////// + +static size_t s_pool_size = 0; +static bool s_enable_cpu_affinity = true; + +INSTANCE_IMP(EventPollerPool) + +EventPoller::Ptr EventPollerPool::getFirstPoller() { + return static_pointer_cast(_threads.front()); +} + +EventPoller::Ptr EventPollerPool::getPoller(bool prefer_current_thread) { + auto poller = EventPoller::getCurrentPoller(); + if (prefer_current_thread && _prefer_current_thread && poller) { + return poller; + } + return static_pointer_cast(getExecutor()); +} + +void EventPollerPool::preferCurrentThread(bool flag) { + _prefer_current_thread = flag; +} + +const std::string EventPollerPool::kOnStarted = "kBroadcastEventPollerPoolStarted"; + +EventPollerPool::EventPollerPool() { + auto size = addPoller("event poller", s_pool_size, ThreadPool::PRIORITY_HIGHEST, true, s_enable_cpu_affinity); + NOTICE_EMIT(EventPollerPoolOnStartedArgs, kOnStarted, *this, size); + InfoL << "EventPoller created size: " << size; +} + +void EventPollerPool::setPoolSize(size_t size) { + s_pool_size = size; +} + +void EventPollerPool::enableCpuAffinity(bool enable) { + s_enable_cpu_affinity = enable; +} + +} // namespace toolkit + diff --git a/ToolKit/Poller/EventPoller.h b/ToolKit/Poller/EventPoller.h new file mode 100644 index 0000000..7288784 --- /dev/null +++ b/ToolKit/Poller/EventPoller.h @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef EventPoller_h +#define EventPoller_h + +#include +#include +#include +#include +#include +#include +#include +#include "PipeWrap.h" +#include "Util/logger.h" +#include "Util/List.h" +#include "Thread/TaskExecutor.h" +#include "Thread/ThreadPool.h" +#include "Network/Buffer.h" +#include "Network/BufferSock.h" + +#if defined(__linux__) || defined(__linux) +#define HAS_EPOLL +#endif //__linux__ + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#define HAS_KQUEUE +#endif // __APPLE__ + +namespace toolkit { + +class EventPoller : public TaskExecutor, public AnyStorage, public std::enable_shared_from_this { +public: + friend class TaskExecutorGetterImp; + + using Ptr = std::shared_ptr; + using PollEventCB = std::function; + using PollCompleteCB = std::function; + using DelayTask = TaskCancelableImp; + + typedef enum { + Event_Read = 1 << 0, //读事件 + Event_Write = 1 << 1, //写事件 + Event_Error = 1 << 2, //错误事件 + Event_LT = 1 << 3,//水平触发 + } Poll_Event; + + ~EventPoller(); + + /** + * 获取EventPollerPool单例中的第一个EventPoller实例, + * 保留该接口是为了兼容老代码 + * @return 单例 + */ + static EventPoller &Instance(); + + /** + * 添加事件监听 + * @param fd 监听的文件描述符 + * @param event 事件类型,例如 Event_Read | Event_Write + * @param cb 事件回调functional + * @return -1:失败,0:成功 + */ + int addEvent(int fd, int event, PollEventCB cb); + + /** + * 删除事件监听 + * @param fd 监听的文件描述符 + * @param cb 删除成功回调functional + * @return -1:失败,0:成功 + */ + int delEvent(int fd, PollCompleteCB cb = nullptr); + + /** + * 修改监听事件类型 + * @param fd 监听的文件描述符 + * @param event 事件类型,例如 Event_Read | Event_Write + * @return -1:失败,0:成功 + */ + int modifyEvent(int fd, int event, PollCompleteCB cb = nullptr); + + /** + * 异步执行任务 + * @param task 任务 + * @param may_sync 如果调用该函数的线程就是本对象的轮询线程,那么may_sync为true时就是同步执行任务 + * @return 是否成功,一定会返回true + */ + Task::Ptr async(TaskIn task, bool may_sync = true) override; + + /** + * 同async方法,不过是把任务打入任务列队头,这样任务优先级最高 + * @param task 任务 + * @param may_sync 如果调用该函数的线程就是本对象的轮询线程,那么may_sync为true时就是同步执行任务 + * @return 是否成功,一定会返回true + */ + Task::Ptr async_first(TaskIn task, bool may_sync = true) override; + + /** + * 判断执行该接口的线程是否为本对象的轮询线程 + * @return 是否为本对象的轮询线程 + */ + bool isCurrentThread(); + + /** + * 延时执行某个任务 + * @param delay_ms 延时毫秒数 + * @param task 任务,返回值为0时代表不再重复任务,否则为下次执行延时,如果任务中抛异常,那么默认不重复任务 + * @return 可取消的任务标签 + */ + DelayTask::Ptr doDelayTask(uint64_t delay_ms, std::function task); + + /** + * 获取当前线程关联的Poller实例 + */ + static EventPoller::Ptr getCurrentPoller(); + + /** + * 获取当前线程下所有socket共享的读缓存 + */ + SocketRecvBuffer::Ptr getSharedBuffer(bool is_udp); + + /** + * 获取poller线程id + */ + std::thread::id getThreadId() const; + + /** + * 获取线程名 + */ + const std::string& getThreadName() const; + +private: + /** + * 本对象只允许在EventPollerPool中构造 + */ + EventPoller(std::string name); + + /** + * 执行事件轮询 + * @param blocked 是否用执行该接口的线程执行轮询 + * @param ref_self 是记录本对象到thread local变量 + */ + void runLoop(bool blocked, bool ref_self); + + /** + * 内部管道事件,用于唤醒轮询线程用 + */ + void onPipeEvent(bool flush = false); + + /** + * 切换线程并执行任务 + * @param task + * @param may_sync + * @param first + * @return 可取消的任务本体,如果已经同步执行,则返回nullptr + */ + Task::Ptr async_l(TaskIn task, bool may_sync = true, bool first = false); + + /** + * 结束事件轮询 + * 需要指出的是,一旦结束就不能再次恢复轮询线程 + */ + void shutdown(); + + /** + * 刷新延时任务 + */ + uint64_t flushDelayTask(uint64_t now); + + /** + * 获取select或epoll休眠时间 + */ + uint64_t getMinDelay(); + + /** + * 添加管道监听事件 + */ + void addEventPipe(); + +private: + class ExitException : public std::exception {}; + +private: + //标记loop线程是否退出 + bool _exit_flag; + //线程名 + std::string _name; + //当前线程下,所有socket共享的读缓存 + std::weak_ptr _shared_buffer[2]; + //执行事件循环的线程 + std::thread *_loop_thread = nullptr; + //通知事件循环的线程已启动 + semaphore _sem_run_started; + + //内部事件管道 + PipeWrap _pipe; + //从其他线程切换过来的任务 + std::mutex _mtx_task; + List _list_task; + + //保持日志可用 + Logger::Ptr _logger; + +#if defined(HAS_EPOLL) || defined(HAS_KQUEUE) + // epoll和kqueue相关 + int _event_fd = -1; + std::unordered_map > _event_map; +#else + // select相关 + struct Poll_Record { + using Ptr = std::shared_ptr; + int fd; + int event; + int attach; + PollEventCB call_back; + }; + std::unordered_map _event_map; +#endif //HAS_EPOLL + std::unordered_set _event_cache_expired; + + //定时器相关 + std::multimap _delay_task_map; +}; + +class EventPollerPool : public std::enable_shared_from_this, public TaskExecutorGetterImp { +public: + using Ptr = std::shared_ptr; + static const std::string kOnStarted; + #define EventPollerPoolOnStartedArgs EventPollerPool &pool, size_t &size + + ~EventPollerPool() = default; + + /** + * 获取单例 + * @return + */ + static EventPollerPool &Instance(); + + /** + * 设置EventPoller个数,在EventPollerPool单例创建前有效 + * 在不调用此方法的情况下,默认创建thread::hardware_concurrency()个EventPoller实例 + * @param size EventPoller个数,如果为0则为thread::hardware_concurrency() + */ + static void setPoolSize(size_t size = 0); + + /** + * 内部创建线程是否设置cpu亲和性,默认设置cpu亲和性 + */ + static void enableCpuAffinity(bool enable); + + /** + * 获取第一个实例 + * @return + */ + EventPoller::Ptr getFirstPoller(); + + /** + * 根据负载情况获取轻负载的实例 + * 如果优先返回当前线程,那么会返回当前线程 + * 返回当前线程的目的是为了提高线程安全性 + * @param prefer_current_thread 是否优先获取当前线程 + */ + EventPoller::Ptr getPoller(bool prefer_current_thread = true); + + /** + * 设置 getPoller() 是否优先返回当前线程 + * 在批量创建Socket对象时,如果优先返回当前线程, + * 那么将导致负载不够均衡,所以可以暂时关闭然后再开启 + * @param flag 是否优先返回当前线程 + */ + void preferCurrentThread(bool flag = true); + +private: + EventPollerPool(); + +private: + bool _prefer_current_thread = true; +}; + +} // namespace toolkit +#endif /* EventPoller_h */ diff --git a/ToolKit/Poller/Pipe.cpp b/ToolKit/Poller/Pipe.cpp new file mode 100644 index 0000000..f2338ca --- /dev/null +++ b/ToolKit/Poller/Pipe.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "Pipe.h" +#include "Network/sockutil.h" + +using namespace std; + +namespace toolkit { + +Pipe::Pipe(const onRead &cb, const EventPoller::Ptr &poller) { + _poller = poller; + if (!_poller) { + _poller = EventPollerPool::Instance().getPoller(); + } + _pipe = std::make_shared(); + auto pipe = _pipe; + _poller->addEvent(_pipe->readFD(), EventPoller::Event_Read, [cb, pipe](int event) { +#if defined(_WIN32) + unsigned long nread = 1024; +#else + int nread = 1024; +#endif //defined(_WIN32) + ioctl(pipe->readFD(), FIONREAD, &nread); +#if defined(_WIN32) + std::shared_ptr buf(new char[nread + 1], [](char *ptr) {delete[] ptr; }); + buf.get()[nread] = '\0'; + nread = pipe->read(buf.get(), nread + 1); + if (cb) { + cb(nread, buf.get()); + } +#else + char buf[nread + 1]; + buf[nread] = '\0'; + nread = pipe->read(buf, sizeof(buf)); + if (cb) { + cb(nread, buf); + } +#endif // defined(_WIN32) + }); +} + +Pipe::~Pipe() { + if (_pipe) { + auto pipe = _pipe; + _poller->delEvent(pipe->readFD(), [pipe](bool success) {}); + } +} + +void Pipe::send(const char *buf, int size) { + _pipe->write(buf, size); +} + +} // namespace toolkit \ No newline at end of file diff --git a/ToolKit/Poller/Pipe.h b/ToolKit/Poller/Pipe.h new file mode 100644 index 0000000..801ae31 --- /dev/null +++ b/ToolKit/Poller/Pipe.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef Pipe_h +#define Pipe_h + +#include +#include "PipeWrap.h" +#include "EventPoller.h" + +namespace toolkit { + +class Pipe { +public: + using onRead = std::function; + + Pipe(const onRead &cb = nullptr, const EventPoller::Ptr &poller = nullptr); + ~Pipe(); + + void send(const char *send, int size = 0); + +private: + std::shared_ptr _pipe; + EventPoller::Ptr _poller; +}; + +} // namespace toolkit +#endif /* Pipe_h */ \ No newline at end of file diff --git a/ToolKit/Poller/PipeWrap.cpp b/ToolKit/Poller/PipeWrap.cpp new file mode 100644 index 0000000..ebd9abd --- /dev/null +++ b/ToolKit/Poller/PipeWrap.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "PipeWrap.h" +#include "Util/util.h" +#include "Util/uv_errno.h" +#include "Network/sockutil.h" + +using namespace std; + +#define checkFD(fd) \ + if (fd == -1) { \ + clearFD(); \ + throw runtime_error(StrPrinter << "Create windows pipe failed: " << get_uv_errmsg());\ + } + +#define closeFD(fd) \ + if (fd != -1) { \ + close(fd);\ + fd = -1;\ + } + +namespace toolkit { + +PipeWrap::PipeWrap() { + reOpen(); +} + +void PipeWrap::reOpen() { + clearFD(); +#if defined(_WIN32) + const char *localip = SockUtil::support_ipv6() ? "::1" : "127.0.0.1"; + auto listener_fd = SockUtil::listen(0, localip); + checkFD(listener_fd) + SockUtil::setNoBlocked(listener_fd,false); + auto localPort = SockUtil::get_local_port(listener_fd); + _pipe_fd[1] = SockUtil::connect(localip, localPort,false); + checkFD(_pipe_fd[1]) + _pipe_fd[0] = (int)accept(listener_fd, nullptr, nullptr); + checkFD(_pipe_fd[0]) + SockUtil::setNoDelay(_pipe_fd[0]); + SockUtil::setNoDelay(_pipe_fd[1]); + close(listener_fd); +#else + if (pipe(_pipe_fd) == -1) { + throw runtime_error(StrPrinter << "Create posix pipe failed: " << get_uv_errmsg()); + } +#endif // defined(_WIN32) + SockUtil::setNoBlocked(_pipe_fd[0], true); + SockUtil::setNoBlocked(_pipe_fd[1], false); + SockUtil::setCloExec(_pipe_fd[0]); + SockUtil::setCloExec(_pipe_fd[1]); +} + +void PipeWrap::clearFD() { + closeFD(_pipe_fd[0]); + closeFD(_pipe_fd[1]); +} + +PipeWrap::~PipeWrap() { + clearFD(); +} + +int PipeWrap::write(const void *buf, int n) { + int ret; + do { +#if defined(_WIN32) + ret = send(_pipe_fd[1], (char *)buf, n, 0); +#else + ret = ::write(_pipe_fd[1], buf, n); +#endif // defined(_WIN32) + } while (-1 == ret && UV_EINTR == get_uv_error(true)); + return ret; +} + +int PipeWrap::read(void *buf, int n) { + int ret; + do { +#if defined(_WIN32) + ret = recv(_pipe_fd[0], (char *)buf, n, 0); +#else + ret = ::read(_pipe_fd[0], buf, n); +#endif // defined(_WIN32) + } while (-1 == ret && UV_EINTR == get_uv_error(true)); + return ret; +} + +} /* namespace toolkit*/ diff --git a/ToolKit/Poller/PipeWrap.h b/ToolKit/Poller/PipeWrap.h new file mode 100644 index 0000000..e457df6 --- /dev/null +++ b/ToolKit/Poller/PipeWrap.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PipeWarp_h +#define PipeWarp_h + +namespace toolkit { + +class PipeWrap { +public: + PipeWrap(); + ~PipeWrap(); + int write(const void *buf, int n); + int read(void *buf, int n); + int readFD() const { return _pipe_fd[0]; } + int writeFD() const { return _pipe_fd[1]; } + void reOpen(); + +private: + void clearFD(); + +private: + int _pipe_fd[2] = { -1, -1 }; +}; + +} /* namespace toolkit */ +#endif // !PipeWarp_h + diff --git a/ToolKit/Poller/SelectWrap.cpp b/ToolKit/Poller/SelectWrap.cpp new file mode 100644 index 0000000..475c590 --- /dev/null +++ b/ToolKit/Poller/SelectWrap.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "SelectWrap.h" + +using namespace std; + +namespace toolkit { + +FdSet::FdSet() { + _ptr = new fd_set; +} + +FdSet::~FdSet() { + delete (fd_set *)_ptr; +} + +void FdSet::fdZero() { + FD_ZERO((fd_set *)_ptr); +} + +void FdSet::fdClr(int fd) { + FD_CLR(fd, (fd_set *)_ptr); +} + +void FdSet::fdSet(int fd) { + FD_SET(fd, (fd_set *)_ptr); +} + +bool FdSet::isSet(int fd) { + return FD_ISSET(fd, (fd_set *)_ptr); +} + +int zl_select(int cnt, FdSet *read, FdSet *write, FdSet *err, struct timeval *tv) { + void *rd, *wt, *er; + rd = read ? read->_ptr : nullptr; + wt = write ? write->_ptr : nullptr; + er = err ? err->_ptr : nullptr; + return ::select(cnt, (fd_set *) rd, (fd_set *) wt, (fd_set *) er, tv); +} + +} /* namespace toolkit */ + + + diff --git a/ToolKit/Poller/SelectWrap.h b/ToolKit/Poller/SelectWrap.h new file mode 100644 index 0000000..c68b35a --- /dev/null +++ b/ToolKit/Poller/SelectWrap.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SRC_POLLER_SELECTWRAP_H_ +#define SRC_POLLER_SELECTWRAP_H_ + +#include "Util/util.h" + +namespace toolkit { + +class FdSet { +public: + FdSet(); + ~FdSet(); + void fdZero(); + void fdSet(int fd); + void fdClr(int fd); + bool isSet(int fd); + void *_ptr; +}; + +int zl_select(int cnt, FdSet *read, FdSet *write, FdSet *err, struct timeval *tv); + +} /* namespace toolkit */ +#endif /* SRC_POLLER_SELECTWRAP_H_ */ diff --git a/ToolKit/Poller/Timer.cpp b/ToolKit/Poller/Timer.cpp new file mode 100644 index 0000000..2512fbb --- /dev/null +++ b/ToolKit/Poller/Timer.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Timer.h" + +namespace toolkit { + +Timer::Timer(float second, const std::function &cb, const EventPoller::Ptr &poller) { + _poller = poller; + if (!_poller) { + _poller = EventPollerPool::Instance().getPoller(); + } + _tag = _poller->doDelayTask((uint64_t) (second * 1000), [cb, second]() { + try { + if (cb()) { + //重复的任务 + return (uint64_t) (1000 * second); + } + //该任务不再重复 + return (uint64_t) 0; + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do timer task: " << ex.what(); + return (uint64_t) (1000 * second); + } + }); +} + +Timer::~Timer() { + auto tag = _tag.lock(); + if (tag) { + tag->cancel(); + } +} + +} // namespace toolkit diff --git a/ToolKit/Poller/Timer.h b/ToolKit/Poller/Timer.h new file mode 100644 index 0000000..99ed419 --- /dev/null +++ b/ToolKit/Poller/Timer.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef Timer_h +#define Timer_h + +#include +#include "EventPoller.h" + +namespace toolkit { + +class Timer { +public: + using Ptr = std::shared_ptr; + + /** + * 构造定时器 + * @param second 定时器重复秒数 + * @param cb 定时器任务,返回true表示重复下次任务,否则不重复,如果任务中抛异常,则默认重复下次任务 + * @param poller EventPoller对象,可以为nullptr + */ + Timer(float second, const std::function &cb, const EventPoller::Ptr &poller); + ~Timer(); + +private: + std::weak_ptr _tag; + //定时器保持EventPoller的强引用 + EventPoller::Ptr _poller; +}; + +} // namespace toolkit +#endif /* Timer_h */ diff --git a/ToolKit/Thread/TaskExecutor.cpp b/ToolKit/Thread/TaskExecutor.cpp new file mode 100644 index 0000000..8388087 --- /dev/null +++ b/ToolKit/Thread/TaskExecutor.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "TaskExecutor.h" +#include "Poller/EventPoller.h" +#include "Util/onceToken.h" +#include "Util/TimeTicker.h" + +using namespace std; + +namespace toolkit { + +ThreadLoadCounter::ThreadLoadCounter(uint64_t max_size, uint64_t max_usec) { + _last_sleep_time = _last_wake_time = getCurrentMicrosecond(); + _max_size = max_size; + _max_usec = max_usec; +} + +void ThreadLoadCounter::startSleep() { + lock_guard lck(_mtx); + _sleeping = true; + auto current_time = getCurrentMicrosecond(); + auto run_time = current_time - _last_wake_time; + _last_sleep_time = current_time; + _time_list.emplace_back(run_time, false); + if (_time_list.size() > _max_size) { + _time_list.pop_front(); + } +} + +void ThreadLoadCounter::sleepWakeUp() { + lock_guard lck(_mtx); + _sleeping = false; + auto current_time = getCurrentMicrosecond(); + auto sleep_time = current_time - _last_sleep_time; + _last_wake_time = current_time; + _time_list.emplace_back(sleep_time, true); + if (_time_list.size() > _max_size) { + _time_list.pop_front(); + } +} + +int ThreadLoadCounter::load() { + lock_guard lck(_mtx); + uint64_t totalSleepTime = 0; + uint64_t totalRunTime = 0; + _time_list.for_each([&](const TimeRecord &rcd) { + if (rcd._sleep) { + totalSleepTime += rcd._time; + } else { + totalRunTime += rcd._time; + } + }); + + if (_sleeping) { + totalSleepTime += (getCurrentMicrosecond() - _last_sleep_time); + } else { + totalRunTime += (getCurrentMicrosecond() - _last_wake_time); + } + + uint64_t totalTime = totalRunTime + totalSleepTime; + while ((_time_list.size() != 0) && (totalTime > _max_usec || _time_list.size() > _max_size)) { + TimeRecord &rcd = _time_list.front(); + if (rcd._sleep) { + totalSleepTime -= rcd._time; + } else { + totalRunTime -= rcd._time; + } + totalTime -= rcd._time; + _time_list.pop_front(); + } + if (totalTime == 0) { + return 0; + } + return (int) (totalRunTime * 100 / totalTime); +} + +//////////////////////////////////////////////////////////////////////////// + +Task::Ptr TaskExecutorInterface::async_first(TaskIn task, bool may_sync) { + return async(std::move(task), may_sync); +} + +void TaskExecutorInterface::sync(const TaskIn &task) { + semaphore sem; + auto ret = async([&]() { + onceToken token(nullptr, [&]() { + //通过RAII原理防止抛异常导致不执行这句代码 + sem.post(); + }); + task(); + }); + if (ret && *ret) { + sem.wait(); + } +} + +void TaskExecutorInterface::sync_first(const TaskIn &task) { + semaphore sem; + auto ret = async_first([&]() { + onceToken token(nullptr, [&]() { + //通过RAII原理防止抛异常导致不执行这句代码 + sem.post(); + }); + task(); + }); + if (ret && *ret) { + sem.wait(); + } +} + +////////////////////////////////////////////////////////////////// + +TaskExecutor::TaskExecutor(uint64_t max_size, uint64_t max_usec) : ThreadLoadCounter(max_size, max_usec) {} + +////////////////////////////////////////////////////////////////// + +TaskExecutor::Ptr TaskExecutorGetterImp::getExecutor() { + auto thread_pos = _thread_pos; + if (thread_pos >= _threads.size()) { + thread_pos = 0; + } + + TaskExecutor::Ptr executor_min_load = _threads[thread_pos]; + auto min_load = executor_min_load->load(); + + for (size_t i = 0; i < _threads.size(); ++i) { + ++thread_pos; + if (thread_pos >= _threads.size()) { + thread_pos = 0; + } + + auto th = _threads[thread_pos]; + auto load = th->load(); + + if (load < min_load) { + min_load = load; + executor_min_load = th; + } + if (min_load == 0) { + break; + } + } + _thread_pos = thread_pos; + return executor_min_load; +} + +vector TaskExecutorGetterImp::getExecutorLoad() { + vector vec(_threads.size()); + int i = 0; + for (auto &executor : _threads) { + vec[i++] = executor->load(); + } + return vec; +} + +void TaskExecutorGetterImp::getExecutorDelay(const function &)> &callback) { + std::shared_ptr > delay_vec = std::make_shared>(_threads.size()); + shared_ptr finished(nullptr, [callback, delay_vec](void *) { + //此析构回调触发时,说明已执行完毕所有async任务 + callback((*delay_vec)); + }); + int index = 0; + for (auto &th : _threads) { + std::shared_ptr delay_ticker = std::make_shared(); + th->async([finished, delay_vec, index, delay_ticker]() { + (*delay_vec)[index] = (int) delay_ticker->elapsedTime(); + }, false); + ++index; + } +} + +void TaskExecutorGetterImp::for_each(const function &cb) { + for (auto &th : _threads) { + cb(th); + } +} + +size_t TaskExecutorGetterImp::getExecutorSize() const { + return _threads.size(); +} + +size_t TaskExecutorGetterImp::addPoller(const string &name, size_t size, int priority, bool register_thread, bool enable_cpu_affinity) { + auto cpus = thread::hardware_concurrency(); + size = size > 0 ? size : cpus; + for (size_t i = 0; i < size; ++i) { + auto full_name = name + " " + to_string(i); + auto cpu_index = i % cpus; + EventPoller::Ptr poller(new EventPoller(full_name)); + poller->runLoop(false, register_thread); + poller->async([cpu_index, full_name, priority, enable_cpu_affinity]() { + // 设置线程优先级 + ThreadPool::setPriority((ThreadPool::Priority)priority); + // 设置线程名 + setThreadName(full_name.data()); + // 设置cpu亲和性 + if (enable_cpu_affinity) { + setThreadAffinity(cpu_index); + } + }); + _threads.emplace_back(std::move(poller)); + } + return size; +} + +}//toolkit diff --git a/ToolKit/Thread/TaskExecutor.h b/ToolKit/Thread/TaskExecutor.h new file mode 100644 index 0000000..efbbfc8 --- /dev/null +++ b/ToolKit/Thread/TaskExecutor.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_TASKEXECUTOR_H +#define ZLTOOLKIT_TASKEXECUTOR_H + +#include +#include +#include +#include "Util/List.h" +#include "Util/util.h" + +namespace toolkit { + +/** +* cpu负载计算器 +*/ +class ThreadLoadCounter { +public: + /** + * 构造函数 + * @param max_size 统计样本数量 + * @param max_usec 统计时间窗口,亦即最近{max_usec}的cpu负载率 + */ + ThreadLoadCounter(uint64_t max_size, uint64_t max_usec); + ~ThreadLoadCounter() = default; + + /** + * 线程进入休眠 + */ + void startSleep(); + + /** + * 休眠唤醒,结束休眠 + */ + void sleepWakeUp(); + + /** + * 返回当前线程cpu使用率,范围为 0 ~ 100 + * @return 当前线程cpu使用率 + */ + int load(); + +private: + struct TimeRecord { + TimeRecord(uint64_t tm, bool slp) { + _time = tm; + _sleep = slp; + } + + bool _sleep; + uint64_t _time; + }; + +private: + bool _sleeping = true; + uint64_t _last_sleep_time; + uint64_t _last_wake_time; + uint64_t _max_size; + uint64_t _max_usec; + std::mutex _mtx; + List _time_list; +}; + +class TaskCancelable : public noncopyable { +public: + TaskCancelable() = default; + virtual ~TaskCancelable() = default; + virtual void cancel() = 0; +}; + +template +class TaskCancelableImp; + +template +class TaskCancelableImp : public TaskCancelable { +public: + using Ptr = std::shared_ptr; + using func_type = std::function; + + ~TaskCancelableImp() = default; + + template + TaskCancelableImp(FUNC &&task) { + _strongTask = std::make_shared(std::forward(task)); + _weakTask = _strongTask; + } + + void cancel() override { + _strongTask = nullptr; + } + + operator bool() { + return _strongTask && *_strongTask; + } + + void operator=(std::nullptr_t) { + _strongTask = nullptr; + } + + R operator()(ArgTypes ...args) const { + auto strongTask = _weakTask.lock(); + if (strongTask && *strongTask) { + return (*strongTask)(std::forward(args)...); + } + return defaultValue(); + } + + template + static typename std::enable_if::value, void>::type + defaultValue() {} + + template + static typename std::enable_if::value, T>::type + defaultValue() { + return nullptr; + } + + template + static typename std::enable_if::value, T>::type + defaultValue() { + return 0; + } + +protected: + std::weak_ptr _weakTask; + std::shared_ptr _strongTask; +}; + +using TaskIn = std::function; +using Task = TaskCancelableImp; + +class TaskExecutorInterface { +public: + TaskExecutorInterface() = default; + virtual ~TaskExecutorInterface() = default; + + /** + * 异步执行任务 + * @param task 任务 + * @param may_sync 是否允许同步执行该任务 + * @return 任务是否添加成功 + */ + virtual Task::Ptr async(TaskIn task, bool may_sync = true) = 0; + + /** + * 最高优先级方式异步执行任务 + * @param task 任务 + * @param may_sync 是否允许同步执行该任务 + * @return 任务是否添加成功 + */ + virtual Task::Ptr async_first(TaskIn task, bool may_sync = true); + + /** + * 同步执行任务 + * @param task + * @return + */ + void sync(const TaskIn &task); + + /** + * 最高优先级方式同步执行任务 + * @param task + * @return + */ + void sync_first(const TaskIn &task); +}; + +/** +* 任务执行器 +*/ +class TaskExecutor : public ThreadLoadCounter, public TaskExecutorInterface { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * @param max_size cpu负载统计样本数 + * @param max_usec cpu负载统计时间窗口大小 + */ + TaskExecutor(uint64_t max_size = 32, uint64_t max_usec = 2 * 1000 * 1000); + ~TaskExecutor() = default; +}; + +class TaskExecutorGetter { +public: + using Ptr = std::shared_ptr; + + virtual ~TaskExecutorGetter() = default; + + /** + * 获取任务执行器 + * @return 任务执行器 + */ + virtual TaskExecutor::Ptr getExecutor() = 0; + + /** + * 获取执行器个数 + */ + virtual size_t getExecutorSize() const = 0; +}; + +class TaskExecutorGetterImp : public TaskExecutorGetter { +public: + TaskExecutorGetterImp() = default; + ~TaskExecutorGetterImp() = default; + + /** + * 根据线程负载情况,获取最空闲的任务执行器 + * @return 任务执行器 + */ + TaskExecutor::Ptr getExecutor() override; + + /** + * 获取所有线程的负载率 + * @return 所有线程的负载率 + */ + std::vector getExecutorLoad(); + + /** + * 获取所有线程任务执行延时,单位毫秒 + * 通过此函数也可以大概知道线程负载情况 + * @return + */ + void getExecutorDelay(const std::function &)> &callback); + + /** + * 遍历所有线程 + */ + void for_each(const std::function &cb); + + /** + * 获取线程数 + */ + size_t getExecutorSize() const override; + +protected: + size_t addPoller(const std::string &name, size_t size, int priority, bool register_thread, bool enable_cpu_affinity = true); + +protected: + size_t _thread_pos = 0; + std::vector _threads; +}; + +}//toolkit +#endif //ZLTOOLKIT_TASKEXECUTOR_H diff --git a/ToolKit/Thread/TaskQueue.h b/ToolKit/Thread/TaskQueue.h new file mode 100644 index 0000000..584e05a --- /dev/null +++ b/ToolKit/Thread/TaskQueue.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TASKQUEUE_H_ +#define TASKQUEUE_H_ + +#include +#include "Util/List.h" +#include "semaphore.h" + +namespace toolkit { + +//实现了一个基于函数对象的任务列队,该列队是线程安全的,任务列队任务数由信号量控制 +template +class TaskQueue { +public: + //打入任务至列队 + template + void push_task(C &&task_func) { + { + std::lock_guard lock(_mutex); + _queue.emplace_back(std::forward(task_func)); + } + _sem.post(); + } + + template + void push_task_first(C &&task_func) { + { + std::lock_guard lock(_mutex); + _queue.emplace_front(std::forward(task_func)); + } + _sem.post(); + } + + //清空任务列队 + void push_exit(size_t n) { + _sem.post(n); + } + + //从列队获取一个任务,由执行线程执行 + bool get_task(T &tsk) { + _sem.wait(); + std::lock_guard lock(_mutex); + if (_queue.empty()) { + return false; + } + tsk = std::move(_queue.front()); + _queue.pop_front(); + return true; + } + + size_t size() const { + std::lock_guard lock(_mutex); + return _queue.size(); + } + +private: + List _queue; + mutable std::mutex _mutex; + semaphore _sem; +}; + +} /* namespace toolkit */ +#endif /* TASKQUEUE_H_ */ diff --git a/ToolKit/Thread/ThreadPool.h b/ToolKit/Thread/ThreadPool.h new file mode 100644 index 0000000..cab7996 --- /dev/null +++ b/ToolKit/Thread/ThreadPool.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THREADPOOL_H_ +#define THREADPOOL_H_ + +#include "threadgroup.h" +#include "TaskQueue.h" +#include "TaskExecutor.h" +#include "Util/util.h" +#include "Util/logger.h" + +namespace toolkit { + +class ThreadPool : public TaskExecutor { +public: + enum Priority { + PRIORITY_LOWEST = 0, + PRIORITY_LOW, + PRIORITY_NORMAL, + PRIORITY_HIGH, + PRIORITY_HIGHEST + }; + + ThreadPool(int num = 1, Priority priority = PRIORITY_HIGHEST, bool auto_run = true, bool set_affinity = true, + const std::string &pool_name = "thread pool") { + _thread_num = num; + _on_setup = [pool_name, priority, set_affinity](int index) { + std::string name = pool_name + ' ' + std::to_string(index); + setPriority(priority); + setThreadName(name.data()); + if (set_affinity) { + setThreadAffinity(index % std::thread::hardware_concurrency()); + } + }; + _logger = Logger::Instance().shared_from_this(); + if (auto_run) { + start(); + } + } + + ~ThreadPool() { + shutdown(); + wait(); + } + + //把任务打入线程池并异步执行 + Task::Ptr async(TaskIn task, bool may_sync = true) override { + if (may_sync && _thread_group.is_this_thread_in()) { + task(); + return nullptr; + } + auto ret = std::make_shared(std::move(task)); + _queue.push_task(ret); + return ret; + } + + Task::Ptr async_first(TaskIn task, bool may_sync = true) override { + if (may_sync && _thread_group.is_this_thread_in()) { + task(); + return nullptr; + } + + auto ret = std::make_shared(std::move(task)); + _queue.push_task_first(ret); + return ret; + } + + size_t size() { + return _queue.size(); + } + + static bool setPriority(Priority priority = PRIORITY_NORMAL, std::thread::native_handle_type threadId = 0) { + // set priority +#if defined(_WIN32) + static int Priorities[] = { THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST }; + if (priority != PRIORITY_NORMAL && SetThreadPriority(GetCurrentThread(), Priorities[priority]) == 0) { + return false; + } + return true; +#else + static int Min = sched_get_priority_min(SCHED_FIFO); + if (Min == -1) { + return false; + } + static int Max = sched_get_priority_max(SCHED_FIFO); + if (Max == -1) { + return false; + } + static int Priorities[] = {Min, Min + (Max - Min) / 4, Min + (Max - Min) / 2, Min + (Max - Min) * 3 / 4, Max}; + + if (threadId == 0) { + threadId = pthread_self(); + } + struct sched_param params; + params.sched_priority = Priorities[priority]; + return pthread_setschedparam(threadId, SCHED_FIFO, ¶ms) == 0; +#endif + } + + void start() { + if (_thread_num <= 0) { + return; + } + size_t total = _thread_num - _thread_group.size(); + for (size_t i = 0; i < total; ++i) { + _thread_group.create_thread([this, i]() {run(i);}); + } + } + +private: + void run(size_t index) { + _on_setup(index); + Task::Ptr task; + while (true) { + startSleep(); + if (!_queue.get_task(task)) { + //空任务,退出线程 + break; + } + sleepWakeUp(); + try { + (*task)(); + task = nullptr; + } catch (std::exception &ex) { + ErrorL << "ThreadPool catch a exception: " << ex.what(); + } + } + } + + void wait() { + _thread_group.join_all(); + } + + void shutdown() { + _queue.push_exit(_thread_num); + } + +private: + size_t _thread_num; + Logger::Ptr _logger; + thread_group _thread_group; + TaskQueue _queue; + std::function _on_setup; +}; + +} /* namespace toolkit */ +#endif /* THREADPOOL_H_ */ diff --git a/ToolKit/Thread/WorkThreadPool.cpp b/ToolKit/Thread/WorkThreadPool.cpp new file mode 100644 index 0000000..ec50edb --- /dev/null +++ b/ToolKit/Thread/WorkThreadPool.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WorkThreadPool.h" + +namespace toolkit { + +static size_t s_pool_size = 0; +static bool s_enable_cpu_affinity = true; + +INSTANCE_IMP(WorkThreadPool) + +EventPoller::Ptr WorkThreadPool::getFirstPoller() { + return std::static_pointer_cast(_threads.front()); +} + +EventPoller::Ptr WorkThreadPool::getPoller() { + return std::static_pointer_cast(getExecutor()); +} + +WorkThreadPool::WorkThreadPool() { + //最低优先级 + addPoller("work poller", s_pool_size, ThreadPool::PRIORITY_LOWEST, false, s_enable_cpu_affinity); +} + +void WorkThreadPool::setPoolSize(size_t size) { + s_pool_size = size; +} + +void WorkThreadPool::enableCpuAffinity(bool enable) { + s_enable_cpu_affinity = enable; +} + +} /* namespace toolkit */ + diff --git a/ToolKit/Thread/WorkThreadPool.h b/ToolKit/Thread/WorkThreadPool.h new file mode 100644 index 0000000..ae7f0a6 --- /dev/null +++ b/ToolKit/Thread/WorkThreadPool.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_WORKTHREADPOOL_H_ +#define UTIL_WORKTHREADPOOL_H_ + +#include +#include "Poller/EventPoller.h" + +namespace toolkit { + +class WorkThreadPool : public std::enable_shared_from_this, public TaskExecutorGetterImp { +public: + using Ptr = std::shared_ptr; + + ~WorkThreadPool() override = default; + + /** + * 获取单例 + */ + static WorkThreadPool &Instance(); + + /** + * 设置EventPoller个数,在WorkThreadPool单例创建前有效 + * 在不调用此方法的情况下,默认创建thread::hardware_concurrency()个EventPoller实例 + * @param size EventPoller个数,如果为0则为thread::hardware_concurrency() + */ + static void setPoolSize(size_t size = 0); + + /** + * 内部创建线程是否设置cpu亲和性,默认设置cpu亲和性 + */ + static void enableCpuAffinity(bool enable); + + /** + * 获取第一个实例 + * @return + */ + EventPoller::Ptr getFirstPoller(); + + /** + * 根据负载情况获取轻负载的实例 + * 如果优先返回当前线程,那么会返回当前线程 + * 返回当前线程的目的是为了提高线程安全性 + * @return + */ + EventPoller::Ptr getPoller(); + +protected: + WorkThreadPool(); +}; + +} /* namespace toolkit */ +#endif /* UTIL_WORKTHREADPOOL_H_ */ diff --git a/ToolKit/Thread/semaphore.h b/ToolKit/Thread/semaphore.h new file mode 100644 index 0000000..46f3155 --- /dev/null +++ b/ToolKit/Thread/semaphore.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SEMAPHORE_H_ +#define SEMAPHORE_H_ + +/* + * 目前发现信号量在32位的系统上有问题, + * 休眠的线程无法被正常唤醒,先禁用之 +#if defined(__linux__) +#include +#define HAVE_SEM +#endif //HAVE_SEM +*/ + +#include +#include + +namespace toolkit { + +class semaphore { +public: + explicit semaphore(size_t initial = 0) { +#if defined(HAVE_SEM) + sem_init(&_sem, 0, initial); +#else + _count = 0; +#endif + } + + ~semaphore() { +#if defined(HAVE_SEM) + sem_destroy(&_sem); +#endif + } + + void post(size_t n = 1) { +#if defined(HAVE_SEM) + while (n--) { + sem_post(&_sem); + } +#else + std::unique_lock lock(_mutex); + _count += n; + if (n == 1) { + _condition.notify_one(); + } else { + _condition.notify_all(); + } +#endif + } + + void wait() { +#if defined(HAVE_SEM) + sem_wait(&_sem); +#else + std::unique_lock lock(_mutex); + while (_count == 0) { + _condition.wait(lock); + } + --_count; +#endif + } + +private: +#if defined(HAVE_SEM) + sem_t _sem; +#else + size_t _count; + std::recursive_mutex _mutex; + std::condition_variable_any _condition; +#endif +}; + +} /* namespace toolkit */ +#endif /* SEMAPHORE_H_ */ diff --git a/ToolKit/Thread/threadgroup.h b/ToolKit/Thread/threadgroup.h new file mode 100644 index 0000000..21d689e --- /dev/null +++ b/ToolKit/Thread/threadgroup.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THREADGROUP_H_ +#define THREADGROUP_H_ + +#include +#include +#include + +namespace toolkit { + +class thread_group { +private: + thread_group(thread_group const &); + thread_group &operator=(thread_group const &); + +public: + thread_group() {} + + ~thread_group() { + _threads.clear(); + } + + bool is_this_thread_in() { + auto thread_id = std::this_thread::get_id(); + if (_thread_id == thread_id) { + return true; + } + return _threads.find(thread_id) != _threads.end(); + } + + bool is_thread_in(std::thread *thrd) { + if (!thrd) { + return false; + } + auto it = _threads.find(thrd->get_id()); + return it != _threads.end(); + } + + template + std::thread *create_thread(F &&threadfunc) { + auto thread_new = std::make_shared(std::forward(threadfunc)); + _thread_id = thread_new->get_id(); + _threads[_thread_id] = thread_new; + return thread_new.get(); + } + + void remove_thread(std::thread *thrd) { + auto it = _threads.find(thrd->get_id()); + if (it != _threads.end()) { + _threads.erase(it); + } + } + + void join_all() { + if (is_this_thread_in()) { + throw std::runtime_error("Trying joining itself in thread_group"); + } + for (auto &it : _threads) { + if (it.second->joinable()) { + it.second->join(); //等待线程主动退出 + } + } + _threads.clear(); + } + + size_t size() { + return _threads.size(); + } + +private: + std::thread::id _thread_id; + std::unordered_map> _threads; +}; + +} /* namespace toolkit */ +#endif /* THREADGROUP_H_ */ diff --git a/ToolKit/Util/File.cpp b/ToolKit/Util/File.cpp new file mode 100644 index 0000000..d1a291b --- /dev/null +++ b/ToolKit/Util/File.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(_WIN32) +#include +#include +#else +#include +#include +#endif // WIN32 + +#include +#include "File.h" +#include "util.h" +#include "logger.h" +#include "uv_errno.h" + +using namespace std; +using namespace toolkit; + +#if !defined(_WIN32) +#define _unlink unlink +#define _rmdir rmdir +#define _access access +#endif + +#if defined(_WIN32) + +int mkdir(const char *path, int mode) { + return _mkdir(path); +} + +DIR *opendir(const char *name) { + char namebuf[512]; + snprintf(namebuf, sizeof(namebuf), "%s\\*.*", name); + + WIN32_FIND_DATAA FindData; + auto hFind = FindFirstFileA(namebuf, &FindData); + if (hFind == INVALID_HANDLE_VALUE) { + return nullptr; + } + DIR *dir = (DIR *)malloc(sizeof(DIR)); + memset(dir, 0, sizeof(DIR)); + dir->dd_fd = 0; // simulate return + dir->handle = hFind; + return dir; +} + +struct dirent *readdir(DIR *d) { + HANDLE hFind = d->handle; + WIN32_FIND_DATAA FileData; + //fail or end + if (!FindNextFileA(hFind, &FileData)) { + return nullptr; + } + struct dirent *dir = (struct dirent *)malloc(sizeof(struct dirent) + sizeof(FileData.cFileName)); + strcpy(dir->d_name, (char *)FileData.cFileName); + dir->d_reclen = (uint16_t)strlen(dir->d_name); + //check there is file or directory + if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + dir->d_type = 2; + } + else { + dir->d_type = 1; + } + if (d->index) { + //覆盖前释放内存 + free(d->index); + d->index = nullptr; + } + d->index = dir; + return dir; +} + +int closedir(DIR *d) { + if (!d) { + return -1; + } + //关闭句柄 + if (d->handle != INVALID_HANDLE_VALUE) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + //释放内存 + if (d->index) { + free(d->index); + d->index = nullptr; + } + free(d); + return 0; +} +#endif // defined(_WIN32) + +namespace toolkit { + +FILE *File::create_file(const std::string &file, const std::string &mode) { + std::string path = file; + std::string dir; + size_t index = 1; + FILE *ret = nullptr; + while (true) { + index = path.find('/', index) + 1; + dir = path.substr(0, index); + if (dir.length() == 0) { + break; + } + if (_access(dir.data(), 0) == -1) { //access函数是查看是不是存在 + if (mkdir(dir.data(), 0777) == -1) { //如果不存在就用mkdir函数来创建 + WarnL << "mkdir " << dir << " failed: " << get_uv_errmsg(); + return nullptr; + } + } + } + if (path[path.size() - 1] != '/') { + ret = fopen(file.data(), mode.data()); + } + return ret; +} + +bool File::create_path(const std::string &file, unsigned int mod) { + std::string path = file; + std::string dir; + size_t index = 1; + while (true) { + index = path.find('/', index) + 1; + dir = path.substr(0, index); + if (dir.length() == 0) { + break; + } + if (_access(dir.data(), 0) == -1) { //access函数是查看是不是存在 + if (mkdir(dir.data(), mod) == -1) { //如果不存在就用mkdir函数来创建 + WarnL << "mkdir " << dir << " failed: " << get_uv_errmsg(); + return false; + } + } + } + return true; +} + +//判断是否为目录 +bool File::is_dir(const std::string &path) { + auto dir = opendir(path.data()); + if (!dir) { + return false; + } + closedir(dir); + return true; +} + +//判断是否为常规文件 +bool File::fileExist(const std::string &path) { + auto fp = fopen(path.data(), "rb"); + if (!fp) { + return false; + } + fclose(fp); + return true; +} + +//判断是否是特殊目录 +bool File::is_special_dir(const std::string &path) { + return path == "." || path == ".."; +} + +static int delete_file_l(const std::string &path_in) { + DIR *dir; + dirent *dir_info; + auto path = path_in; + if (path.back() == '/') { + path.pop_back(); + } + if (File::is_dir(path)) { + if ((dir = opendir(path.data())) == nullptr) { + return _rmdir(path.data()); + } + while ((dir_info = readdir(dir)) != nullptr) { + if (File::is_special_dir(dir_info->d_name)) { + continue; + } + File::delete_file(path + "/" + dir_info->d_name); + } + auto ret = _rmdir(path.data()); + closedir(dir); + return ret; + } + return remove(path.data()) ? _unlink(path.data()) : 0; +} + +int File::delete_file(const std::string &path, bool del_empty_dir, bool backtrace) { + auto ret = delete_file_l(path); + if (!ret && del_empty_dir) { + // delete success + File::deleteEmptyDir(parentDir(path), backtrace); + } + return ret; +} + +string File::loadFile(const std::string &path) { + FILE *fp = fopen(path.data(), "rb"); + if (!fp) { + return ""; + } + fseek(fp, 0, SEEK_END); + auto len = ftell(fp); + fseek(fp, 0, SEEK_SET); + string str(len, '\0'); + if (len != (decltype(len))fread((char *)str.data(), 1, str.size(), fp)) { + WarnL << "fread " << path << " failed: " << get_uv_errmsg(); + } + fclose(fp); + return str; +} + +bool File::saveFile(const string &data, const std::string &path) { + FILE *fp = fopen(path.data(), "wb"); + if (!fp) { + return false; + } + fwrite(data.data(), data.size(), 1, fp); + fclose(fp); + return true; +} + +string File::parentDir(const std::string &path) { + auto parent_dir = path; + if (parent_dir.back() == '/') { + parent_dir.pop_back(); + } + auto pos = parent_dir.rfind('/'); + if (pos != string::npos) { + parent_dir = parent_dir.substr(0, pos + 1); + } + return parent_dir; +} + +string File::absolutePath(const std::string &path, const std::string ¤t_path, bool can_access_parent) { + string currentPath = current_path; + if (!currentPath.empty()) { + //当前目录不为空 + if (currentPath.front() == '.') { + //如果当前目录是相对路径,那么先转换成绝对路径 + currentPath = absolutePath(current_path, exeDir(), true); + } + } else { + currentPath = exeDir(); + } + + if (path.empty()) { + //相对路径为空,那么返回当前目录 + return currentPath; + } + + if (currentPath.back() != '/') { + //确保当前目录最后字节为'/' + currentPath.push_back('/'); + } + auto rootPath = currentPath; + auto dir_vec = split(path, "/"); + for (auto &dir : dir_vec) { + if (dir.empty() || dir == ".") { + //忽略空或本文件夹 + continue; + } + if (dir == "..") { + //访问上级目录 + if (!can_access_parent && currentPath.size() <= rootPath.size()) { + //不能访问根目录之外的目录, 返回根目录 + return rootPath; + } + currentPath = parentDir(currentPath); + continue; + } + currentPath.append(dir); + currentPath.append("/"); + } + + if (path.back() != '/' && currentPath.back() == '/') { + //在路径是文件的情况下,防止转换成目录 + currentPath.pop_back(); + } + return currentPath; +} + +void File::scanDir(const std::string &path_in, const function &cb, + bool enter_subdirectory, bool show_hidden_file) { + string path = path_in; + if (path.back() == '/') { + path.pop_back(); + } + + DIR *pDir; + dirent *pDirent; + if ((pDir = opendir(path.data())) == nullptr) { + //文件夹无效 + return; + } + while ((pDirent = readdir(pDir)) != nullptr) { + if (is_special_dir(pDirent->d_name)) { + continue; + } + if (!show_hidden_file && pDirent->d_name[0] == '.') { + //隐藏的文件 + continue; + } + string strAbsolutePath = path + "/" + pDirent->d_name; + bool isDir = is_dir(strAbsolutePath); + if (!cb(strAbsolutePath, isDir)) { + //不再继续扫描 + break; + } + + if (isDir && enter_subdirectory) { + //如果是文件夹并且扫描子文件夹,那么递归扫描 + scanDir(strAbsolutePath, cb, enter_subdirectory); + } + } + closedir(pDir); +} + +uint64_t File::fileSize(FILE *fp, bool remain_size) { + if (!fp) { + return 0; + } + auto current = ftell64(fp); + fseek64(fp, 0L, SEEK_END); /* 定位到文件末尾 */ + auto end = ftell64(fp); /* 得到文件大小 */ + fseek64(fp, current, SEEK_SET); + return end - (remain_size ? current : 0); +} + +uint64_t File::fileSize(const std::string &path) { + if (path.empty()) { + return 0; + } + auto fp = std::unique_ptr(fopen(path.data(), "rb"), fclose); + return fileSize(fp.get()); +} + +static bool isEmptyDir(const std::string &path) { + bool empty = true; + File::scanDir(path,[&](const std::string &path, bool isDir) { + empty = false; + return false; + }, true, true); + return empty; +} + +void File::deleteEmptyDir(const std::string &dir, bool backtrace) { + if (!File::is_dir(dir) || !isEmptyDir(dir)) { + // 不是文件夹或者非空 + return; + } + File::delete_file(dir); + if (backtrace) { + deleteEmptyDir(File::parentDir(dir), true); + } +} + +} /* namespace toolkit */ diff --git a/ToolKit/Util/File.h b/ToolKit/Util/File.h new file mode 100644 index 0000000..4d78155 --- /dev/null +++ b/ToolKit/Util/File.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SRC_UTIL_FILE_H_ +#define SRC_UTIL_FILE_H_ + +#include +#include +#include +#include "util.h" +#include + +#if defined(__linux__) +#include +#endif + +#if defined(_WIN32) +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif // !PATH_MAX + +struct dirent{ + long d_ino; /* inode number*/ + off_t d_off; /* offset to this dirent*/ + unsigned short d_reclen; /* length of this d_name*/ + unsigned char d_type; /* the type of d_name*/ + char d_name[1]; /* file name (null-terminated)*/ +}; +typedef struct _dirdesc { + int dd_fd; /** file descriptor associated with directory */ + long dd_loc; /** offset in current buffer */ + long dd_size; /** amount of data returned by getdirentries */ + char *dd_buf; /** data buffer */ + int dd_len; /** size of data buffer */ + long dd_seek; /** magic cookie returned by getdirentries */ + HANDLE handle; + struct dirent *index; +} DIR; +# define __dirfd(dp) ((dp)->dd_fd) + +int mkdir(const char *path, int mode); +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); + +#endif // defined(_WIN32) + +#if defined(_WIN32) || defined(_WIN64) +#define fseek64 _fseeki64 +#define ftell64 _ftelli64 +#else +#define fseek64 fseek +#define ftell64 ftell +#endif + +namespace toolkit { + +class File { +public: + //创建路径 + static bool create_path(const std::string &file, unsigned int mod); + + //新建文件,目录文件夹自动生成 + static FILE *create_file(const std::string &file, const std::string &mode); + + //判断是否为目录 + static bool is_dir(const std::string &path); + + //判断是否是特殊目录(. or ..) + static bool is_special_dir(const std::string &path); + + //删除目录或文件 + static int delete_file(const std::string &path, bool del_empty_dir = false, bool backtrace = true); + + //判断文件是否存在 + static bool fileExist(const std::string &path); + + /** + * 加载文件内容至string + * @param path 加载的文件路径 + * @return 文件内容 + */ + static std::string loadFile(const std::string &path); + + /** + * 保存内容至文件 + * @param data 文件内容 + * @param path 保存的文件路径 + * @return 是否保存成功 + */ + static bool saveFile(const std::string &data, const std::string &path); + + /** + * 获取父文件夹 + * @param path 路径 + * @return 文件夹 + */ + static std::string parentDir(const std::string &path); + + /** + * 替换"../",获取绝对路径 + * @param path 相对路径,里面可能包含 "../" + * @param current_path 当前目录 + * @param can_access_parent 能否访问父目录之外的目录 + * @return 替换"../"之后的路径 + */ + static std::string absolutePath(const std::string &path, const std::string ¤t_path, bool can_access_parent = false); + + /** + * 遍历文件夹下的所有文件 + * @param path 文件夹路径 + * @param cb 回调对象 ,path为绝对路径,isDir为该路径是否为文件夹,返回true代表继续扫描,否则中断 + * @param enter_subdirectory 是否进入子目录扫描 + * @param show_hidden_file 是否显示隐藏的文件 + */ + static void scanDir(const std::string &path, const std::function &cb, + bool enter_subdirectory = false, bool show_hidden_file = false); + + /** + * 获取文件大小 + * @param fp 文件句柄 + * @param remain_size true:获取文件剩余未读数据大小,false:获取文件总大小 + */ + static uint64_t fileSize(FILE *fp, bool remain_size = false); + + /** + * 获取文件大小 + * @param path 文件路径 + * @return 文件大小 + * @warning 调用者应确保文件存在 + */ + static uint64_t fileSize(const std::string &path); + + /** + * 尝试删除空文件夹 + * @param dir 文件夹路径 + * @param backtrace 是否回溯上层文件夹,上层文件夹为空也一并删除,以此类推 + */ + static void deleteEmptyDir(const std::string &dir, bool backtrace = true); + +private: + File(); + ~File(); +}; + +} /* namespace toolkit */ +#endif /* SRC_UTIL_FILE_H_ */ diff --git a/ToolKit/Util/List.h b/ToolKit/Util/List.h new file mode 100644 index 0000000..e85c4fa --- /dev/null +++ b/ToolKit/Util/List.h @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_LIST_H +#define ZLTOOLKIT_LIST_H + +#include +#include + +namespace toolkit { + +#if 0 +template +class List; + +template +class ListNode +{ +public: + friend class List; + ~ListNode(){} + + template + ListNode(Args&&... args):_data(std::forward(args)...){} + +private: + T _data; + ListNode *next = nullptr; +}; + + +template +class List { +public: + using NodeType = ListNode; + List(){} + List(List &&that){ + swap(that); + } + ~List(){ + clear(); + } + void clear(){ + auto ptr = _front; + auto last = ptr; + while(ptr){ + last = ptr; + ptr = ptr->next; + delete last; + } + _size = 0; + _front = nullptr; + _back = nullptr; + } + + template + void for_each(FUN &&fun) const { + auto ptr = _front; + while (ptr) { + fun(ptr->_data); + ptr = ptr->next; + } + } + + size_t size() const{ + return _size; + } + + bool empty() const{ + return _size == 0; + } + template + void emplace_front(Args&&... args){ + NodeType *node = new NodeType(std::forward(args)...); + if(!_front){ + _front = node; + _back = node; + _size = 1; + }else{ + node->next = _front; + _front = node; + ++_size; + } + } + + template + void emplace_back(Args&&... args){ + NodeType *node = new NodeType(std::forward(args)...); + if(!_back){ + _back = node; + _front = node; + _size = 1; + }else{ + _back->next = node; + _back = node; + ++_size; + } + } + + T &front() const{ + return _front->_data; + } + + T &back() const{ + return _back->_data; + } + + T &operator[](size_t pos){ + NodeType *front = _front ; + while(pos--){ + front = front->next; + } + return front->_data; + } + + void pop_front(){ + if(!_front){ + return; + } + auto ptr = _front; + _front = _front->next; + delete ptr; + if(!_front){ + _back = nullptr; + } + --_size; + } + + void swap(List &other){ + NodeType *tmp_node; + + tmp_node = _front; + _front = other._front; + other._front = tmp_node; + + tmp_node = _back; + _back = other._back; + other._back = tmp_node; + + size_t tmp_size = _size; + _size = other._size; + other._size = tmp_size; + } + + void append(List &other){ + if(other.empty()){ + return; + } + if(_back){ + _back->next = other._front; + _back = other._back; + }else{ + _front = other._front; + _back = other._back; + } + _size += other._size; + + other._front = other._back = nullptr; + other._size = 0; + } + + List &operator=(const List &that) { + that.for_each([&](const T &t) { + emplace_back(t); + }); + return *this; + } + +private: + NodeType *_front = nullptr; + NodeType *_back = nullptr; + size_t _size = 0; +}; + +#else + +template +class List : public std::list { +public: + template + List(ARGS &&...args) : std::list(std::forward(args)...) {}; + + ~List() = default; + + void append(List &other) { + if (other.empty()) { + return; + } + this->insert(this->end(), other.begin(), other.end()); + other.clear(); + } + + template + void for_each(FUNC &&func) { + for (auto &t : *this) { + func(t); + } + } + + template + void for_each(FUNC &&func) const { + for (auto &t : *this) { + func(t); + } + } +}; + +#endif + +} /* namespace toolkit */ +#endif //ZLTOOLKIT_LIST_H diff --git a/ToolKit/Util/MD5.cpp b/ToolKit/Util/MD5.cpp new file mode 100644 index 0000000..bd5803e --- /dev/null +++ b/ToolKit/Util/MD5.cpp @@ -0,0 +1,372 @@ +//MD5.cpp +/* MD5 +converted to C++ class by Frank Thilo (thilo@unix-ag.org) +for bzflag (http://www.bzflag.org) + +based on: + +md5.h and md5.c +reference implemantion of RFC 1321 + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +/* interface header */ +#include "MD5.h" +/* system implementation headers */ +#include +#include + +namespace toolkit { + +// Constants for MD5Transform routine. +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +/////////////////////////////////////////////// + +// F, G, H and I are basic MD5 functions. +inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) { + return (x&y) | (~x&z); +} + +inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) { + return (x&z) | (y&~z); +} + +inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) { + return x^y^z; +} + +inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) { + return y ^ (x | ~z); +} + +// rotate_left rotates x left n bits. +inline MD5::uint4 MD5::rotate_left(uint4 x, int n) { + return (x << n) | (x >> (32-n)); +} + +// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +// Rotation is separate from addition to prevent recomputation. +inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a+ F(b,c,d) + x + ac, s) + b; +} + +inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + G(b,c,d) + x + ac, s) + b; +} + +inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + H(b,c,d) + x + ac, s) + b; +} + +inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + I(b,c,d) + x + ac, s) + b; +} + +////////////////////////////////////////////// + +// default ctor, just initailize +MD5::MD5() +{ + init(); +} + +////////////////////////////////////////////// + +// nifty shortcut ctor, compute MD5 for string and finalize it right away +MD5::MD5(const std::string &text) +{ + init(); + update(text.c_str(), text.length()); + finalize(); +} + +////////////////////////////// + +void MD5::init() +{ + finalized=false; + + count[0] = 0; + count[1] = 0; + + // load magic initialization constants. + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; +} + +////////////////////////////// + +// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4. +void MD5::decode(uint4 output[], const uint1 input[], size_type len) +{ + for (unsigned int i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) | + (((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24); +} + +////////////////////////////// + +// encodes input (uint4) into output (unsigned char). Assumes len is +// a multiple of 4. +void MD5::encode(uint1 output[], const uint4 input[], size_type len) +{ + for (size_type i = 0, j = 0; j < len; i++, j += 4) { + output[j] = input[i] & 0xff; + output[j+1] = (input[i] >> 8) & 0xff; + output[j+2] = (input[i] >> 16) & 0xff; + output[j+3] = (input[i] >> 24) & 0xff; + } +} + +////////////////////////////// + +// apply MD5 algo on a block +void MD5::transform(const uint1 block[blocksize]) +{ + uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + decode (x, block, blocksize); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + // Zeroize sensitive information. + memset(x, 0, sizeof x); +} + +////////////////////////////// + +// MD5 block update operation. Continues an MD5 message-digest +// operation, processing another message block +void MD5::update(const unsigned char input[], size_type length) +{ + // compute number of bytes mod 64 + size_type index = count[0] / 8 % blocksize; + + // Update number of bits + if ((count[0] += (length << 3)) < (length << 3)) + count[1]++; + count[1] += (length >> 29); + + // number of bytes we need to fill in buffer + size_type firstpart = 64 - index; + + size_type i; + + // transform as many times as possible. + if (length >= firstpart) + { + // fill buffer first, transform + memcpy(&buffer[index], input, firstpart); + transform(buffer); + + // transform chunks of blocksize (64 bytes) + for (i = firstpart; i + blocksize <= length; i += blocksize) + transform(&input[i]); + + index = 0; + } + else + i = 0; + + // buffer remaining input + memcpy(&buffer[index], &input[i], length-i); +} + +////////////////////////////// + +// for convenience provide a verson with signed char +void MD5::update(const char input[], size_type length) +{ + update((const unsigned char*)input, length); +} + +////////////////////////////// + +// MD5 finalization. Ends an MD5 message-digest operation, writing the +// the message digest and zeroizing the context. +MD5& MD5::finalize() +{ + static unsigned char padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (!finalized) { + // Save number of bits + unsigned char bits[8]; + encode(bits, count, 8); + + // pad out to 56 mod 64. + size_type index = count[0] / 8 % 64; + size_type padLen = (index < 56) ? (56 - index) : (120 - index); + update(padding, padLen); + + // Append length (before padding) + update(bits, 8); + + // Store state in digest + encode(digest, state, 16); + + // Zeroize sensitive information. + memset(buffer, 0, sizeof buffer); + memset(count, 0, sizeof count); + + finalized=true; + } + + return *this; +} + +////////////////////////////// + +// return hex representation of digest as string +std::string MD5::hexdigest() const +{ + if (!finalized) + return ""; + + char buf[33]; + for (int i=0; i<16; i++) + sprintf(buf+i*2, "%02x", digest[i]); + buf[32]=0; + + return std::string(buf); +} + +std::string MD5::rawdigest() const{ + return std::string((char *)digest, sizeof(digest)); +} + + +////////////////////////////// + +std::ostream& operator<<(std::ostream& out, MD5 md5) +{ + return out << md5.hexdigest(); +} + +////////////////////////////// + +std::string md5(const std::string str) +{ + MD5 md5 = MD5(str); + + return md5.hexdigest(); +} +} /* namespace toolkit */ diff --git a/ToolKit/Util/MD5.h b/ToolKit/Util/MD5.h new file mode 100644 index 0000000..7f02ec4 --- /dev/null +++ b/ToolKit/Util/MD5.h @@ -0,0 +1,97 @@ +//MD5.cpp +/* MD5 +converted to C++ class by Frank Thilo (thilo@unix-ag.org) +for bzflag (http://www.bzflag.org) + +based on: + +md5.h and md5.c +reference implemantion of RFC 1321 + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +#ifndef SRC_UTIL_MD5_H_ +#define SRC_UTIL_MD5_H_ + +#include +#include +#include + +namespace toolkit { + +// a small class for calculating MD5 hashes of strings or byte arrays +// it is not meant to be fast or secure +// +// usage: 1) feed it blocks of uchars with update() +// 2) finalize() +// 3) get hexdigest() string +// or +// MD5(std::string).hexdigest() +// +// assumes that char is 8 bit and int is 32 bit +class MD5 +{ +public: + typedef unsigned int size_type; // must be 32bit + + MD5(); + MD5(const std::string& text); + void update(const unsigned char *buf, size_type length); + void update(const char *buf, size_type length); + MD5& finalize(); + std::string hexdigest() const; + std::string rawdigest() const; + friend std::ostream& operator<<(std::ostream&, MD5 md5); +private: + void init(); + typedef uint8_t uint1; // 8bit + typedef uint32_t uint4; // 32bit + enum {blocksize = 64}; // VC6 won't eat a const static int here + + void transform(const uint1 block[blocksize]); + static void decode(uint4 output[], const uint1 input[], size_type len); + static void encode(uint1 output[], const uint4 input[], size_type len); + + bool finalized; + uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk + uint4 count[2]; // 64bit counter for number of bits (lo, hi) + uint4 state[4]; // digest so far + uint1 digest[16]; // the result + + // low level logic operations + static inline uint4 F(uint4 x, uint4 y, uint4 z); + static inline uint4 G(uint4 x, uint4 y, uint4 z); + static inline uint4 H(uint4 x, uint4 y, uint4 z); + static inline uint4 I(uint4 x, uint4 y, uint4 z); + static inline uint4 rotate_left(uint4 x, int n); + static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); +}; + + +} /* namespace toolkit */ + +#endif /* SRC_UTIL_MD5_H_ */ diff --git a/ToolKit/Util/NoticeCenter.cpp b/ToolKit/Util/NoticeCenter.cpp new file mode 100644 index 0000000..ffabf80 --- /dev/null +++ b/ToolKit/Util/NoticeCenter.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "util.h" +#include "NoticeCenter.h" + +namespace toolkit { + +INSTANCE_IMP(NoticeCenter) + +} /* namespace toolkit */ + diff --git a/ToolKit/Util/NoticeCenter.h b/ToolKit/Util/NoticeCenter.h new file mode 100644 index 0000000..c14a3dd --- /dev/null +++ b/ToolKit/Util/NoticeCenter.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SRC_UTIL_NOTICECENTER_H_ +#define SRC_UTIL_NOTICECENTER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "function_traits.h" + +namespace toolkit { + +class EventDispatcher { +public: + friend class NoticeCenter; + using Ptr = std::shared_ptr; + + ~EventDispatcher() = default; + +private: + using MapType = std::unordered_multimap; + + EventDispatcher() = default; + + class InterruptException : public std::runtime_error { + public: + InterruptException() : std::runtime_error("InterruptException") {} + ~InterruptException() {} + }; + + template + int emitEvent(bool safe, ArgsType &&...args) { + using stl_func = std::function(args))...)>; + decltype(_mapListener) copy; + { + // 先拷贝(开销比较小),目的是防止在触发回调时还是上锁状态从而导致交叉互锁 + std::lock_guard lck(_mtxListener); + copy = _mapListener; + } + + int ret = 0; + for (auto &pr : copy) { + try { + pr.second.get(safe)(std::forward(args)...); + ++ret; + } catch (InterruptException &) { + ++ret; + break; + } + } + return ret; + } + + template + void addListener(void *tag, FUNC &&func) { + using stl_func = typename function_traits::type>::stl_function_type; + Any listener; + listener.set(std::forward(func)); + std::lock_guard lck(_mtxListener); + _mapListener.emplace(tag, listener); + } + + void delListener(void *tag, bool &empty) { + std::lock_guard lck(_mtxListener); + _mapListener.erase(tag); + empty = _mapListener.empty(); + } + +private: + std::recursive_mutex _mtxListener; + MapType _mapListener; +}; + +class NoticeCenter : public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + static NoticeCenter &Instance(); + + template + int emitEvent(const std::string &event, ArgsType &&...args) { + return emitEvent_l(false, event, std::forward(args)...); + } + + template + int emitEventSafe(const std::string &event, ArgsType &&...args) { + return emitEvent_l(true, event, std::forward(args)...); + } + + template + void addListener(void *tag, const std::string &event, FUNC &&func) { + getDispatcher(event, true)->addListener(tag, std::forward(func)); + } + + void delListener(void *tag, const std::string &event) { + auto dispatcher = getDispatcher(event); + if (!dispatcher) { + // 不存在该事件 + return; + } + bool empty; + dispatcher->delListener(tag, empty); + if (empty) { + delDispatcher(event, dispatcher); + } + } + + // 这个方法性能比较差 + void delListener(void *tag) { + std::lock_guard lck(_mtxListener); + bool empty; + for (auto it = _mapListener.begin(); it != _mapListener.end();) { + it->second->delListener(tag, empty); + if (empty) { + it = _mapListener.erase(it); + continue; + } + ++it; + } + } + + void clearAll() { + std::lock_guard lck(_mtxListener); + _mapListener.clear(); + } + +private: + template + int emitEvent_l(bool safe, const std::string &event, ArgsType &&...args) { + auto dispatcher = getDispatcher(event); + if (!dispatcher) { + // 该事件无人监听 + return 0; + } + return dispatcher->emitEvent(safe, std::forward(args)...); + } + + EventDispatcher::Ptr getDispatcher(const std::string &event, bool create = false) { + std::lock_guard lck(_mtxListener); + auto it = _mapListener.find(event); + if (it != _mapListener.end()) { + return it->second; + } + if (create) { + // 如果为空则创建一个 + EventDispatcher::Ptr dispatcher(new EventDispatcher()); + _mapListener.emplace(event, dispatcher); + return dispatcher; + } + return nullptr; + } + + void delDispatcher(const std::string &event, const EventDispatcher::Ptr &dispatcher) { + std::lock_guard lck(_mtxListener); + auto it = _mapListener.find(event); + if (it != _mapListener.end() && dispatcher == it->second) { + // 两者相同则删除 + _mapListener.erase(it); + } + } + +private: + std::recursive_mutex _mtxListener; + std::unordered_map _mapListener; +}; + +template +struct NoticeHelper; + +template +struct NoticeHelper { +public: + template + static int emit(const std::string &event, ArgsType &&...args) { + return emit(NoticeCenter::Instance(), event, std::forward(args)...); + } + + template + static int emit(NoticeCenter ¢er, const std::string &event, ArgsType &&...args) { + return center.emitEventSafe(event, std::forward(args)...); + } +}; + +#define NOTICE_EMIT(types, ...) NoticeHelper::emit(__VA_ARGS__) + +} /* namespace toolkit */ +#endif /* SRC_UTIL_NOTICECENTER_H_ */ diff --git a/ToolKit/Util/ResourcePool.h b/ToolKit/Util/ResourcePool.h new file mode 100644 index 0000000..8731c03 --- /dev/null +++ b/ToolKit/Util/ResourcePool.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_RECYCLEPOOL_H_ +#define UTIL_RECYCLEPOOL_H_ + +#include "List.h" +#include +#include +#include +#include +#include +#include + +namespace toolkit { + +#if (defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 9))) || defined(__clang__) \ + || !defined(__GNUC__) +#define SUPPORT_DYNAMIC_TEMPLATE +#endif + +template +class ResourcePool_l; +template +class ResourcePool; + +template +class shared_ptr_imp : public std::shared_ptr { +public: + shared_ptr_imp() {} + + /** + * 构造智能指针 + * @param ptr 裸指针 + * @param weakPool 管理本指针的循环池 + * @param quit 对接是否放弃循环使用 + */ + shared_ptr_imp( + C *ptr, const std::weak_ptr> &weakPool, std::shared_ptr quit, + const std::function &on_recycle); + + /** + * 放弃或恢复回到循环池继续使用 + * @param flag + */ + void quit(bool flag = true) { + if (_quit) { + *_quit = flag; + } + } + +private: + std::shared_ptr _quit; +}; + +template +class ResourcePool_l : public std::enable_shared_from_this> { +public: + using ValuePtr = shared_ptr_imp; + friend class shared_ptr_imp; + friend class ResourcePool; + + ResourcePool_l() { + _alloc = []() -> C * { return new C(); }; + } + +#if defined(SUPPORT_DYNAMIC_TEMPLATE) + template + ResourcePool_l(ArgTypes &&...args) { + _alloc = [args...]() -> C * { return new C(args...); }; + } +#endif // defined(SUPPORT_DYNAMIC_TEMPLATE) + + ~ResourcePool_l() { + for (auto ptr : _objs) { + delete ptr; + } + } + + void setSize(size_t size) { + _pool_size = size; + _objs.reserve(size); + } + + ValuePtr obtain(const std::function &on_recycle = nullptr) { + return ValuePtr(getPtr(), _weak_self, std::make_shared(false), on_recycle); + } + + std::shared_ptr obtain2() { + auto weak_self = _weak_self; + return std::shared_ptr(getPtr(), [weak_self](C *ptr) { + auto strongPool = weak_self.lock(); + if (strongPool) { + //放入循环池 + strongPool->recycle(ptr); + } else { + delete ptr; + } + }); + } + +private: + void recycle(C *obj) { + auto is_busy = _busy.test_and_set(); + if (!is_busy) { + //获取到锁 + if (_objs.size() >= _pool_size) { + delete obj; + } else { + _objs.emplace_back(obj); + } + _busy.clear(); + } else { + //未获取到锁 + delete obj; + } + } + + C *getPtr() { + C *ptr; + auto is_busy = _busy.test_and_set(); + if (!is_busy) { + //获取到锁 + if (_objs.size() == 0) { + ptr = _alloc(); + } else { + ptr = _objs.back(); + _objs.pop_back(); + } + _busy.clear(); + } else { + //未获取到锁 + ptr = _alloc(); + } + return ptr; + } + + void setup() { _weak_self = this->shared_from_this(); } + +private: + size_t _pool_size = 8; + std::vector _objs; + std::function _alloc; + std::atomic_flag _busy { false }; + std::weak_ptr _weak_self; +}; + +/** + * 循环池,注意,循环池里面的对象不能继承enable_shared_from_this! + * @tparam C + */ +template +class ResourcePool { +public: + using ValuePtr = shared_ptr_imp; + ResourcePool() { + pool.reset(new ResourcePool_l()); + pool->setup(); + } +#if defined(SUPPORT_DYNAMIC_TEMPLATE) + template + ResourcePool(ArgTypes &&...args) { + pool = std::make_shared>(std::forward(args)...); + pool->setup(); + } +#endif // defined(SUPPORT_DYNAMIC_TEMPLATE) + void setSize(size_t size) { pool->setSize(size); } + + //获取一个对象,性能差些,但是功能丰富些 + ValuePtr obtain(const std::function &on_recycle = nullptr) { return pool->obtain(on_recycle); } + + //获取一个对象,性能好些 + std::shared_ptr obtain2() { return pool->obtain2(); } + +private: + std::shared_ptr> pool; +}; + +template +shared_ptr_imp::shared_ptr_imp(C *ptr, + const std::weak_ptr > &weakPool, + std::shared_ptr quit, + const std::function &on_recycle) : + std::shared_ptr(ptr, [weakPool, quit, on_recycle](C *ptr) { + if (on_recycle) { + on_recycle(ptr); + } + auto strongPool = weakPool.lock(); + if (strongPool && !(*quit)) { + //循环池还在并且不放弃放入循环池 + strongPool->recycle(ptr); + } else { + delete ptr; + } + }), _quit(std::move(quit)) {} + +} /* namespace toolkit */ +#endif /* UTIL_RECYCLEPOOL_H_ */ diff --git a/ToolKit/Util/RingBuffer.h b/ToolKit/Util/RingBuffer.h new file mode 100644 index 0000000..8ac1c85 --- /dev/null +++ b/ToolKit/Util/RingBuffer.h @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_RINGBUFFER_H_ +#define UTIL_RINGBUFFER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "List.h" +#include "Poller/EventPoller.h" + +// GOP缓存最大长度下限值 +#define RING_MIN_SIZE 32 +#define LOCK_GUARD(mtx) std::lock_guard lck(mtx) + +namespace toolkit { + +template +class RingDelegate { +public: + using Ptr = std::shared_ptr; + RingDelegate() = default; + virtual ~RingDelegate() = default; + virtual void onWrite(T in, bool is_key = true) = 0; +}; + +template +class _RingStorage; + +template +class _RingReaderDispatcher; + +/** + * 环形缓存读取器 + * 该对象的事件触发都会在绑定的poller线程中执行 + * 所以把锁去掉了 + * 对该对象的一切操作都应该在poller线程中执行 + */ +template +class _RingReader { +public: + using Ptr = std::shared_ptr<_RingReader>; + friend class _RingReaderDispatcher; + + _RingReader(std::shared_ptr<_RingStorage> storage) { + _storage = std::move(storage); + setReadCB(nullptr); + setDetachCB(nullptr); + setGetInfoCB(nullptr); + setMessageCB(nullptr); + } + + ~_RingReader() = default; + + void setReadCB(std::function cb) { + if (!cb) { + _read_cb = [](const T &) {}; + } else { + _read_cb = std::move(cb); + flushGop(); + } + } + + void setDetachCB(std::function cb) { + _detach_cb = cb ? std::move(cb) : []() {}; + } + + void setGetInfoCB(std::function cb) { + _info_cb = cb ? std::move(cb) : []() { return Any(); }; + } + + void setMessageCB(std::function cb) { + _msg_cb = cb ? std::move(cb) : [](const Any &data) {}; + } + +private: + void onRead(const T &data, bool /*is_key*/) { _read_cb(data); } + void onMessage(const Any &data) { _msg_cb(data); } + void onDetach() const { _detach_cb(); } + Any getInfo() { return _info_cb(); } + + void flushGop() { + if (!_storage) { + return; + } + _storage->getCache().for_each([this](const List> &lst) { + lst.for_each([this](const std::pair &pr) { onRead(pr.second, pr.first); }); + }); + } + + +private: + std::shared_ptr<_RingStorage> _storage; + std::function _detach_cb; + std::function _read_cb; + std::function _info_cb; + std::function _msg_cb; +}; + +template +class _RingStorage { +public: + using Ptr = std::shared_ptr<_RingStorage>; + using GopType = List>>; + _RingStorage(size_t max_size, size_t max_gop_size) { + // gop缓存个数不能小于32 + if (max_size < RING_MIN_SIZE) { + max_size = RING_MIN_SIZE; + } + _max_size = max_size; + _max_gop_size = max_gop_size; + clearCache(); + } + + ~_RingStorage() = default; + + /** + * 写入环形缓存数据 + * @param in 数据 + * @param is_key 是否为关键帧 + * @return 是否触发重置环形缓存大小 + */ + void write(T in, bool is_key = true) { + if (is_key) { + _have_idr = true; + _started = true; + if (!_data_cache.back().empty()) { + //当前gop列队还没收到任意缓存 + _data_cache.emplace_back(); + } + if (_data_cache.size() > _max_gop_size) { + // GOP个数超过限制,那么移除最早的GOP + popFrontGop(); + } + } + + if (!_have_idr && _started) { + //缓存中没有关键帧,那么gop缓存无效 + return; + } + _data_cache.back().emplace_back(std::make_pair(is_key, std::move(in))); + if (++_size > _max_size) { + // GOP缓存溢出 + while (_data_cache.size() > 1) { + //先尝试清除老的GOP缓存 + popFrontGop(); + } + if (_size > _max_size) { + //还是大于最大缓冲限制,那么清空所有GOP + clearCache(); + } + } + } + + Ptr clone() const { + Ptr ret(new _RingStorage()); + ret->_size = _size; + ret->_have_idr = _have_idr; + ret->_started = _started; + ret->_max_size = _max_size; + ret->_max_gop_size = _max_gop_size; + ret->_data_cache = _data_cache; + return ret; + } + + const GopType &getCache() const { return _data_cache; } + + void clearCache() { + _size = 0; + _have_idr = false; + _data_cache.clear(); + _data_cache.emplace_back(); + } + +private: + _RingStorage() = default; + + void popFrontGop() { + if (!_data_cache.empty()) { + _size -= _data_cache.front().size(); + _data_cache.pop_front(); + if (_data_cache.empty()) { + _data_cache.emplace_back(); + } + } + } + +private: + bool _started = false; + bool _have_idr; + size_t _size; + size_t _max_size; + size_t _max_gop_size; + GopType _data_cache; +}; + +template +class RingBuffer; + +/** + * 环形缓存事件派发器,只能一个poller线程操作它 + * @tparam T + */ +template +class _RingReaderDispatcher : public std::enable_shared_from_this<_RingReaderDispatcher> { +public: + using Ptr = std::shared_ptr<_RingReaderDispatcher>; + using RingReader = _RingReader; + using RingStorage = _RingStorage; + using onChangeInfoCB = std::function; + + friend class RingBuffer; + + ~_RingReaderDispatcher() { + decltype(_reader_map) reader_map; + reader_map.swap(_reader_map); + for (auto &pr : reader_map) { + auto reader = pr.second.lock(); + if (reader) { + reader->onDetach(); + } + } + } + +private: + _RingReaderDispatcher( + const typename RingStorage::Ptr &storage, std::function onSizeChanged) { + _reader_size = 0; + _storage = storage; + _on_size_changed = std::move(onSizeChanged); + assert(_on_size_changed); + } + + void write(T in, bool is_key = true) { + for (auto it = _reader_map.begin(); it != _reader_map.end();) { + auto reader = it->second.lock(); + if (!reader) { + it = _reader_map.erase(it); + --_reader_size; + onSizeChanged(false); + continue; + } + reader->onRead(in, is_key); + ++it; + } + _storage->write(std::move(in), is_key); + } + + void sendMessage(const Any &data) { + for (auto it = _reader_map.begin(); it != _reader_map.end();) { + auto reader = it->second.lock(); + if (!reader) { + it = _reader_map.erase(it); + --_reader_size; + onSizeChanged(false); + continue; + } + reader->onMessage(data); + ++it; + } + } + + std::shared_ptr attach(const EventPoller::Ptr &poller, bool use_cache) { + if (!poller->isCurrentThread()) { + throw std::runtime_error("You can attach RingBuffer only in it's poller thread"); + } + + std::weak_ptr<_RingReaderDispatcher> weak_self = this->shared_from_this(); + auto on_dealloc = [weak_self, poller](RingReader *ptr) { + poller->async([weak_self, ptr]() { + auto strong_self = weak_self.lock(); + if (strong_self && strong_self->_reader_map.erase(ptr)) { + --strong_self->_reader_size; + strong_self->onSizeChanged(false); + } + delete ptr; + }); + }; + + std::shared_ptr reader(new RingReader(use_cache ? _storage : nullptr), on_dealloc); + _reader_map[reader.get()] = reader; + ++_reader_size; + onSizeChanged(true); + return reader; + } + + void onSizeChanged(bool add_flag) { _on_size_changed(_reader_size, add_flag); } + + void clearCache() { + if (_reader_size == 0) { + _storage->clearCache(); + } + } + + std::list getInfoList(const onChangeInfoCB &on_change) { + std::list ret; + for (auto &pr : _reader_map) { + auto reader = pr.second.lock(); + if (!reader) { + continue; + } + auto info = reader->getInfo(); + if (!info) { + continue; + } + ret.emplace_back(on_change(std::move(info))); + } + return ret; + } + +private: + std::atomic_int _reader_size; + std::function _on_size_changed; + typename RingStorage::Ptr _storage; + std::unordered_map> _reader_map; +}; + +template +class RingBuffer : public std::enable_shared_from_this> { +public: + using Ptr = std::shared_ptr; + using RingReader = _RingReader; + using RingStorage = _RingStorage; + using RingReaderDispatcher = _RingReaderDispatcher; + using onReaderChanged = std::function; + using onGetInfoCB = std::function &info_list)>; + + RingBuffer(size_t max_size = 1024, onReaderChanged cb = nullptr, size_t max_gop_size = 1) { + _storage = std::make_shared(max_size, max_gop_size); + _on_reader_changed = cb ? std::move(cb) : [](int size) {}; + //先触发无人观看 + _on_reader_changed(0); + } + + ~RingBuffer() = default; + + void write(T in, bool is_key = true) { + if (_delegate) { + _delegate->onWrite(std::move(in), is_key); + return; + } + + LOCK_GUARD(_mtx_map); + for (auto &pr : _dispatcher_map) { + auto &second = pr.second; + //切换线程后触发onRead事件 + pr.first->async([second, in, is_key]() mutable { second->write(std::move(in), is_key); }, false); + } + _storage->write(std::move(in), is_key); + } + + void sendMessage(const Any &data) { + LOCK_GUARD(_mtx_map); + for (auto &pr : _dispatcher_map) { + auto &second = pr.second; + // 切换线程后触发sendMessage + pr.first->async([second, data]() { second->sendMessage(data); }, false); + } + } + + void setDelegate(const typename RingDelegate::Ptr &delegate) { _delegate = delegate; } + + std::shared_ptr attach(const EventPoller::Ptr &poller, bool use_cache = true) { + typename RingReaderDispatcher::Ptr dispatcher; + { + LOCK_GUARD(_mtx_map); + auto &ref = _dispatcher_map[poller]; + if (!ref) { + std::weak_ptr weak_self = this->shared_from_this(); + auto onSizeChanged = [weak_self, poller](int size, bool add_flag) { + if (auto strong_self = weak_self.lock()) { + strong_self->onSizeChanged(poller, size, add_flag); + } + }; + auto onDealloc = [poller](RingReaderDispatcher *ptr) { poller->async([ptr]() { delete ptr; }); }; + ref.reset(new RingReaderDispatcher(_storage->clone(), std::move(onSizeChanged)), std::move(onDealloc)); + } + dispatcher = ref; + } + + return dispatcher->attach(poller, use_cache); + } + + int readerCount() { return _total_count; } + + void clearCache() { + LOCK_GUARD(_mtx_map); + _storage->clearCache(); + for (auto &pr : _dispatcher_map) { + auto &second = pr.second; + //切换线程后清空缓存 + pr.first->async([second]() { second->clearCache(); }, false); + } + } + + void getInfoList(const onGetInfoCB &cb, const typename RingReaderDispatcher::onChangeInfoCB &on_change = nullptr) { + if (!cb) { + return; + } + if (!on_change) { + const_cast(on_change) = [](Any &&info) { return std::move(info); }; + } + + LOCK_GUARD(_mtx_map); + + auto info_vec = std::make_shared>>(); + // 1、最少确保一个元素 + info_vec->resize(_dispatcher_map.empty() ? 1 : _dispatcher_map.size()); + std::shared_ptr on_finished(nullptr, [cb, info_vec](void *) mutable { + // 2、防止这里为空 + auto &lst = *info_vec->begin(); + for (auto &item : *info_vec) { + if (&lst != &item) { + lst.insert(lst.end(), item.begin(), item.end()); + } + } + cb(lst); + }); + + auto i = 0U; + for (auto &pr : _dispatcher_map) { + auto &second = pr.second; + pr.first->async([second, info_vec, on_finished, i, on_change]() { (*info_vec)[i] = second->getInfoList(on_change); }); + ++i; + } + } + +private: + void onSizeChanged(const EventPoller::Ptr &poller, int size, bool add_flag) { + if (size == 0) { + LOCK_GUARD(_mtx_map); + _dispatcher_map.erase(poller); + } + + if (add_flag) { + ++_total_count; + } else { + --_total_count; + } + _on_reader_changed(_total_count); + } + +private: + struct HashOfPtr { + std::size_t operator()(const EventPoller::Ptr &key) const { return (std::size_t)key.get(); } + }; + +private: + std::mutex _mtx_map; + std::atomic_int _total_count { 0 }; + typename RingStorage::Ptr _storage; + typename RingDelegate::Ptr _delegate; + onReaderChanged _on_reader_changed; + std::unordered_map _dispatcher_map; +}; + +} /* namespace toolkit */ +#endif /* UTIL_RINGBUFFER_H_ */ diff --git a/ToolKit/Util/SHA1.cpp b/ToolKit/Util/SHA1.cpp new file mode 100644 index 0000000..b9c63d9 --- /dev/null +++ b/ToolKit/Util/SHA1.cpp @@ -0,0 +1,341 @@ +// 100% Public Domain. +// +// Original C Code +// -- Steve Reid +// Small changes to fit into bglibs +// -- Bruce Guenter +// Translation to simpler C++ Code +// -- Volker Grabsch +// Safety fixes +// -- Eugene Hopkinson +// Adapt for project +// Dmitriy Khaustov +// +// File created on: 2017.02.25 + +// SHA1.cpp + +#include "SHA1.h" + +#include +#include +#include + +namespace toolkit { + +static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ +static const size_t BLOCK_BYTES = BLOCK_INTS * 4; + + +static void reset(uint32_t digest[], std::string &buffer, uint64_t &transforms) +{ + /* SHA1 initialization constants */ + digest[0] = 0x67452301; + digest[1] = 0xefcdab89; + digest[2] = 0x98badcfe; + digest[3] = 0x10325476; + digest[4] = 0xc3d2e1f0; + + /* Reset counters */ + buffer = ""; + transforms = 0; +} + + +static uint32_t rol(const uint32_t value, const size_t bits) +{ + return (value << bits) | (value >> (32 - bits)); +} + + +static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i) +{ + return rol(block[(i+13)&15] ^ block[(i+8)&15] ^ block[(i+2)&15] ^ block[i], 1); +} + + +/* + * (R0+R1), R2, R3, R4 are the different operations used in SHA1 + */ + +static void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + + +static void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + + +static void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); + w = rol(w, 30); +} + + +static void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); + w = rol(w, 30); +} + + +static void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); + w = rol(w, 30); +} + + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ + +static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms) +{ + /* Copy digest[] to working vars */ + uint32_t a = digest[0]; + uint32_t b = digest[1]; + uint32_t c = digest[2]; + uint32_t d = digest[3]; + uint32_t e = digest[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(block, a, b, c, d, e, 0); + R0(block, e, a, b, c, d, 1); + R0(block, d, e, a, b, c, 2); + R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); + R0(block, a, b, c, d, e, 5); + R0(block, e, a, b, c, d, 6); + R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); + R0(block, b, c, d, e, a, 9); + R0(block, a, b, c, d, e, 10); + R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); + R0(block, c, d, e, a, b, 13); + R0(block, b, c, d, e, a, 14); + R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 0); + R1(block, d, e, a, b, c, 1); + R1(block, c, d, e, a, b, 2); + R1(block, b, c, d, e, a, 3); + R2(block, a, b, c, d, e, 4); + R2(block, e, a, b, c, d, 5); + R2(block, d, e, a, b, c, 6); + R2(block, c, d, e, a, b, 7); + R2(block, b, c, d, e, a, 8); + R2(block, a, b, c, d, e, 9); + R2(block, e, a, b, c, d, 10); + R2(block, d, e, a, b, c, 11); + R2(block, c, d, e, a, b, 12); + R2(block, b, c, d, e, a, 13); + R2(block, a, b, c, d, e, 14); + R2(block, e, a, b, c, d, 15); + R2(block, d, e, a, b, c, 0); + R2(block, c, d, e, a, b, 1); + R2(block, b, c, d, e, a, 2); + R2(block, a, b, c, d, e, 3); + R2(block, e, a, b, c, d, 4); + R2(block, d, e, a, b, c, 5); + R2(block, c, d, e, a, b, 6); + R2(block, b, c, d, e, a, 7); + R3(block, a, b, c, d, e, 8); + R3(block, e, a, b, c, d, 9); + R3(block, d, e, a, b, c, 10); + R3(block, c, d, e, a, b, 11); + R3(block, b, c, d, e, a, 12); + R3(block, a, b, c, d, e, 13); + R3(block, e, a, b, c, d, 14); + R3(block, d, e, a, b, c, 15); + R3(block, c, d, e, a, b, 0); + R3(block, b, c, d, e, a, 1); + R3(block, a, b, c, d, e, 2); + R3(block, e, a, b, c, d, 3); + R3(block, d, e, a, b, c, 4); + R3(block, c, d, e, a, b, 5); + R3(block, b, c, d, e, a, 6); + R3(block, a, b, c, d, e, 7); + R3(block, e, a, b, c, d, 8); + R3(block, d, e, a, b, c, 9); + R3(block, c, d, e, a, b, 10); + R3(block, b, c, d, e, a, 11); + R4(block, a, b, c, d, e, 12); + R4(block, e, a, b, c, d, 13); + R4(block, d, e, a, b, c, 14); + R4(block, c, d, e, a, b, 15); + R4(block, b, c, d, e, a, 0); + R4(block, a, b, c, d, e, 1); + R4(block, e, a, b, c, d, 2); + R4(block, d, e, a, b, c, 3); + R4(block, c, d, e, a, b, 4); + R4(block, b, c, d, e, a, 5); + R4(block, a, b, c, d, e, 6); + R4(block, e, a, b, c, d, 7); + R4(block, d, e, a, b, c, 8); + R4(block, c, d, e, a, b, 9); + R4(block, b, c, d, e, a, 10); + R4(block, a, b, c, d, e, 11); + R4(block, e, a, b, c, d, 12); + R4(block, d, e, a, b, c, 13); + R4(block, c, d, e, a, b, 14); + R4(block, b, c, d, e, a, 15); + + /* Add the working vars back into digest[] */ + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; + + /* Count the number of transformations */ + transforms++; +} + + +static void buffer_to_block(const std::string &buffer, uint32_t block[BLOCK_INTS]) +{ + /* Convert the std::string (byte buffer) to a uint32_t array (MSB) */ + for (size_t i = 0; i < BLOCK_INTS; i++) + { + block[i] = + (buffer[4*i+3] & 0xFF) + | (buffer[4*i+2] & 0xFF) << 8 + | (buffer[4*i+1] & 0xff) << 16 + | (buffer[4*i+0] & 0xff) << 24; + } +} + + +SHA1::SHA1() +{ + reset(digest, buffer, transforms); +} + + +void SHA1::update(const std::string &s) +{ + std::istringstream is(s); + update(is); +} + + +void SHA1::update(std::istream &is) +{ + while (true) + { + char sbuf[BLOCK_BYTES]; + is.read(sbuf, BLOCK_BYTES - buffer.size()); + buffer.append(sbuf, is.gcount()); + if (buffer.size() != BLOCK_BYTES) + { + return; + } + uint32_t block[BLOCK_INTS]; + buffer_to_block(buffer, block); + transform(digest, block, transforms); + buffer.clear(); + } +} + + +/* + * Add padding and return the message digest. + */ + +std::string SHA1::final() +{ + auto str = final_bin(); + + std::ostringstream result; + + for (size_t i = 0; i < str.size(); i++) + { + char b[3]; + sprintf(b, "%02x", static_cast(str[i])); + result << b; + } + + return result.str(); +} + +std::string SHA1::final_bin() +{ + /* Total number of hashed bits */ + uint64_t total_bits = (transforms*BLOCK_BYTES + buffer.size()) * 8; + + /* Padding */ + buffer += 0x80; + size_t orig_size = buffer.size(); + while (buffer.size() < BLOCK_BYTES) + { + buffer += (char)0x00; + } + + uint32_t block[BLOCK_INTS]; + buffer_to_block(buffer, block); + + if (orig_size > BLOCK_BYTES - 8) + { + transform(digest, block, transforms); + for (size_t i = 0; i < BLOCK_INTS - 2; i++) + { + block[i] = 0; + } + } + + /* Append total_bits, split this uint64_t into two uint32_t */ + block[BLOCK_INTS - 1] = total_bits; + block[BLOCK_INTS - 2] = (total_bits >> 32); + transform(digest, block, transforms); + + /* Hex std::string */ + std::string result; + for (size_t i = 0; i < sizeof(digest) / sizeof(digest[0]); i++) + { + for (size_t b = 0; b < sizeof(digest[0])/sizeof(uint8_t); b++) + { + result.push_back((digest[i] >> (8 * (sizeof(digest[0]) / sizeof(uint8_t) - 1 - b))) & 0xFF); + } + } + + /* Reset for next run */ + reset(digest, buffer, transforms); + + return result; +} + +std::string SHA1::from_file(const std::string &filename) +{ + std::ifstream stream(filename.c_str(), std::ios::binary); + SHA1 checksum; + checksum.update(stream); + return checksum.final(); +} + +std::string SHA1::encode(const std::string &s) +{ + SHA1 sha1; + sha1.update(s); + return sha1.final(); +} + +std::string SHA1::encode_bin(const std::string &s) +{ + SHA1 sha1; + sha1.update(s); + return sha1.final_bin(); +} + +} //namespace toolkit \ No newline at end of file diff --git a/ToolKit/Util/SHA1.h b/ToolKit/Util/SHA1.h new file mode 100644 index 0000000..a26d499 --- /dev/null +++ b/ToolKit/Util/SHA1.h @@ -0,0 +1,47 @@ +// 100% Public Domain. +// +// Original C Code +// -- Steve Reid +// Small changes to fit into bglibs +// -- Bruce Guenter +// Translation to simpler C++ Code +// -- Volker Grabsch +// Safety fixes +// -- Eugene Hopkinson +// Adapt for project +// Dmitriy Khaustov +// +// File created on: 2017.02.25 + +// SHA1.h + +#pragma once + +#include +#include +#include + +namespace toolkit { + +class SHA1 final +{ +public: + SHA1(); + + void update(const std::string &s); + void update(std::istream &is); + std::string final(); + std::string final_bin(); + + static std::string from_file(const std::string &filename); + + static std::string encode(const std::string &s); + static std::string encode_bin(const std::string &s); + +private: + uint32_t digest[5]; + std::string buffer; + uint64_t transforms; +}; + +}//namespace toolkit \ No newline at end of file diff --git a/ToolKit/Util/SSLBox.cpp b/ToolKit/Util/SSLBox.cpp new file mode 100644 index 0000000..a823482 --- /dev/null +++ b/ToolKit/Util/SSLBox.cpp @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "SSLBox.h" +#include "onceToken.h" +#include "SSLUtil.h" + +#if defined(ENABLE_OPENSSL) +#include +#include +#include +#include +#include +#include +#include +#endif //defined(ENABLE_OPENSSL) + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME +//openssl版本是否支持sni +#define SSL_ENABLE_SNI +#endif + +using namespace std; + +namespace toolkit { + +static bool s_ignore_invalid_cer = true; + +SSL_Initor &SSL_Initor::Instance() { + static SSL_Initor obj; + return obj; +} + +void SSL_Initor::ignoreInvalidCertificate(bool ignore) { + s_ignore_invalid_cer = ignore; +} + +SSL_Initor::SSL_Initor() { +#if defined(ENABLE_OPENSSL) + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_digests(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_algorithms(); + CRYPTO_set_locking_callback([](int mode, int n, const char *file, int line) { + static mutex *s_mutexes = new mutex[CRYPTO_num_locks()]; + static onceToken token(nullptr, []() { + delete[] s_mutexes; + }); + if (mode & CRYPTO_LOCK) { + s_mutexes[n].lock(); + } else { + s_mutexes[n].unlock(); + } + }); + + CRYPTO_set_id_callback([]() -> unsigned long { +#if !defined(_WIN32) + return (unsigned long) pthread_self(); +#else + return (unsigned long) GetCurrentThreadId(); +#endif + }); + + setContext("", SSLUtil::makeSSLContext(vector >(), nullptr, false), false); + setContext("", SSLUtil::makeSSLContext(vector >(), nullptr, true), true); +#endif //defined(ENABLE_OPENSSL) +} + +SSL_Initor::~SSL_Initor() { +#if defined(ENABLE_OPENSSL) + EVP_cleanup(); + ERR_free_strings(); + ERR_clear_error(); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L + ERR_remove_thread_state(nullptr); +#elif OPENSSL_VERSION_NUMBER < 0x10000000L + ERR_remove_state(0); +#endif + CRYPTO_set_locking_callback(nullptr); + //sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); + CRYPTO_cleanup_all_ex_data(); + CONF_modules_unload(1); + CONF_modules_free(); +#endif //defined(ENABLE_OPENSSL) +} + +bool SSL_Initor::loadCertificate(const string &pem_or_p12, bool server_mode, const string &password, bool is_file, + bool is_default) { + auto cers = SSLUtil::loadPublicKey(pem_or_p12, password, is_file); + auto key = SSLUtil::loadPrivateKey(pem_or_p12, password, is_file); + auto ssl_ctx = SSLUtil::makeSSLContext(cers, key, server_mode, true); + if (!ssl_ctx) { + return false; + } + for (auto &cer : cers) { + auto server_name = SSLUtil::getServerName(cer.get()); + setContext(server_name, ssl_ctx, server_mode, is_default); + break; + } + return true; +} + +int SSL_Initor::findCertificate(SSL *ssl, int *, void *arg) { +#if !defined(ENABLE_OPENSSL) || !defined(SSL_ENABLE_SNI) + return 0; +#else + if (!ssl) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + SSL_CTX *ctx = nullptr; + static auto &ref = SSL_Initor::Instance(); + const char *vhost = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + if (vhost && vhost[0] != '\0') { + //根据域名找到证书 + ctx = ref.getSSLCtx(vhost, (bool) (arg)).get(); + if (!ctx) { + //未找到对应的证书 + WarnL << "Can not find any certificate of host: " << vhost + << ", select default certificate of: " << ref._default_vhost[(bool) (arg)]; + } + } + + if (!ctx) { + //客户端未指定域名或者指定的证书不存在,那么选择一个默认的证书 + ctx = ref.getSSLCtx("", (bool) (arg)).get(); + } + + if (!ctx) { + //未有任何有效的证书 + WarnL << "Can not find any available certificate of host: " << (vhost ? vhost : "default host") + << ", tls handshake failed"; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + SSL_set_SSL_CTX(ssl, ctx); + return SSL_TLSEXT_ERR_OK; +#endif +} + +bool SSL_Initor::setContext(const string &vhost, const shared_ptr &ctx, bool server_mode, bool is_default) { + if (!ctx) { + return false; + } + setupCtx(ctx.get()); +#if defined(ENABLE_OPENSSL) + if (vhost.empty()) { + _ctx_empty[server_mode] = ctx; +#ifdef SSL_ENABLE_SNI + if (server_mode) { + SSL_CTX_set_tlsext_servername_callback(ctx.get(), findCertificate); + SSL_CTX_set_tlsext_servername_arg(ctx.get(), (void *) server_mode); + } +#endif // SSL_ENABLE_SNI + + } else { + _ctxs[server_mode][vhost] = ctx; + if (is_default) { + _default_vhost[server_mode] = vhost; + } + if (vhost.find("*.") == 0) { + //通配符证书 + _ctxs_wildcards[server_mode][vhost.substr(1)] = ctx; + } + DebugL << "Add certificate of: " << vhost; + } + return true; +#else + WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; + return false; +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Initor::setupCtx(SSL_CTX *ctx) { +#if defined(ENABLE_OPENSSL) + //加载默认信任证书 + SSLUtil::loadDefaultCAs(ctx); + SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:!3DES:!DES:!IDEA:!RC4:@STRENGTH"); + SSL_CTX_set_verify_depth(ctx, 9); + SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, [](int ok, X509_STORE_CTX *pStore) { + if (!ok) { + int depth = X509_STORE_CTX_get_error_depth(pStore); + int err = X509_STORE_CTX_get_error(pStore); + WarnL << "SSL_CTX_set_verify callback, depth: " << depth << " ,err: " << X509_verify_cert_error_string(err); + } + return s_ignore_invalid_cer ? 1 : ok; + }); + +#ifndef SSL_OP_NO_COMPRESSION +#define SSL_OP_NO_COMPRESSION 0 +#endif +#ifndef SSL_MODE_RELEASE_BUFFERS /* OpenSSL >= 1.0.0 */ +#define SSL_MODE_RELEASE_BUFFERS 0 +#endif + unsigned long ssloptions = SSL_OP_ALL + | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION + | SSL_OP_NO_COMPRESSION; + +#ifdef SSL_OP_NO_RENEGOTIATION /* openssl 1.1.0 */ + ssloptions |= SSL_OP_NO_RENEGOTIATION; +#endif + +#ifdef SSL_OP_NO_SSLv2 + ssloptions |= SSL_OP_NO_SSLv2; +#endif + +#ifdef SSL_OP_NO_SSLv3 + ssloptions |= SSL_OP_NO_SSLv3; +#endif + +#ifdef SSL_OP_NO_TLSv1 + ssloptions |= SSL_OP_NO_TLSv1; +#endif + +#ifdef SSL_OP_NO_TLSv1_1 /* openssl 1.0.1 */ + ssloptions |= SSL_OP_NO_TLSv1_1; +#endif + + SSL_CTX_set_options(ctx, ssloptions); + +#endif //defined(ENABLE_OPENSSL) +} + +shared_ptr SSL_Initor::makeSSL(bool server_mode) { +#if defined(ENABLE_OPENSSL) +#ifdef SSL_ENABLE_SNI + //openssl 版本支持SNI + return SSLUtil::makeSSL(_ctx_empty[server_mode].get()); +#else + //openssl 版本不支持SNI,选择默认证书 + return SSLUtil::makeSSL(getSSLCtx("",server_mode).get()); +#endif//SSL_CTRL_SET_TLSEXT_HOSTNAME +#else + return nullptr; +#endif //defined(ENABLE_OPENSSL) +} + +bool SSL_Initor::trustCertificate(X509 *cer, bool server_mode) { + return SSLUtil::trustCertificate(_ctx_empty[server_mode].get(), cer); +} + +bool SSL_Initor::trustCertificate(const string &pem_p12_cer, bool server_mode, const string &password, bool is_file) { + auto cers = SSLUtil::loadPublicKey(pem_p12_cer, password, is_file); + for (auto &cer : cers) { + trustCertificate(cer.get(), server_mode); + } + return true; +} + +std::shared_ptr SSL_Initor::getSSLCtx(const string &vhost, bool server_mode) { + auto ret = getSSLCtx_l(vhost, server_mode); + if (ret) { + return ret; + } + return getSSLCtxWildcards(vhost, server_mode); +} + +std::shared_ptr SSL_Initor::getSSLCtxWildcards(const string &vhost, bool server_mode) { + for (auto &pr : _ctxs_wildcards[server_mode]) { + auto pos = strcasestr(vhost.data(), pr.first.data()); + if (pos && pos + pr.first.size() == &vhost.back() + 1) { + return pr.second; + } + } + return nullptr; +} + +std::shared_ptr SSL_Initor::getSSLCtx_l(const string &vhost_in, bool server_mode) { + auto vhost = vhost_in; + if (vhost.empty()) { + if (!_default_vhost[server_mode].empty()) { + vhost = _default_vhost[server_mode]; + } else { + //没默认主机,选择空主机 + if (server_mode) { + WarnL << "Server with ssl must have certification and key"; + } + return _ctx_empty[server_mode]; + } + } + //根据主机名查找证书 + auto it = _ctxs[server_mode].find(vhost); + if (it == _ctxs[server_mode].end()) { + return nullptr; + } + return it->second; +} + +string SSL_Initor::defaultVhost(bool server_mode) { + return _default_vhost[server_mode]; +} + +////////////////////////////////////////////////////SSL_Box//////////////////////////////////////////////////////////// + +SSL_Box::~SSL_Box() {} + +SSL_Box::SSL_Box(bool server_mode, bool enable, int buff_size) { +#if defined(ENABLE_OPENSSL) + _read_bio = BIO_new(BIO_s_mem()); + _server_mode = server_mode; + if (enable) { + _ssl = SSL_Initor::Instance().makeSSL(server_mode); + } + if (_ssl) { + _write_bio = BIO_new(BIO_s_mem()); + SSL_set_bio(_ssl.get(), _read_bio, _write_bio); + _server_mode ? SSL_set_accept_state(_ssl.get()) : SSL_set_connect_state(_ssl.get()); + } else { + WarnL << "makeSSL failed"; + } + _send_handshake = false; + _buff_size = buff_size; +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Box::shutdown() { +#if defined(ENABLE_OPENSSL) + _buffer_send.clear(); + int ret = SSL_shutdown(_ssl.get()); + if (ret != 1) { + ErrorL << "SSL_shutdown failed: " << SSLUtil::getLastError(); + } else { + flush(); + } +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Box::onRecv(const Buffer::Ptr &buffer) { + if (!buffer->size()) { + return; + } + if (!_ssl) { + if (_on_dec) { + _on_dec(buffer); + } + return; + } +#if defined(ENABLE_OPENSSL) + uint32_t offset = 0; + while (offset < buffer->size()) { + auto nwrite = BIO_write(_read_bio, buffer->data() + offset, buffer->size() - offset); + if (nwrite > 0) { + //部分或全部写入bio完毕 + offset += nwrite; + flush(); + continue; + } + //nwrite <= 0,出现异常 + ErrorL << "Ssl error on BIO_write: " << SSLUtil::getLastError(); + shutdown(); + break; + } +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Box::onSend(Buffer::Ptr buffer) { + if (!buffer->size()) { + return; + } + if (!_ssl) { + if (_on_enc) { + _on_enc(buffer); + } + return; + } +#if defined(ENABLE_OPENSSL) + if (!_server_mode && !_send_handshake) { + _send_handshake = true; + SSL_do_handshake(_ssl.get()); + } + _buffer_send.emplace_back(std::move(buffer)); + flush(); +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Box::setOnDecData(const function &cb) { + _on_dec = cb; +} + +void SSL_Box::setOnEncData(const function &cb) { + _on_enc = cb; +} + +void SSL_Box::flushWriteBio() { +#if defined(ENABLE_OPENSSL) + int total = 0; + int nread = 0; + auto buffer_bio = _buffer_pool.obtain2(); + buffer_bio->setCapacity(_buff_size); + auto buf_size = buffer_bio->getCapacity() - 1; + do { + nread = BIO_read(_write_bio, buffer_bio->data() + total, buf_size - total); + if (nread > 0) { + total += nread; + } + } while (nread > 0 && buf_size - total > 0); + + if (!total) { + //未有数据 + return; + } + + //触发此次回调 + buffer_bio->data()[total] = '\0'; + buffer_bio->setSize(total); + if (_on_enc) { + _on_enc(buffer_bio); + } + + if (nread > 0) { + //还有剩余数据,读取剩余数据 + flushWriteBio(); + } +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Box::flushReadBio() { +#if defined(ENABLE_OPENSSL) + int total = 0; + int nread = 0; + auto buffer_bio = _buffer_pool.obtain2(); + buffer_bio->setCapacity(_buff_size); + auto buf_size = buffer_bio->getCapacity() - 1; + do { + nread = SSL_read(_ssl.get(), buffer_bio->data() + total, buf_size - total); + if (nread > 0) { + total += nread; + } + } while (nread > 0 && buf_size - total > 0); + + if (!total) { + //未有数据 + return; + } + + //触发此次回调 + buffer_bio->data()[total] = '\0'; + buffer_bio->setSize(total); + if (_on_dec) { + _on_dec(buffer_bio); + } + + if (nread > 0) { + //还有剩余数据,读取剩余数据 + flushReadBio(); + } +#endif //defined(ENABLE_OPENSSL) +} + +void SSL_Box::flush() { +#if defined(ENABLE_OPENSSL) + if (_is_flush) { + return; + } + onceToken token([&] { + _is_flush = true; + }, [&]() { + _is_flush = false; + }); + + flushReadBio(); + if (!SSL_is_init_finished(_ssl.get()) || _buffer_send.empty()) { + //ssl未握手结束或没有需要发送的数据 + flushWriteBio(); + return; + } + + //加密数据并发送 + while (!_buffer_send.empty()) { + auto &front = _buffer_send.front(); + uint32_t offset = 0; + while (offset < front->size()) { + auto nwrite = SSL_write(_ssl.get(), front->data() + offset, front->size() - offset); + if (nwrite > 0) { + //部分或全部写入完毕 + offset += nwrite; + flushWriteBio(); + continue; + } + //nwrite <= 0,出现异常 + break; + } + + if (offset != front->size()) { + //这个包未消费完毕,出现了异常,清空数据并断开ssl + ErrorL << "Ssl error on SSL_write: " << SSLUtil::getLastError(); + shutdown(); + break; + } + + //这个包消费完毕,开始消费下一个包 + _buffer_send.pop_front(); + } +#endif //defined(ENABLE_OPENSSL) +} + +bool SSL_Box::setHost(const char *host) { + if (!_ssl) { + return false; + } +#ifdef SSL_ENABLE_SNI + return 0 != SSL_set_tlsext_host_name(_ssl.get(), host); +#else + return false; +#endif//SSL_ENABLE_SNI +} + +} /* namespace toolkit */ diff --git a/ToolKit/Util/SSLBox.h b/ToolKit/Util/SSLBox.h new file mode 100644 index 0000000..34515b1 --- /dev/null +++ b/ToolKit/Util/SSLBox.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CRYPTO_SSLBOX_H_ +#define CRYPTO_SSLBOX_H_ + +#include +#include +#include +#include "logger.h" +#include "List.h" +#include "util.h" +#include "Network/Buffer.h" +#include "ResourcePool.h" + +typedef struct x509_st X509; +typedef struct evp_pkey_st EVP_PKEY; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct ssl_st SSL; +typedef struct bio_st BIO; + +namespace toolkit { + +class SSL_Initor { +public: + friend class SSL_Box; + + static SSL_Initor &Instance(); + + /** + * 从文件或字符串中加载公钥和私钥 + * 该证书文件必须同时包含公钥和私钥(cer格式的证书只包括公钥,请使用后面的方法加载) + * 客户端默认可以不加载证书(除非服务器要求客户端提供证书) + * @param pem_or_p12 pem或p12文件路径或者文件内容字符串 + * @param server_mode 是否为服务器模式 + * @param password 私钥加密密码 + * @param is_file 参数pem_or_p12是否为文件路径 + * @param is_default 是否为默认证书 + */ + bool loadCertificate(const std::string &pem_or_p12, bool server_mode = true, const std::string &password = "", + bool is_file = true, bool is_default = true); + + /** + * 是否忽略无效的证书 + * 默认忽略,强烈建议不要忽略! + * @param ignore 标记 + */ + void ignoreInvalidCertificate(bool ignore = true); + + /** + * 信任某证书,一般用于客户端信任自签名的证书或自签名CA签署的证书使用 + * 比如说我的客户端要信任我自己签发的证书,那么我们可以只信任这个证书 + * @param pem_p12_cer pem文件或p12文件或cer文件路径或内容 + * @param server_mode 是否为服务器模式 + * @param password pem或p12证书的密码 + * @param is_file 是否为文件路径 + * @return 是否加载成功 + */ + bool trustCertificate(const std::string &pem_p12_cer, bool server_mode = false, const std::string &password = "", + bool is_file = true); + + /** + * 信任某证书 + * @param cer 证书公钥 + * @param server_mode 是否为服务模式 + * @return 是否加载成功 + */ + bool trustCertificate(X509 *cer, bool server_mode = false); + + /** + * 根据虚拟主机获取SSL_CTX对象 + * @param vhost 虚拟主机名 + * @param server_mode 是否为服务器模式 + * @return SSL_CTX对象 + */ + std::shared_ptr getSSLCtx(const std::string &vhost, bool server_mode); + +private: + SSL_Initor(); + ~SSL_Initor(); + + /** + * 创建SSL对象 + */ + std::shared_ptr makeSSL(bool server_mode); + + /** + * 设置ssl context + * @param vhost 虚拟主机名 + * @param ctx ssl context + * @param server_mode ssl context + * @param is_default 是否为默认证书 + */ + bool setContext(const std::string &vhost, const std::shared_ptr &ctx, bool server_mode, bool is_default = true); + + /** + * 设置SSL_CTX的默认配置 + * @param ctx 对象指针 + */ + void setupCtx(SSL_CTX *ctx); + + std::shared_ptr getSSLCtx_l(const std::string &vhost, bool server_mode); + + std::shared_ptr getSSLCtxWildcards(const std::string &vhost, bool server_mode); + + /** + * 获取默认的虚拟主机 + */ + std::string defaultVhost(bool server_mode); + + /** + * 完成vhost name 匹配的回调函数 + */ + static int findCertificate(SSL *ssl, int *ad, void *arg); + +private: + struct less_nocase { + bool operator()(const std::string &x, const std::string &y) const { + return strcasecmp(x.data(), y.data()) < 0; + } + }; + +private: + std::string _default_vhost[2]; + std::shared_ptr _ctx_empty[2]; + std::map, less_nocase> _ctxs[2]; + std::map, less_nocase> _ctxs_wildcards[2]; +}; + +//////////////////////////////////////////////////////////////////////////////////// + +class SSL_Box { +public: + SSL_Box(bool server_mode = true, bool enable = true, int buff_size = 32 * 1024); + + ~SSL_Box(); + + /** + * 收到密文后,调用此函数解密 + * @param buffer 收到的密文数据 + */ + void onRecv(const Buffer::Ptr &buffer); + + /** + * 需要加密明文调用此函数 + * @param buffer 需要加密的明文数据 + */ + void onSend(Buffer::Ptr buffer); + + /** + * 设置解密后获取明文的回调 + * @param cb 回调对象 + */ + void setOnDecData(const std::function &cb); + + /** + * 设置加密后获取密文的回调 + * @param cb 回调对象 + */ + void setOnEncData(const std::function &cb); + + /** + * 终结ssl + */ + void shutdown(); + + /** + * 清空数据 + */ + void flush(); + + /** + * 设置虚拟主机名 + * @param host 虚拟主机名 + * @return 是否成功 + */ + bool setHost(const char *host); + +private: + void flushWriteBio(); + + void flushReadBio(); + +private: + bool _server_mode; + bool _send_handshake; + bool _is_flush = false; + int _buff_size; + BIO *_read_bio; + BIO *_write_bio; + std::shared_ptr _ssl; + List _buffer_send; + ResourcePool _buffer_pool; + std::function _on_dec; + std::function _on_enc; +}; + +} /* namespace toolkit */ +#endif /* CRYPTO_SSLBOX_H_ */ diff --git a/ToolKit/Util/SSLUtil.cpp b/ToolKit/Util/SSLUtil.cpp new file mode 100644 index 0000000..7207413 --- /dev/null +++ b/ToolKit/Util/SSLUtil.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "SSLUtil.h" +#include "onceToken.h" +#include "logger.h" + +#if defined(ENABLE_OPENSSL) +#include +#include +#include +#include +#include +#include +#include +#include +#endif //defined(ENABLE_OPENSSL) + +using namespace std; + +namespace toolkit { + +std::string SSLUtil::getLastError() { +#if defined(ENABLE_OPENSSL) + unsigned long errCode = ERR_get_error(); + if (errCode != 0) { + char buffer[256]; + ERR_error_string_n(errCode, buffer, sizeof(buffer)); + return buffer; + } else +#endif //defined(ENABLE_OPENSSL) + { + return "No error"; + } +} + +#if defined(ENABLE_OPENSSL) + +static int getCerType(BIO *bio, const char *passwd, X509 **x509, int type) { + //尝试pem格式 + if (type == 1 || type == 0) { + if (type == 0) { + BIO_reset(bio); + } + // 尝试PEM格式 + *x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (*x509) { + return 1; + } + } + + if (type == 2 || type == 0) { + if (type == 0) { + BIO_reset(bio); + } + //尝试DER格式 + *x509 = d2i_X509_bio(bio, nullptr); + if (*x509) { + return 2; + } + } + + if (type == 3 || type == 0) { + if (type == 0) { + BIO_reset(bio); + } + //尝试p12格式 + PKCS12 *p12 = d2i_PKCS12_bio(bio, nullptr); + if (p12) { + EVP_PKEY *pkey = nullptr; + PKCS12_parse(p12, passwd, &pkey, x509, nullptr); + PKCS12_free(p12); + if (pkey) { + EVP_PKEY_free(pkey); + } + if (*x509) { + return 3; + } + } + } + + return 0; +} + +#endif //defined(ENABLE_OPENSSL) + +vector > SSLUtil::loadPublicKey(const string &file_path_or_data, const string &passwd, bool isFile) { + vector > ret; +#if defined(ENABLE_OPENSSL) + BIO *bio = isFile ? BIO_new_file((char *) file_path_or_data.data(), "r") : + BIO_new_mem_buf((char *) file_path_or_data.data(), file_path_or_data.size()); + if (!bio) { + WarnL << (isFile ? "BIO_new_file" : "BIO_new_mem_buf") << " failed: " << getLastError(); + return ret; + } + + onceToken token0(nullptr, [&]() { + BIO_free(bio); + }); + + int cer_type = 0; + X509 *x509 = nullptr; + do { + cer_type = getCerType(bio, passwd.data(), &x509, cer_type); + if (cer_type) { + ret.push_back(shared_ptr(x509, [](X509 *ptr) { X509_free(ptr); })); + } + } while (cer_type != 0); + return ret; +#else + return ret; +#endif //defined(ENABLE_OPENSSL) +} + +shared_ptr SSLUtil::loadPrivateKey(const string &file_path_or_data, const string &passwd, bool isFile) { +#if defined(ENABLE_OPENSSL) + BIO *bio = isFile ? + BIO_new_file((char *) file_path_or_data.data(), "r") : + BIO_new_mem_buf((char *) file_path_or_data.data(), file_path_or_data.size()); + if (!bio) { + WarnL << (isFile ? "BIO_new_file" : "BIO_new_mem_buf") << " failed: " << getLastError(); + return nullptr; + } + + pem_password_cb *cb = [](char *buf, int size, int rwflag, void *userdata) -> int { + const string *passwd = (const string *) userdata; + size = size < (int) passwd->size() ? size : (int) passwd->size(); + memcpy(buf, passwd->data(), size); + return size; + }; + + onceToken token0(nullptr, [&]() { + BIO_free(bio); + }); + + //尝试pem格式 + EVP_PKEY *evp_key = PEM_read_bio_PrivateKey(bio, nullptr, cb, (void *) &passwd); + if (!evp_key) { + //尝试p12格式 + BIO_reset(bio); + PKCS12 *p12 = d2i_PKCS12_bio(bio, nullptr); + if (!p12) { + return nullptr; + } + X509 *x509 = nullptr; + PKCS12_parse(p12, passwd.data(), &evp_key, &x509, nullptr); + PKCS12_free(p12); + if (x509) { + X509_free(x509); + } + if (!evp_key) { + return nullptr; + } + } + + return shared_ptr(evp_key, [](EVP_PKEY *ptr) { + EVP_PKEY_free(ptr); + }); +#else + return nullptr; +#endif //defined(ENABLE_OPENSSL) +} + +shared_ptr SSLUtil::makeSSLContext(const vector > &cers, const shared_ptr &key, bool serverMode, bool checkKey) { +#if defined(ENABLE_OPENSSL) + SSL_CTX *ctx = SSL_CTX_new(serverMode ? SSLv23_server_method() : SSLv23_client_method()); + if (!ctx) { + WarnL << "SSL_CTX_new " << (serverMode ? "SSLv23_server_method" : "SSLv23_client_method") << " failed: " << getLastError(); + return nullptr; + } + int i = 0; + for (auto &cer : cers) { + //加载公钥 + if (i++ == 0) { + //SSL_CTX_use_certificate内部会调用X509_up_ref,所以这里不用X509_dup + SSL_CTX_use_certificate(ctx, cer.get()); + } else { + //需要先拷贝X509对象,否则指针会失效 + SSL_CTX_add_extra_chain_cert(ctx, X509_dup(cer.get())); + } + } + + if (key) { + //提供了私钥 + if (SSL_CTX_use_PrivateKey(ctx, key.get()) != 1) { + WarnL << "SSL_CTX_use_PrivateKey failed: " << getLastError(); + SSL_CTX_free(ctx); + return nullptr; + } + } + + if (key || checkKey) { + //加载私钥成功 + if (SSL_CTX_check_private_key(ctx) != 1) { + WarnL << "SSL_CTX_check_private_key failed: " << getLastError(); + SSL_CTX_free(ctx); + return nullptr; + } + } + + //公钥私钥匹配或者没有公私钥 + return shared_ptr(ctx, [](SSL_CTX *ptr) { SSL_CTX_free(ptr); }); +#else + return nullptr; +#endif //defined(ENABLE_OPENSSL) +} + +shared_ptr SSLUtil::makeSSL(SSL_CTX *ctx) { +#if defined(ENABLE_OPENSSL) + auto *ssl = SSL_new(ctx); + if (!ssl) { + return nullptr; + } + return shared_ptr(ssl, [](SSL *ptr) { + SSL_free(ptr); + }); +#else + return nullptr; +#endif //defined(ENABLE_OPENSSL) +} + +bool SSLUtil::loadDefaultCAs(SSL_CTX *ctx) { +#if defined(ENABLE_OPENSSL) + if (!ctx) { + return false; + } + + if (SSL_CTX_set_default_verify_paths(ctx) != 1) { + WarnL << "SSL_CTX_set_default_verify_paths failed: " << getLastError(); + return false; + } + return true; +#else + return false; +#endif //defined(ENABLE_OPENSSL) +} + +bool SSLUtil::trustCertificate(SSL_CTX *ctx, X509 *cer) { +#if defined(ENABLE_OPENSSL) + X509_STORE *store = SSL_CTX_get_cert_store(ctx); + if (store && cer) { + if (X509_STORE_add_cert(store, cer) != 1) { + WarnL << "X509_STORE_add_cert failed: " << getLastError(); + return false; + } + return true; + } +#endif //defined(ENABLE_OPENSSL) + return false; +} + +bool SSLUtil::verifyX509(X509 *cer, ...) { +#if defined(ENABLE_OPENSSL) + va_list args; + va_start(args, cer); + X509_STORE *store = X509_STORE_new(); + do { + X509 *ca; + if ((ca = va_arg(args, X509*)) == nullptr) { + break; + } + X509_STORE_add_cert(store, ca); + } while (true); + va_end(args); + + X509_STORE_CTX *store_ctx = X509_STORE_CTX_new(); + X509_STORE_CTX_init(store_ctx, store, cer, nullptr); + auto ret = X509_verify_cert(store_ctx); + if (ret != 1) { + int depth = X509_STORE_CTX_get_error_depth(store_ctx); + int err = X509_STORE_CTX_get_error(store_ctx); + WarnL << "X509_verify_cert failed, depth: " << depth << ", err: " << X509_verify_cert_error_string(err); + } + + X509_STORE_CTX_free(store_ctx); + X509_STORE_free(store); + return ret == 1; +#else + WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; + return false; +#endif //defined(ENABLE_OPENSSL) +} + +#ifdef ENABLE_OPENSSL +#ifndef X509_F_X509_PUBKEY_GET0 +EVP_PKEY *X509_get0_pubkey(X509 *x){ + EVP_PKEY *ret = X509_get_pubkey(x); + if(ret){ + EVP_PKEY_free(ret); + } + return ret; +} +#endif //X509_F_X509_PUBKEY_GET0 + +#ifndef EVP_F_EVP_PKEY_GET0_RSA +RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey){ + RSA *ret = EVP_PKEY_get1_RSA(pkey); + if(ret){ + RSA_free(ret); + } + return ret; +} +#endif //EVP_F_EVP_PKEY_GET0_RSA +#endif //ENABLE_OPENSSL + +string SSLUtil::cryptWithRsaPublicKey(X509 *cer, const string &in_str, bool enc_or_dec) { +#if defined(ENABLE_OPENSSL) + EVP_PKEY *public_key = X509_get0_pubkey(cer); + if (!public_key) { + return ""; + } + auto rsa = EVP_PKEY_get1_RSA(public_key); + if (!rsa) { + return ""; + } + string out_str(RSA_size(rsa), '\0'); + int ret = 0; + if (enc_or_dec) { + ret = RSA_public_encrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, + RSA_PKCS1_PADDING); + } else { + ret = RSA_public_decrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, + RSA_PKCS1_PADDING); + } + if (ret > 0) { + out_str.resize(ret); + return out_str; + } + WarnL << (enc_or_dec ? "RSA_public_encrypt" : "RSA_public_decrypt") << " failed: " << getLastError(); + return ""; +#else + WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; + return ""; +#endif //defined(ENABLE_OPENSSL) +} + +string SSLUtil::cryptWithRsaPrivateKey(EVP_PKEY *private_key, const string &in_str, bool enc_or_dec) { +#if defined(ENABLE_OPENSSL) + auto rsa = EVP_PKEY_get1_RSA(private_key); + if (!rsa) { + return ""; + } + string out_str(RSA_size(rsa), '\0'); + int ret = 0; + if (enc_or_dec) { + ret = RSA_private_encrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, + RSA_PKCS1_PADDING); + } else { + ret = RSA_private_decrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, + RSA_PKCS1_PADDING); + } + if (ret > 0) { + out_str.resize(ret); + return out_str; + } + WarnL << getLastError(); + return ""; +#else + WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; + return ""; +#endif //defined(ENABLE_OPENSSL) +} + +string SSLUtil::getServerName(X509 *cer) { +#if defined(ENABLE_OPENSSL) && defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) + if (!cer) { + return ""; + } + //获取证书里的域名 + X509_NAME *name = X509_get_subject_name(cer); + char ret[256] = {0}; + X509_NAME_get_text_by_NID(name, NID_commonName, ret, sizeof(ret)); + return ret; +#else + return ""; +#endif +} + +}//namespace toolkit diff --git a/ToolKit/Util/SSLUtil.h b/ToolKit/Util/SSLUtil.h new file mode 100644 index 0000000..4c8820b --- /dev/null +++ b/ToolKit/Util/SSLUtil.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_SSLUTIL_H +#define ZLTOOLKIT_SSLUTIL_H + +#include +#include +#include + +typedef struct x509_st X509; +typedef struct evp_pkey_st EVP_PKEY; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct ssl_st SSL; +typedef struct bio_st BIO; + +namespace toolkit { +/** + * ssl证书后缀一般分为以下几种 + * pem:这个是base64的字符编码串,可能存在公钥、私钥或者两者都存在 + * cer:只且只能是公钥,可以与pem的私钥配合使用 + * p12:必须包括私钥和公钥 + */ +class SSLUtil { +public: + static std::string getLastError(); + + /** + * 加载公钥证书,支持pem,p12,cer后缀 + * 由于openssl加载p12证书时会校验公钥和私钥是否匹对,所以加载p12的公钥时可能需要传入证书密码 + * @param file_path_or_data 文件路径或文件内容 + * @param isFile 是否为文件 + * @return 公钥证书列表 + */ + static std::vector > loadPublicKey(const std::string &file_path_or_data, const std::string &passwd = "", bool isFile = true); + + /** + * 加载私钥证书,支持pem,p12后缀 + * @param file_path_or_data 文件路径或文件内容 + * @param passwd 密码 + * @param isFile 是否为文件 + * @return 私钥证书 + */ + static std::shared_ptr loadPrivateKey(const std::string &file_path_or_data, const std::string &passwd = "", bool isFile = true); + + /** + * 创建SSL_CTX对象 + * @param cer 公钥数组 + * @param key 私钥 + * @param serverMode 是否为服务器模式或客户端模式 + * @return SSL_CTX对象 + */ + static std::shared_ptr makeSSLContext(const std::vector > &cers, const std::shared_ptr &key, bool serverMode = true, bool checkKey = false); + + /** + * 创建ssl对象 + * @param ctx SSL_CTX对象 + */ + static std::shared_ptr makeSSL(SSL_CTX *ctx); + + /** + * specifies that the default locations from which CA certificates are loaded should be used. + * There is one default directory and one default file. + * The default CA certificates directory is called "certs" in the default OpenSSL directory. + * Alternatively the SSL_CERT_DIR environment variable can be defined to override this location. + * The default CA certificates file is called "cert.pem" in the default OpenSSL directory. + * Alternatively the SSL_CERT_FILE environment variable can be defined to override this location. + * 信任/usr/local/ssl/certs/目录下的所有证书/usr/local/ssl/cert.pem的证书 + * 环境变量SSL_CERT_FILE将替换/usr/local/ssl/cert.pem的路径 + */ + static bool loadDefaultCAs(SSL_CTX *ctx); + + /** + * 信任某公钥 + */ + static bool trustCertificate(SSL_CTX *ctx, X509 *cer); + + + /** + * 验证证书合法性 + * @param cer 待验证的证书 + * @param ... 信任的CA根证书,X509类型,以nullptr结尾 + * @return 是否合法 + */ + static bool verifyX509(X509 *cer, ...); + + /** + * 使用公钥加解密数据 + * @param cer 公钥,必须为ras的公钥 + * @param in_str 加密或解密的原始数据,实测加密最大支持245个字节,加密后数据长度固定为256个字节 + * @param enc_or_dec true:加密,false:解密 + * @return 加密或解密后的数据 + */ + static std::string cryptWithRsaPublicKey(X509 *cer, const std::string &in_str, bool enc_or_dec); + + /** + * 使用私钥加解密数据 + * @param private_key 私钥,必须为ras的私钥 + * @param in_str 加密或解密的原始数据,实测加密最大支持245个字节,加密后数据长度固定为256个字节 + * @param enc_or_dec true:加密,false:解密 + * @return 加密或解密后的数据 + */ + static std::string cryptWithRsaPrivateKey(EVP_PKEY *private_key, const std::string &in_str, bool enc_or_dec); + + /** + * 获取证书域名 + * @param cer 证书公钥 + * @return 证书域名 + */ + static std::string getServerName(X509 *cer); +}; + +}//namespace toolkit +#endif //ZLTOOLKIT_SSLUTIL_H diff --git a/ToolKit/Util/SpeedStatistic.h b/ToolKit/Util/SpeedStatistic.h new file mode 100644 index 0000000..16b1aad --- /dev/null +++ b/ToolKit/Util/SpeedStatistic.h @@ -0,0 +1,65 @@ +/* +* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. +* +* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). +* +* Use of this source code is governed by MIT license that can be found in the +* LICENSE file in the root of the source tree. All contributing project authors +* may be found in the AUTHORS file in the root of the source tree. +*/ + +#ifndef SPEED_STATISTIC_H_ +#define SPEED_STATISTIC_H_ + +#include "TimeTicker.h" + +namespace toolkit { + +class BytesSpeed { +public: + BytesSpeed() = default; + ~BytesSpeed() = default; + + /** + * 添加统计字节 + */ + BytesSpeed &operator+=(size_t bytes) { + _bytes += bytes; + if (_bytes > 1024 * 1024) { + //数据大于1MB就计算一次网速 + computeSpeed(); + } + return *this; + } + + /** + * 获取速度,单位bytes/s + */ + int getSpeed() { + if (_ticker.elapsedTime() < 1000) { + //获取频率小于1秒,那么返回上次计算结果 + return _speed; + } + return computeSpeed(); + } + +private: + int computeSpeed() { + auto elapsed = _ticker.elapsedTime(); + if (!elapsed) { + return _speed; + } + _speed = (int)(_bytes * 1000 / elapsed); + _ticker.resetTime(); + _bytes = 0; + return _speed; + } + +private: + int _speed = 0; + size_t _bytes = 0; + Ticker _ticker; +}; + +} /* namespace toolkit */ +#endif /* SPEED_STATISTIC_H_ */ diff --git a/ToolKit/Util/TimeTicker.h b/ToolKit/Util/TimeTicker.h new file mode 100644 index 0000000..9b31d50 --- /dev/null +++ b/ToolKit/Util/TimeTicker.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_TIMETICKER_H_ +#define UTIL_TIMETICKER_H_ + +#include +#include "logger.h" + +namespace toolkit { + +class Ticker { +public: + /** + * 此对象可以用于代码执行时间统计,以可以用于一般计时 + * @param min_ms 开启码执行时间统计时,如果代码执行耗时超过该参数,则打印警告日志 + * @param ctx 日志上下文捕获,用于捕获当前日志代码所在位置 + * @param print_log 是否打印代码执行时间 + */ + Ticker(uint64_t min_ms = 0, + LogContextCapture ctx = LogContextCapture(Logger::Instance(), LWarn, __FILE__, "", __LINE__), + bool print_log = false) : _ctx(std::move(ctx)) { + if (!print_log) { + _ctx.clear(); + } + _created = _begin = getCurrentMillisecond(); + _min_ms = min_ms; + } + + ~Ticker() { + uint64_t tm = createdTime(); + if (tm > _min_ms) { + _ctx << "take time: " << tm << "ms" << ", thread may be overloaded"; + } else { + _ctx.clear(); + } + } + + /** + * 获取上次resetTime后至今的时间,单位毫秒 + */ + uint64_t elapsedTime() const { + return getCurrentMillisecond() - _begin; + } + + /** + * 获取从创建至今的时间,单位毫秒 + */ + uint64_t createdTime() const { + return getCurrentMillisecond() - _created; + } + + /** + * 重置计时器 + */ + void resetTime() { + _begin = getCurrentMillisecond(); + } + +private: + uint64_t _min_ms; + uint64_t _begin; + uint64_t _created; + LogContextCapture _ctx; +}; + +class SmoothTicker { +public: + /** + * 此对象用于生成平滑的时间戳 + * @param reset_ms 时间戳重置间隔,没间隔reset_ms毫秒, 生成的时间戳会同步一次系统时间戳 + */ + SmoothTicker(uint64_t reset_ms = 10000) { + _reset_ms = reset_ms; + _ticker.resetTime(); + } + + ~SmoothTicker() {} + + /** + * 返回平滑的时间戳,防止由于网络抖动导致时间戳不平滑 + */ + uint64_t elapsedTime() { + auto now_time = _ticker.elapsedTime(); + if (_first_time == 0) { + if (now_time < _last_time) { + auto last_time = _last_time - _time_inc; + double elapse_time = (now_time - last_time); + _time_inc += (elapse_time / ++_pkt_count) / 3; + auto ret_time = last_time + _time_inc; + _last_time = (uint64_t) ret_time; + return (uint64_t) ret_time; + } + _first_time = now_time; + _last_time = now_time; + _pkt_count = 0; + _time_inc = 0; + return now_time; + } + + auto elapse_time = (now_time - _first_time); + _time_inc += elapse_time / ++_pkt_count; + auto ret_time = _first_time + _time_inc; + if (elapse_time > _reset_ms) { + _first_time = 0; + } + _last_time = (uint64_t) ret_time; + return (uint64_t) ret_time; + } + + /** + * 时间戳重置为0开始 + */ + void resetTime() { + _first_time = 0; + _pkt_count = 0; + _ticker.resetTime(); + } + +private: + double _time_inc = 0; + uint64_t _first_time = 0; + uint64_t _last_time = 0; + uint64_t _pkt_count = 0; + uint64_t _reset_ms; + Ticker _ticker; +}; + +#if !defined(NDEBUG) +#define TimeTicker() Ticker __ticker(5,WarnL,true) +#define TimeTicker1(tm) Ticker __ticker1(tm,WarnL,true) +#define TimeTicker2(tm, log) Ticker __ticker2(tm,log,true) +#else +#define TimeTicker() +#define TimeTicker1(tm) +#define TimeTicker2(tm,log) +#endif + +} /* namespace toolkit */ +#endif /* UTIL_TIMETICKER_H_ */ diff --git a/ToolKit/Util/base64.cpp b/ToolKit/Util/base64.cpp new file mode 100644 index 0000000..ca89ae9 --- /dev/null +++ b/ToolKit/Util/base64.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @brief Base64 encode/decode + * @author Ryan Martell (with lots of Michael) + */ + +//#include "common.h" +#include "stdio.h" +#include "base64.h" +#include +#include + +using namespace std; + +/* ---------------- private code */ +static const uint8_t map2[] = +{ + 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 +}; + +int av_base64_decode(uint8_t *out, const char *in, int out_size) +{ + int i, v; + uint8_t *dst = out; + + v = 0; + for (i = 0; in[i] && in[i] != '='; i++) { + unsigned int index= in[i]-43; + if (index>=FF_ARRAY_ELEMS(map2) || map2[index] == 0xff) + return -1; + v = (v << 6) + map2[index]; + if (i & 3) { + if (dst - out < out_size) { + *dst++ = v >> (6 - 2 * (i & 3)); + } + } + } + + return dst - out; +} + +/***************************************************************************** +* b64_encode: Stolen from VLC's http.c. +* Simplified by Michael. +* Fixed edge cases and made it work from data (vs. strings) by Ryan. +*****************************************************************************/ + +char *av_base64_encode_l(char *out, int *out_size, const uint8_t *in, int in_size) { + static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char *ret, *dst; + unsigned i_bits = 0; + int i_shift = 0; + int bytes_remaining = in_size; + + if ((size_t)in_size >= UINT_MAX / 4 || *out_size < AV_BASE64_SIZE(in_size)) { + return nullptr; + } + ret = dst = out; + while (bytes_remaining) { + i_bits = (i_bits << 8) + *in++; + bytes_remaining--; + i_shift += 8; + + do { + *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; + i_shift -= 6; + } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); + } + while ((dst - ret) & 3) + *dst++ = '='; + *dst = '\0'; + + *out_size = dst - out; + return ret; +} + +char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size) { + return av_base64_encode_l(out, &out_size, in, in_size); +} + +string encodeBase64(const string &txt) { + if (txt.empty()) { + return ""; + } + int size = AV_BASE64_SIZE(txt.size()) + 10; + string ret; + ret.resize(size); + + if (!av_base64_encode_l((char *) ret.data(), &size, (const uint8_t *) txt.data(), txt.size())) { + return ""; + } + ret.resize(size); + return ret; +} + +string decodeBase64(const string &txt){ + if (txt.empty()) { + return ""; + } + string ret; + ret.resize(txt.size() * 3 / 4 + 10); + auto size = av_base64_decode((uint8_t *) ret.data(), txt.data(), ret.size()); + + if (size <= 0) { + return ""; + } + ret.resize(size); + return ret; +} + +#ifdef TEST + +#undef printf + +#define MAX_DATA_SIZE 1024 +#define MAX_ENCODED_SIZE 2048 + +static int test_encode_decode(const uint8_t *data, unsigned int data_size, + const char *encoded_ref) +{ + char encoded[MAX_ENCODED_SIZE]; + uint8_t data2[MAX_DATA_SIZE]; + int data2_size, max_data2_size = MAX_DATA_SIZE; + + if (!av_base64_encode(encoded, MAX_ENCODED_SIZE, data, data_size)) { + printf("Failed: cannot encode the input data\n"); + return 1; + } + if (encoded_ref && strcmp(encoded, encoded_ref)) { + printf("Failed: encoded string differs from reference\n" + "Encoded:\n%s\nReference:\n%s\n", encoded, encoded_ref); + return 1; + } + + if ((data2_size = av_base64_decode(data2, encoded, max_data2_size)) < 0) { + printf("Failed: cannot decode the encoded string\n" + "Encoded:\n%s\n", encoded); + return 1; + } + if (memcmp(data2, data, data_size)) { + printf("Failed: encoded/decoded data differs from original data\n"); + return 1; + } + + printf("Passed!\n"); + return 0; +} + +int main(void) +{ + int i, error_count = 0; + struct test { + const uint8_t *data; + const char *encoded_ref; + } tests[] = { + { "", ""}, + { "1", "MQ=="}, + { "22", "MjI="}, + { "333", "MzMz"}, + { "4444", "NDQ0NA=="}, + { "55555", "NTU1NTU="}, + { "666666", "NjY2NjY2"}, + { "abc:def", "YWJjOmRlZg=="}, + }; + + printf("Encoding/decoding tests\n"); + for (i = 0; i < FF_ARRAY_ELEMS(tests); i++) + error_count += test_encode_decode(tests[i].data, strlen(tests[i].data), tests[i].encoded_ref); + + return error_count; +} + +#endif diff --git a/ToolKit/Util/base64.h b/ToolKit/Util/base64.h new file mode 100644 index 0000000..e762614 --- /dev/null +++ b/ToolKit/Util/base64.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) +#ifndef AVUTIL_BASE64_H +#define AVUTIL_BASE64_H + +#include +#include + +/** + * Decode a base64-encoded string. + * + * @param out buffer for decoded data + * @param in null-terminated input string + * @param out_size size in bytes of the out buffer, must be at + * least 3/4 of the length of in + * @return number of bytes written, or a negative value in case of + * invalid input + */ +int av_base64_decode(uint8_t *out, const char *in, int out_size); + +/** + * Encode data to base64 and null-terminate. + * + * @param out buffer for encoded data + * @param out_size size in bytes of the output buffer, must be at + * least AV_BASE64_SIZE(in_size) + * @param in_size size in bytes of the 'in' buffer + * @return 'out' or NULL in case of error + */ +char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size); + +/** + * Calculate the output size needed to base64-encode x bytes. + */ +#define AV_BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) + + +/** + * 编码base64 + * @param txt 明文 + * @return 密文 + */ +std::string encodeBase64(const std::string &txt); + +/** + * 解码base64 + * @param txt 密文 + * @return 明文 + */ +std::string decodeBase64(const std::string &txt); + +#endif /* AVUTIL_BASE64_H */ diff --git a/ToolKit/Util/function_traits.h b/ToolKit/Util/function_traits.h new file mode 100644 index 0000000..14ad030 --- /dev/null +++ b/ToolKit/Util/function_traits.h @@ -0,0 +1,55 @@ +#ifndef SRC_UTIL_FUNCTION_TRAITS_H_ +#define SRC_UTIL_FUNCTION_TRAITS_H_ + +#include +#include + +namespace toolkit { + +template +struct function_traits; + +//普通函数 +template +struct function_traits +{ +public: + enum { arity = sizeof...(Args) }; + typedef Ret function_type(Args...); + typedef Ret return_type; + using stl_function_type = std::function; + typedef Ret(*pointer)(Args...); + + template + struct args + { + static_assert(I < arity, "index is out of range, index must less than sizeof Args"); + using type = typename std::tuple_element >::type; + }; +}; + +//函数指针 +template +struct function_traits : function_traits{}; + +//std::function +template +struct function_traits> : function_traits{}; + +//member function +#define FUNCTION_TRAITS(...) \ + template \ + struct function_traits : function_traits{}; \ + +FUNCTION_TRAITS() +FUNCTION_TRAITS(const) +FUNCTION_TRAITS(volatile) +FUNCTION_TRAITS(const volatile) + +//函数对象 +template +struct function_traits : function_traits{}; + +} /* namespace toolkit */ + +#endif /* SRC_UTIL_FUNCTION_TRAITS_H_ */ diff --git a/ToolKit/Util/local_time.cpp b/ToolKit/Util/local_time.cpp new file mode 100644 index 0000000..9d458cd --- /dev/null +++ b/ToolKit/Util/local_time.cpp @@ -0,0 +1,174 @@ +// +// Created by alex on 2022/5/29. +// + +/* + * Copyright (c) 2018, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef _WIN32 +#include +#endif + +/* This is a safe version of localtime() which contains no locks and is + * fork() friendly. Even the _r version of localtime() cannot be used safely + * in Redis. Another thread may be calling localtime() while the main thread + * forks(). Later when the child process calls localtime() again, for instance + * in order to log something to the Redis log, it may deadlock: in the copy + * of the address space of the forked process the lock will never be released. + * + * This function takes the timezone 'tz' as argument, and the 'dst' flag is + * used to check if daylight saving time is currently in effect. The caller + * of this function should obtain such information calling tzset() ASAP in the + * main() function to obtain the timezone offset from the 'timezone' global + * variable. To obtain the daylight information, if it is currently active or not, + * one trick is to call localtime() in main() ASAP as well, and get the + * information from the tm_isdst field of the tm structure. However the daylight + * time may switch in the future for long running processes, so this information + * should be refreshed at safe times. + * + * Note that this function does not work for dates < 1/1/1970, it is solely + * designed to work with what time(NULL) may return, and to support Redis + * logging of the dates, it's not really a complete implementation. */ +namespace toolkit { +static int _daylight_active; +static long _current_timezone; + +int get_daylight_active() { + return _daylight_active; +} + +static int is_leap_year(time_t year) { + if (year % 4) + return 0; /* A year not divisible by 4 is not leap. */ + else if (year % 100) + return 1; /* If div by 4 and not 100 is surely leap. */ + else if (year % 400) + return 0; /* If div by 100 *and* not by 400 is not leap. */ + else + return 1; /* If div by 100 and 400 is leap. */ +} + +void no_locks_localtime(struct tm *tmp, time_t t) { + const time_t secs_min = 60; + const time_t secs_hour = 3600; + const time_t secs_day = 3600 * 24; + + t -= _current_timezone; /* Adjust for timezone. */ + t += 3600 * get_daylight_active(); /* Adjust for daylight time. */ + time_t days = t / secs_day; /* Days passed since epoch. */ + time_t seconds = t % secs_day; /* Remaining seconds. */ + + tmp->tm_isdst = get_daylight_active(); + tmp->tm_hour = seconds / secs_hour; + tmp->tm_min = (seconds % secs_hour) / secs_min; + tmp->tm_sec = (seconds % secs_hour) % secs_min; +#ifndef _WIN32 + tmp->tm_gmtoff = -_current_timezone; +#endif + /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure + * where sunday = 0, so to calculate the day of the week we have to add 4 + * and take the modulo by 7. */ + tmp->tm_wday = (days + 4) % 7; + + /* Calculate the current year. */ + tmp->tm_year = 1970; + while (1) { + /* Leap years have one day more. */ + time_t days_this_year = 365 + is_leap_year(tmp->tm_year); + if (days_this_year > days) + break; + days -= days_this_year; + tmp->tm_year++; + } + tmp->tm_yday = days; /* Number of day of the current year. */ + + /* We need to calculate in which month and day of the month we are. To do + * so we need to skip days according to how many days there are in each + * month, and adjust for the leap year that has one more day in February. */ + int mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + mdays[1] += is_leap_year(tmp->tm_year); + + tmp->tm_mon = 0; + while (days >= mdays[tmp->tm_mon]) { + days -= mdays[tmp->tm_mon]; + tmp->tm_mon++; + } + + tmp->tm_mday = days + 1; /* Add 1 since our 'days' is zero-based. */ + tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */ +} + +void local_time_init() { + /* Obtain timezone and daylight info. */ + tzset(); /* Now 'timezome' global is populated. */ +#if defined(__linux__) || defined(__sun) + _current_timezone = timezone; +#elif defined(_WIN32) + time_t time_utc; + struct tm tm_local; + + // Get the UTC time + time(&time_utc); + + // Get the local time + // Use localtime_r for threads safe for linux + //localtime_r(&time_utc, &tm_local); + localtime_s(&tm_local, &time_utc); + + time_t time_local; + struct tm tm_gmt; + + // Change tm to time_t + time_local = mktime(&tm_local); + + // Change it to GMT tm + //gmtime_r(&time_utc, &tm_gmt);//linux + gmtime_s(&tm_gmt, &time_utc); + + int time_zone = tm_local.tm_hour - tm_gmt.tm_hour; + if (time_zone < -12) { + time_zone += 24; + } + else if (time_zone > 12) { + time_zone -= 24; + } + + _current_timezone = time_zone; +#else + struct timeval tv; + struct timezone tz; + gettimeofday(&tv, &tz); + _current_timezone = tz.tz_minuteswest * 60L; +#endif + time_t t = time(NULL); + struct tm *aux = localtime(&t); + _daylight_active = aux->tm_isdst; +} +} // namespace toolkit diff --git a/ToolKit/Util/local_time.h b/ToolKit/Util/local_time.h new file mode 100644 index 0000000..cc83543 --- /dev/null +++ b/ToolKit/Util/local_time.h @@ -0,0 +1,15 @@ +// +// Created by alex on 2022/5/29. +// + +#ifndef UTIL_LOCALTIME_H +#define UTIL_LOCALTIME_H +#include + +namespace toolkit { +void no_locks_localtime(struct tm *tmp, time_t t); +void local_time_init(); +int get_daylight_active(); + +} // namespace toolkit +#endif // UTIL_LOCALTIME_H diff --git a/ToolKit/Util/logger.cpp b/ToolKit/Util/logger.cpp new file mode 100644 index 0000000..fc82c83 --- /dev/null +++ b/ToolKit/Util/logger.cpp @@ -0,0 +1,654 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "logger.h" +#include "onceToken.h" +#include "File.h" +#include "NoticeCenter.h" + +#if defined(_WIN32) +#include "strptime_win.h" +#endif +#ifdef ANDROID +#include +#endif //ANDROID + +#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) +#include +#endif + +using namespace std; + +namespace toolkit { +#ifdef _WIN32 +#define CLEAR_COLOR 7 +static const WORD LOG_CONST_TABLE[][3] = { + {0x97, 0x09 , 'T'},//蓝底灰字,黑底蓝字,window console默认黑底 + {0xA7, 0x0A , 'D'},//绿底灰字,黑底绿字 + {0xB7, 0x0B , 'I'},//天蓝底灰字,黑底天蓝字 + {0xE7, 0x0E , 'W'},//黄底灰字,黑底黄字 + {0xC7, 0x0C , 'E'} };//红底灰字,黑底红字 + +bool SetConsoleColor(WORD Color) +{ + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (handle == 0) + return false; + + BOOL ret = SetConsoleTextAttribute(handle, Color); + return(ret == TRUE); +} +#else +#define CLEAR_COLOR "\033[0m" +static const char *LOG_CONST_TABLE[][3] = { + {"\033[44;37m", "\033[34m", "T"}, + {"\033[42;37m", "\033[32m", "D"}, + {"\033[46;37m", "\033[36m", "I"}, + {"\033[43;37m", "\033[33m", "W"}, + {"\033[41;37m", "\033[31m", "E"}}; +#endif + +Logger *g_defaultLogger = nullptr; + +Logger &getLogger() { + if (!g_defaultLogger) { + g_defaultLogger = &Logger::Instance(); + } + return *g_defaultLogger; +} + +void setLogger(Logger *logger) { + g_defaultLogger = logger; +} + +///////////////////Logger/////////////////// + +INSTANCE_IMP(Logger, exeName()) + +Logger::Logger(const string &loggerName) { + _logger_name = loggerName; + _last_log = std::make_shared(); + _default_channel = std::make_shared("default", LTrace); + +#if defined(_WIN32) + SetConsoleOutputCP(CP_UTF8); +#endif +} + +Logger::~Logger() { + _writer.reset(); + { + LogContextCapture(*this, LInfo, __FILE__, __FUNCTION__, __LINE__); + } + _channels.clear(); +} + +void Logger::add(const std::shared_ptr &channel) { + _channels[channel->name()] = channel; +} + +void Logger::del(const string &name) { + _channels.erase(name); +} + +std::shared_ptr Logger::get(const string &name) { + auto it = _channels.find(name); + if (it == _channels.end()) { + return nullptr; + } + return it->second; +} + +void Logger::setWriter(const std::shared_ptr &writer) { + _writer = writer; +} + +void Logger::write(const LogContextPtr &ctx) { + if (_writer) { + _writer->write(ctx, *this); + } else { + writeChannels(ctx); + } +} + +void Logger::setLevel(LogLevel level) { + for (auto &chn : _channels) { + chn.second->setLevel(level); + } +} + +void Logger::writeChannels_l(const LogContextPtr &ctx) { + if (_channels.empty()) { + _default_channel->write(*this, ctx); + } else { + for (auto &chn : _channels) { + chn.second->write(*this, ctx); + } + } + _last_log = ctx; + _last_log->_repeat = 0; +} + +//返回毫秒 +static int64_t timevalDiff(struct timeval &a, struct timeval &b) { + return (1000 * (b.tv_sec - a.tv_sec)) + ((b.tv_usec - a.tv_usec) / 1000); +} + +void Logger::writeChannels(const LogContextPtr &ctx) { + if (ctx->_line == _last_log->_line && ctx->_file == _last_log->_file && ctx->str() == _last_log->str() && ctx->_thread_name == _last_log->_thread_name) { + //重复的日志每隔500ms打印一次,过滤频繁的重复日志 + ++_last_log->_repeat; + if (timevalDiff(_last_log->_tv, ctx->_tv) > 500) { + ctx->_repeat = _last_log->_repeat; + writeChannels_l(ctx); + } + return; + } + if (_last_log->_repeat) { + writeChannels_l(_last_log); + } + writeChannels_l(ctx); +} + +const string &Logger::getName() const { + return _logger_name; +} + +///////////////////LogContext/////////////////// +static inline const char *getFileName(const char *file) { + auto pos = strrchr(file, '/'); +#ifdef _WIN32 + if(!pos){ + pos = strrchr(file, '\\'); + } +#endif + return pos ? pos + 1 : file; +} + +static inline const char *getFunctionName(const char *func) { +#ifndef _WIN32 + return func; +#else + auto pos = strrchr(func, ':'); + return pos ? pos + 1 : func; +#endif +} + +LogContext::LogContext(LogLevel level, const char *file, const char *function, int line, const char *module_name, const char *flag) + : _level(level), _line(line), _file(getFileName(file)), _function(getFunctionName(function)), + _module_name(module_name), _flag(flag) { + gettimeofday(&_tv, nullptr); + _thread_name = getThreadName(); +} + +const string &LogContext::str() { + if (_got_content) { + return _content; + } + _content = ostringstream::str(); + _got_content = true; + return _content; +} + +///////////////////AsyncLogWriter/////////////////// + +static string s_module_name = exeName(false); + +LogContextCapture::LogContextCapture(Logger &logger, LogLevel level, const char *file, const char *function, int line, const char *flag) : + _ctx(new LogContext(level, file, function, line, s_module_name.c_str() ? s_module_name.c_str() : "", flag)), _logger(logger) { +} + +LogContextCapture::LogContextCapture(const LogContextCapture &that) : _ctx(that._ctx), _logger(that._logger) { + const_cast(that._ctx).reset(); +} + +LogContextCapture::~LogContextCapture() { + *this << endl; +} + +LogContextCapture &LogContextCapture::operator<<(ostream &(*f)(ostream &)) { + if (!_ctx) { + return *this; + } + _logger.write(_ctx); + _ctx.reset(); + return *this; +} + +void LogContextCapture::clear() { + _ctx.reset(); +} + +///////////////////AsyncLogWriter/////////////////// + +AsyncLogWriter::AsyncLogWriter() : _exit_flag(false) { + _thread = std::make_shared([this]() { this->run(); }); +} + +AsyncLogWriter::~AsyncLogWriter() { + _exit_flag = true; + _sem.post(); + _thread->join(); + flushAll(); +} + +void AsyncLogWriter::write(const LogContextPtr &ctx, Logger &logger) { + { + lock_guard lock(_mutex); + _pending.emplace_back(std::make_pair(ctx, &logger)); + } + _sem.post(); +} + +void AsyncLogWriter::run() { + setThreadName("async log"); + while (!_exit_flag) { + _sem.wait(); + flushAll(); + } +} + +void AsyncLogWriter::flushAll() { + decltype(_pending) tmp; + { + lock_guard lock(_mutex); + tmp.swap(_pending); + } + + tmp.for_each([&](std::pair &pr) { + pr.second->writeChannels(pr.first); + }); +} + +///////////////////EventChannel//////////////////// + +const string EventChannel::kBroadcastLogEvent = "kBroadcastLogEvent"; + +EventChannel::EventChannel(const string &name, LogLevel level) : LogChannel(name, level) {} + +void EventChannel::write(const Logger &logger, const LogContextPtr &ctx) { + if (_level > ctx->_level) { + return; + } + NOTICE_EMIT(BroadcastLogEventArgs, kBroadcastLogEvent, logger, ctx); +} + +const std::string &EventChannel::getBroadcastLogEventName() { return kBroadcastLogEvent;} + +///////////////////ConsoleChannel/////////////////// + +ConsoleChannel::ConsoleChannel(const string &name, LogLevel level) : LogChannel(name, level) {} + +void ConsoleChannel::write(const Logger &logger, const LogContextPtr &ctx) { + if (_level > ctx->_level) { + return; + } + +#if defined(OS_IPHONE) + //ios禁用日志颜色 + format(logger, std::cout, ctx, false); +#elif defined(ANDROID) + static android_LogPriority LogPriorityArr[10]; + static onceToken s_token([](){ + LogPriorityArr[LTrace] = ANDROID_LOG_VERBOSE; + LogPriorityArr[LDebug] = ANDROID_LOG_DEBUG; + LogPriorityArr[LInfo] = ANDROID_LOG_INFO; + LogPriorityArr[LWarn] = ANDROID_LOG_WARN; + LogPriorityArr[LError] = ANDROID_LOG_ERROR; + }); + __android_log_print(LogPriorityArr[ctx->_level],"JNI","%s %s",ctx->_function.data(),ctx->str().data()); +#else + //linux/windows日志启用颜色并显示日志详情 + format(logger, std::cout, ctx); +#endif +} + +///////////////////SysLogChannel/////////////////// + +#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) + +SysLogChannel::SysLogChannel(const string &name, LogLevel level) : LogChannel(name, level) {} + +void SysLogChannel::write(const Logger &logger, const LogContextPtr &ctx) { + if (_level > ctx->_level) { + return; + } + static int s_syslog_lev[10]; + static onceToken s_token([]() { + s_syslog_lev[LTrace] = LOG_DEBUG; + s_syslog_lev[LDebug] = LOG_INFO; + s_syslog_lev[LInfo] = LOG_NOTICE; + s_syslog_lev[LWarn] = LOG_WARNING; + s_syslog_lev[LError] = LOG_ERR; + }, nullptr); + + syslog(s_syslog_lev[ctx->_level], "-> %s %d\r\n", ctx->_file.data(), ctx->_line); + syslog(s_syslog_lev[ctx->_level], "## %s %s | %s %s\r\n", printTime(ctx->_tv).data(), + LOG_CONST_TABLE[ctx->_level][2], ctx->_function.data(), ctx->str().data()); +} + +#endif//#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) + +///////////////////LogChannel/////////////////// +LogChannel::LogChannel(const string &name, LogLevel level) : _name(name), _level(level) {} + +LogChannel::~LogChannel() {} + +const string &LogChannel::name() const { return _name; } + +void LogChannel::setLevel(LogLevel level) { _level = level; } + +std::string LogChannel::printTime(const timeval &tv) { + auto tm = getLocalTime(tv.tv_sec); + char buf[128]; + snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d.%03d", + 1900 + tm.tm_year, + 1 + tm.tm_mon, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + (int) (tv.tv_usec / 1000)); + return buf; +} + +#ifdef _WIN32 +#define printf_pid() GetCurrentProcessId() +#else +#define printf_pid() getpid() +#endif + +void LogChannel::format(const Logger &logger, ostream &ost, const LogContextPtr &ctx, bool enable_color, bool enable_detail) { + if (!enable_detail && ctx->str().empty()) { + // 没有任何信息打印 + return; + } + + if (enable_color) { + // color console start +#ifdef _WIN32 + SetConsoleColor(LOG_CONST_TABLE[ctx->_level][1]); +#else + ost << LOG_CONST_TABLE[ctx->_level][1]; +#endif + } + + // print log time and level +#ifdef _WIN32 + ost << printTime(ctx->_tv) << " " << (char)LOG_CONST_TABLE[ctx->_level][2] << " "; +#else + ost << printTime(ctx->_tv) << " " << LOG_CONST_TABLE[ctx->_level][2] << " "; +#endif + + if (enable_detail) { + // tag or process name + ost << "[" << (!ctx->_flag.empty() ? ctx->_flag : logger.getName()) << "] "; + // pid and thread_name + ost << "[" << printf_pid() << "-" << ctx->_thread_name << "] "; + // source file location + ost << ctx->_file << ":" << ctx->_line << " " << ctx->_function << " | "; + } + + // log content + ost << ctx->str(); + + if (enable_color) { + // color console end +#ifdef _WIN32 + SetConsoleColor(CLEAR_COLOR); +#else + ost << CLEAR_COLOR; +#endif + } + + if (ctx->_repeat > 1) { + // log repeated + ost << "\r\n Last message repeated " << ctx->_repeat << " times"; + } + + // flush log and new line + ost << endl; +} + +///////////////////FileChannelBase/////////////////// + +FileChannelBase::FileChannelBase(const string &name, const string &path, LogLevel level) : LogChannel(name, level), _path(path) {} + +FileChannelBase::~FileChannelBase() { + close(); +} + +void FileChannelBase::write(const Logger &logger, const std::shared_ptr &ctx) { + if (_level > ctx->_level) { + return; + } + if (!_fstream.is_open()) { + open(); + } + //打印至文件,不启用颜色 + format(logger, _fstream, ctx, false); +} + +bool FileChannelBase::setPath(const string &path) { + _path = path; + return open(); +} + +const string &FileChannelBase::path() const { + return _path; +} + +bool FileChannelBase::open() { + // Ensure a path was set + if (_path.empty()) { + throw runtime_error("Log file path must be set"); + } + // Open the file stream + _fstream.close(); +#if !defined(_WIN32) + //创建文件夹 + File::create_path(_path, S_IRWXO | S_IRWXG | S_IRWXU); +#else + File::create_path(_path,0); +#endif + _fstream.open(_path.data(), ios::out | ios::app); + if (!_fstream.is_open()) { + return false; + } + //打开文件成功 + return true; +} + +void FileChannelBase::close() { + _fstream.close(); +} + +size_t FileChannelBase::size() { + return (_fstream << std::flush).tellp(); +} + +///////////////////FileChannel/////////////////// + +static const auto s_second_per_day = 24 * 60 * 60; + +//根据GMT UNIX时间戳生产日志文件名 +static string getLogFilePath(const string &dir, time_t second, int32_t index) { + auto tm = getLocalTime(second); + char buf[64]; + snprintf(buf, sizeof(buf), "%d-%02d-%02d_%02d.log", 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday, index); + return dir + buf; +} + +//根据日志文件名返回GMT UNIX时间戳 +static time_t getLogFileTime(const string &full_path) { + auto name = getFileName(full_path.data()); + struct tm tm{0}; + if (!strptime(name, "%Y-%m-%d", &tm)) { + return 0; + } + //此函数会把本地时间转换成GMT时间戳 + return mktime(&tm); +} + +//获取1970年以来的第几天 +static uint64_t getDay(time_t second) { + return (second + getGMTOff()) / s_second_per_day; +} + +FileChannel::FileChannel(const string &name, const string &dir, LogLevel level) : FileChannelBase(name, "", level) { + _dir = dir; + if (_dir.back() != '/') { + _dir.append("/"); + } + + //收集所有日志文件 + File::scanDir(_dir, [this](const string &path, bool isDir) -> bool { + if (!isDir && end_with(path, ".log")) { + _log_file_map.emplace(path); + } + return true; + }, false); + + //获取今天日志文件的最大index号 + auto log_name_prefix = getTimeStr("%Y-%m-%d_"); + for (auto it = _log_file_map.begin(); it != _log_file_map.end(); ++it) { + auto name = getFileName(it->data()); + //筛选出今天所有的日志文件 + if (start_with(name, log_name_prefix)) { + int tm_mday; // day of the month - [1, 31] + int tm_mon; // months since January - [0, 11] + int tm_year; // years since 1900 + uint32_t index; + //今天第几个文件 + int count = sscanf(name, "%d-%02d-%02d_%d.log", &tm_year, &tm_mon, &tm_mday, &index); + if (count == 4) { + _index = index >= _index ? index : _index; + } + } + } +} + +void FileChannel::write(const Logger &logger, const LogContextPtr &ctx) { + //GMT UNIX时间戳 + time_t second = ctx->_tv.tv_sec; + //这条日志所在第几天 + auto day = getDay(second); + if ((int64_t) day != _last_day) { + if (_last_day != -1) { + //重置日志index + _index = 0; + } + //这条日志是新的一天,记录这一天 + _last_day = day; + //获取日志当天对应的文件,每天可能有多个日志切片文件 + changeFile(second); + } else { + //检查该天日志是否需要重新分片 + checkSize(second); + } + + //写日志 + if (_can_write) { + FileChannelBase::write(logger, ctx); + } +} + +void FileChannel::clean() { + //获取今天是第几天 + auto today = getDay(time(nullptr)); + //遍历所有日志文件,删除超过若干天前的过期日志文件 + for (auto it = _log_file_map.begin(); it != _log_file_map.end();) { + auto day = getDay(getLogFileTime(it->data())); + if (today < day + _log_max_day) { + //这个日志文件距今尚未超过一定天数,后面的文件更新,所以停止遍历 + break; + } + //这个文件距今超过了一定天数,则删除文件 + File::delete_file(*it); + //删除这条记录 + it = _log_file_map.erase(it); + } + + //按文件个数清理,限制最大文件切片个数 + while (_log_file_map.size() > _log_max_count) { + auto it = _log_file_map.begin(); + if (*it == path()) { + //当前文件,停止删除 + break; + } + //删除文件 + File::delete_file(*it); + //删除这条记录 + _log_file_map.erase(it); + } +} + +void FileChannel::checkSize(time_t second) { + //每60秒检查一下文件大小,防止频繁flush日志文件 + if (second - _last_check_time > 60) { + if (FileChannelBase::size() > _log_max_size * 1024 * 1024) { + changeFile(second); + } + _last_check_time = second; + } +} + +void FileChannel::changeFile(time_t second) { + auto log_file = getLogFilePath(_dir, second, _index++); + //记录所有的日志文件,以便后续删除老的日志 + _log_file_map.emplace(log_file); + //打开新的日志文件 + _can_write = setPath(log_file); + if (!_can_write) { + ErrorL << "Failed to open log file: " << _path; + } + //尝试删除过期的日志文件 + clean(); +} + +void FileChannel::setMaxDay(size_t max_day) { + _log_max_day = max_day > 1 ? max_day : 1; +} + +void FileChannel::setFileMaxSize(size_t max_size) { + _log_max_size = max_size > 1 ? max_size : 1; +} + +void FileChannel::setFileMaxCount(size_t max_count) { + _log_max_count = max_count > 1 ? max_count : 1; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +void LoggerWrapper::printLogV(Logger &logger, int level, const char *file, const char *function, int line, const char *fmt, va_list ap) { + LogContextCapture info(logger, (LogLevel) level, file, function, line); + char *str = nullptr; + if (vasprintf(&str, fmt, ap) >= 0 && str) { + info << str; +#ifdef ASAN_USE_DELETE + delete [] str; // 开启asan后,用free会卡死 +#else + free(str); +#endif + } +} + +void LoggerWrapper::printLog(Logger &logger, int level, const char *file, const char *function, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + printLogV(logger, level, file, function, line, fmt, ap); + va_end(ap); +} + +} /* namespace toolkit */ + diff --git a/ToolKit/Util/logger.h b/ToolKit/Util/logger.h new file mode 100644 index 0000000..b886735 --- /dev/null +++ b/ToolKit/Util/logger.h @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_LOGGER_H_ +#define UTIL_LOGGER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "List.h" +#include "Thread/semaphore.h" + +namespace toolkit { + +class LogContext; +class LogChannel; +class LogWriter; +class Logger; + +using LogContextPtr = std::shared_ptr; + +typedef enum { + LTrace = 0, LDebug, LInfo, LWarn, LError +} LogLevel; + +Logger &getLogger(); +void setLogger(Logger *logger); + +/** +* 日志类 +*/ +class Logger : public std::enable_shared_from_this, public noncopyable { +public: + friend class AsyncLogWriter; + using Ptr = std::shared_ptr; + + /** + * 获取日志单例 + * @return + */ + static Logger &Instance(); + + explicit Logger(const std::string &loggerName); + ~Logger(); + + /** + * 添加日志通道,非线程安全的 + * @param channel log通道 + */ + void add(const std::shared_ptr &channel); + + /** + * 删除日志通道,非线程安全的 + * @param name log通道名 + */ + void del(const std::string &name); + + /** + * 获取日志通道,非线程安全的 + * @param name log通道名 + * @return 线程通道 + */ + std::shared_ptr get(const std::string &name); + + /** + * 设置写log器,非线程安全的 + * @param writer 写log器 + */ + void setWriter(const std::shared_ptr &writer); + + /** + * 设置所有日志通道的log等级 + * @param level log等级 + */ + void setLevel(LogLevel level); + + /** + * 获取logger名 + * @return logger名 + */ + const std::string &getName() const; + + /** + * 写日志 + * @param ctx 日志信息 + */ + void write(const LogContextPtr &ctx); + +private: + /** + * 写日志到各channel,仅供AsyncLogWriter调用 + * @param ctx 日志信息 + */ + void writeChannels(const LogContextPtr &ctx); + void writeChannels_l(const LogContextPtr &ctx); + +private: + LogContextPtr _last_log; + std::string _logger_name; + std::shared_ptr _writer; + std::shared_ptr _default_channel; + std::map > _channels; +}; + +///////////////////LogContext/////////////////// +/** +* 日志上下文 +*/ +class LogContext : public std::ostringstream { +public: + //_file,_function改成string保存,目的是有些情况下,指针可能会失效 + //比如说动态库中打印了一条日志,然后动态库卸载了,那么指向静态数据区的指针就会失效 + LogContext() = default; + LogContext(LogLevel level, const char *file, const char *function, int line, const char *module_name, const char *flag); + ~LogContext() = default; + + LogLevel _level; + int _line; + int _repeat = 0; + std::string _file; + std::string _function; + std::string _thread_name; + std::string _module_name; + std::string _flag; + struct timeval _tv; + + const std::string &str(); + +private: + bool _got_content = false; + std::string _content; +}; + +/** + * 日志上下文捕获器 + */ +class LogContextCapture { +public: + using Ptr = std::shared_ptr; + + LogContextCapture(Logger &logger, LogLevel level, const char *file, const char *function, int line, const char *flag = ""); + LogContextCapture(const LogContextCapture &that); + ~LogContextCapture(); + + /** + * 输入std::endl(回车符)立即输出日志 + * @param f std::endl(回车符) + * @return 自身引用 + */ + LogContextCapture &operator<<(std::ostream &(*f)(std::ostream &)); + + template + LogContextCapture &operator<<(T &&data) { + if (!_ctx) { + return *this; + } + (*_ctx) << std::forward(data); + return *this; + } + + void clear(); + +private: + LogContextPtr _ctx; + Logger &_logger; +}; + + +///////////////////LogWriter/////////////////// +/** + * 写日志器 + */ +class LogWriter : public noncopyable { +public: + LogWriter() = default; + virtual ~LogWriter() = default; + + virtual void write(const LogContextPtr &ctx, Logger &logger) = 0; +}; + +class AsyncLogWriter : public LogWriter { +public: + AsyncLogWriter(); + ~AsyncLogWriter(); + +private: + void run(); + void flushAll(); + void write(const LogContextPtr &ctx, Logger &logger) override; + +private: + bool _exit_flag; + semaphore _sem; + std::mutex _mutex; + std::shared_ptr _thread; + List > _pending; +}; + +///////////////////LogChannel/////////////////// +/** + * 日志通道 + */ +class LogChannel : public noncopyable { +public: + LogChannel(const std::string &name, LogLevel level = LTrace); + virtual ~LogChannel(); + + virtual void write(const Logger &logger, const LogContextPtr &ctx) = 0; + const std::string &name() const; + void setLevel(LogLevel level); + static std::string printTime(const timeval &tv); + +protected: + /** + * 打印日志至输出流 + * @param ost 输出流 + * @param enable_color 是否启用颜色 + * @param enable_detail 是否打印细节(函数名、源码文件名、源码行) + */ + virtual void format(const Logger &logger, std::ostream &ost, const LogContextPtr &ctx, bool enable_color = true, bool enable_detail = true); + +protected: + std::string _name; + LogLevel _level; +}; + +/** + * 输出日至到广播 + */ +class EventChannel : public LogChannel { +public: + //输出日志时的广播名 + static const std::string kBroadcastLogEvent; + //toolkit目前仅只有一处全局变量被外部引用,减少导出相关定义,导出以下函数避免导出kBroadcastLogEvent + static const std::string& getBroadcastLogEventName(); + //日志广播参数类型和列表 + #define BroadcastLogEventArgs const Logger &logger, const LogContextPtr &ctx + + EventChannel(const std::string &name = "EventChannel", LogLevel level = LTrace); + ~EventChannel() override = default; + + void write(const Logger &logger, const LogContextPtr &ctx) override; +}; + +/** + * 输出日志至终端,支持输出日志至android logcat + */ +class ConsoleChannel : public LogChannel { +public: + ConsoleChannel(const std::string &name = "ConsoleChannel", LogLevel level = LTrace); + ~ConsoleChannel() override = default; + + void write(const Logger &logger, const LogContextPtr &logContext) override; +}; + +/** + * 输出日志至文件 + */ +class FileChannelBase : public LogChannel { +public: + FileChannelBase(const std::string &name = "FileChannelBase", const std::string &path = exePath() + ".log", LogLevel level = LTrace); + ~FileChannelBase() override; + + void write(const Logger &logger, const LogContextPtr &ctx) override; + bool setPath(const std::string &path); + const std::string &path() const; + +protected: + virtual bool open(); + virtual void close(); + virtual size_t size(); + +protected: + std::string _path; + std::ofstream _fstream; +}; + +class Ticker; + +/** + * 自动清理的日志文件通道 + * 默认最多保存30天的日志 + */ +class FileChannel : public FileChannelBase { +public: + FileChannel(const std::string &name = "FileChannel", const std::string &dir = exeDir() + "log/", LogLevel level = LTrace); + ~FileChannel() override = default; + + /** + * 写日志时才会触发新建日志文件或者删除老的日志文件 + * @param logger + * @param stream + */ + void write(const Logger &logger, const LogContextPtr &ctx) override; + + /** + * 设置日志最大保存天数 + * @param max_day 天数 + */ + void setMaxDay(size_t max_day); + + /** + * 设置日志切片文件最大大小 + * @param max_size 单位MB + */ + void setFileMaxSize(size_t max_size); + + /** + * 设置日志切片文件最大个数 + * @param max_count 个数 + */ + void setFileMaxCount(size_t max_count); + +private: + /** + * 删除日志切片文件,条件为超过最大保存天数与最大切片个数 + */ + void clean(); + + /** + * 检查当前日志切片文件大小,如果超过限制,则创建新的日志切片文件 + */ + void checkSize(time_t second); + + /** + * 创建并切换到下一个日志切片文件 + */ + void changeFile(time_t second); + +private: + bool _can_write = false; + //默认最多保存30天的日志文件 + size_t _log_max_day = 30; + //每个日志切片文件最大默认128MB + size_t _log_max_size = 128; + //最多默认保持30个日志切片文件 + size_t _log_max_count = 30; + //当前日志切片文件索引 + size_t _index = 0; + int64_t _last_day = -1; + time_t _last_check_time = 0; + std::string _dir; + std::set _log_file_map; +}; + +#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) +class SysLogChannel : public LogChannel { +public: + SysLogChannel(const std::string &name = "SysLogChannel", LogLevel level = LTrace); + ~SysLogChannel() override = default; + + void write(const Logger &logger, const LogContextPtr &logContext) override; +}; + +#endif//#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) + +class BaseLogFlagInterface { +protected: + virtual ~BaseLogFlagInterface() {} + // 获得日志标记Flag + const char* getLogFlag(){ + return _log_flag; + } + void setLogFlag(const char *flag) { _log_flag = flag; } +private: + const char *_log_flag = ""; +}; + +class LoggerWrapper { +public: + template + static inline void printLogArray(Logger &logger, LogLevel level, const char *file, const char *function, int line, First &&first, ARGS &&...args) { + LogContextCapture log(logger, level, file, function, line); + log << std::forward(first); + appendLog(log, std::forward(args)...); + } + + static inline void printLogArray(Logger &logger, LogLevel level, const char *file, const char *function, int line) { + LogContextCapture log(logger, level, file, function, line); + } + + template + static inline void appendLog(Log &out, First &&first, ARGS &&...args) { + out << std::forward(first); + appendLog(out, std::forward(args)...); + } + + template + static inline void appendLog(Log &out) {} + + //printf样式的日志打印 + static void printLog(Logger &logger, int level, const char *file, const char *function, int line, const char *fmt, ...); + static void printLogV(Logger &logger, int level, const char *file, const char *function, int line, const char *fmt, va_list ap); +}; + +//可重置默认值 +extern Logger *g_defaultLogger; + +//用法: DebugL << 1 << "+" << 2 << '=' << 3; +#define WriteL(level) ::toolkit::LogContextCapture(::toolkit::getLogger(), level, __FILE__, __FUNCTION__, __LINE__) +#define TraceL WriteL(::toolkit::LTrace) +#define DebugL WriteL(::toolkit::LDebug) +#define InfoL WriteL(::toolkit::LInfo) +#define WarnL WriteL(::toolkit::LWarn) +#define ErrorL WriteL(::toolkit::LError) + +//只能在虚继承BaseLogFlagInterface的类中使用 +#define WriteF(level) ::toolkit::LogContextCapture(::toolkit::getLogger(), level, __FILE__, __FUNCTION__, __LINE__, getLogFlag()) +#define TraceF WriteF(::toolkit::LTrace) +#define DebugF WriteF(::toolkit::LDebug) +#define InfoF WriteF(::toolkit::LInfo) +#define WarnF WriteF(::toolkit::LWarn) +#define ErrorF WriteF(::toolkit::LError) + +//用法: PrintD("%d + %s = %c", 1 "2", 'c'); +#define PrintLog(level, ...) ::toolkit::LoggerWrapper::printLog(::toolkit::getLogger(), level, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define PrintT(...) PrintLog(::toolkit::LTrace, ##__VA_ARGS__) +#define PrintD(...) PrintLog(::toolkit::LDebug, ##__VA_ARGS__) +#define PrintI(...) PrintLog(::toolkit::LInfo, ##__VA_ARGS__) +#define PrintW(...) PrintLog(::toolkit::LWarn, ##__VA_ARGS__) +#define PrintE(...) PrintLog(::toolkit::LError, ##__VA_ARGS__) + +//用法: LogD(1, "+", "2", '=', 3); +//用于模板实例化的原因,如果每次打印参数个数和类型不一致,可能会导致二进制代码膨胀 +#define LogL(level, ...) ::toolkit::LoggerWrapper::printLogArray(::toolkit::getLogger(), (LogLevel)level, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define LogT(...) LogL(::toolkit::LTrace, ##__VA_ARGS__) +#define LogD(...) LogL(::toolkit::LDebug, ##__VA_ARGS__) +#define LogI(...) LogL(::toolkit::LInfo, ##__VA_ARGS__) +#define LogW(...) LogL(::toolkit::LWarn, ##__VA_ARGS__) +#define LogE(...) LogL(::toolkit::LError, ##__VA_ARGS__) + +} /* namespace toolkit */ +#endif /* UTIL_LOGGER_H_ */ diff --git a/ToolKit/Util/mini.cpp b/ToolKit/Util/mini.cpp new file mode 100644 index 0000000..da94296 --- /dev/null +++ b/ToolKit/Util/mini.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "mini.h" + +using namespace std; + +namespace toolkit { + +template<> +mINI_basic &mINI_basic::Instance(){ + static mINI_basic instance; + return instance; +} + +template <> +bool variant::as() const { + if (empty() || isdigit(front())) { + //数字开头 + return as_default(); + } + if (strToLower(std::string(*this)) == "true") { + return true; + } + if (strToLower(std::string(*this)) == "false") { + return false; + } + //未识别字符串 + return as_default(); +} + +template<> +uint8_t variant::as() const { + return 0xFF & as_default(); +} + +} // namespace toolkit + + diff --git a/ToolKit/Util/mini.h b/ToolKit/Util/mini.h new file mode 100644 index 0000000..6a67fc2 --- /dev/null +++ b/ToolKit/Util/mini.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ +#ifndef UTIL_MINI_H +#define UTIL_MINI_H + +#include +#include +#include +#include +#include +#include "util.h" + +namespace toolkit { + +template +class mINI_basic : public std::map { + // Public API : existing map<> interface plus following methods +public: + void parse(const std::string &text) { + // reset, split lines and parse + std::vector lines = tokenize(text, "\n"); + std::string symbol, tag; + for (auto &line : lines) { + // trim blanks + line = trim(line); + // split line into tokens and parse tokens + if (line.empty() || line.front() == ';' || line.front() == '#') { + continue; + } + if (line.size() >= 3 && line.front() == '[' && line.back() == ']') { + tag = trim(line.substr(1, line.size() - 2)); + } else { + auto at = line.find('='); + symbol = trim(tag + "." + line.substr(0, at)); + (*this)[symbol] = (at == std::string::npos ? std::string() : trim(line.substr(at + 1))); + } + } + } + + void parseFile(const std::string &fileName = exePath() + ".ini") { + std::ifstream in(fileName, std::ios::in | std::ios::binary | std::ios::ate); + if (!in.good()) { + throw std::invalid_argument("Invalid ini file: " + fileName); + } + auto size = in.tellg(); + in.seekg(0, std::ios::beg); + std::string buf; + buf.resize(size); + in.read((char *) buf.data(), size); + parse(buf); + } + + std::string dump(const std::string &header = "; auto-generated by mINI class {", + const std::string &footer = "; } ---") const { + std::string front(header + (header.empty() ? "" : "\r\n")), output, tag; + std::vector kv; + for (auto &pr : *this) { + auto pos = pr.first.find('.'); + if (pos == std::string::npos) { + kv = {"", pr.first}; + } else { + kv = {pr.first.substr(0, pos), pr.first.substr(pos + 1)}; + } + if (kv[0].empty()) { + front += kv[1] + "=" + pr.second + "\r\n"; + continue; + } + if (tag != kv[0]) { + output += "\r\n[" + (tag = kv[0]) + "]\r\n"; + } + output += kv[1] + "=" + pr.second + "\r\n"; + } + return front + output + "\r\n" + footer + (footer.empty() ? "" : "\r\n"); + } + + void dumpFile(const std::string &fileName = exePath() + ".ini") { + std::ofstream out(fileName, std::ios::out | std::ios::binary | std::ios::trunc); + auto dmp = dump(); + out.write(dmp.data(), dmp.size()); + } + + static mINI_basic &Instance(); + +private: + std::vector tokenize(const std::string &self, const std::string &chars) const { + std::vector tokens(1); + std::string map(256, '\0'); + for (char ch : chars) { + map[(uint8_t) ch] = '\1'; + } + for (char ch : self) { + if (!map.at((uint8_t) ch)) { + tokens.back().push_back(ch); + } else if (tokens.back().size()) { + tokens.push_back(std::string()); + } + } + while (tokens.size() && tokens.back().empty()) { + tokens.pop_back(); + } + return tokens; + } +}; + +// handy variant class as key/values +struct variant : public std::string { + template + variant(const T &t) : + std::string(std::to_string(t)) { + } + + template + variant(const char (&s)[N]) : + std::string(s, N) { + } + + variant(const char *cstr) : + std::string(cstr) { + } + + variant(const std::string &other = std::string()) : + std::string(other) { + } + + template + operator T() const { + return as(); + } + + template + bool operator==(const T &t) const { + return 0 == this->compare(variant(t)); + } + + bool operator==(const char *t) const { + return this->compare(t) == 0; + } + + template + typename std::enable_if::value, T>::type as() const { + return as_default(); + } + + template + typename std::enable_if::value, T>::type as() const { + return T((const std::string &)*this); + } + +private: + template + T as_default() const { + T t; + std::stringstream ss; + return ss << *this && ss >> t ? t : T(); + } +}; + +template <> +bool variant::as() const; + +template <> +uint8_t variant::as() const; + +using mINI = mINI_basic; + +} // namespace toolkit +#endif //UTIL_MINI_H + diff --git a/ToolKit/Util/onceToken.h b/ToolKit/Util/onceToken.h new file mode 100644 index 0000000..7a91ca1 --- /dev/null +++ b/ToolKit/Util/onceToken.h @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. +* +* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). +* +* Use of this source code is governed by MIT license that can be found in the +* LICENSE file in the root of the source tree. All contributing project authors +* may be found in the AUTHORS file in the root of the source tree. +*/ + +#ifndef UTIL_ONCETOKEN_H_ +#define UTIL_ONCETOKEN_H_ + +#include +#include + +namespace toolkit { + +class onceToken { +public: + using task = std::function; + + template + onceToken(const FUNC &onConstructed, task onDestructed = nullptr) { + onConstructed(); + _onDestructed = std::move(onDestructed); + } + + onceToken(std::nullptr_t, task onDestructed = nullptr) { + _onDestructed = std::move(onDestructed); + } + + ~onceToken() { + if (_onDestructed) { + _onDestructed(); + } + } + +private: + onceToken() = delete; + onceToken(const onceToken &) = delete; + onceToken(onceToken &&) = delete; + onceToken &operator=(const onceToken &) = delete; + onceToken &operator=(onceToken &&) = delete; + +private: + task _onDestructed; +}; + +} /* namespace toolkit */ +#endif /* UTIL_ONCETOKEN_H_ */ diff --git a/ToolKit/Util/util.cpp b/ToolKit/Util/util.cpp new file mode 100644 index 0000000..43396b4 --- /dev/null +++ b/ToolKit/Util/util.cpp @@ -0,0 +1,666 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "local_time.h" +#include "File.h" +#include "onceToken.h" +#include "logger.h" +#include "uv_errno.h" +#include "Network/sockutil.h" + +#if defined(_WIN32) +#include +#include +#include +#include +#pragma comment(lib, "shlwapi.lib") +extern "C" const IMAGE_DOS_HEADER __ImageBase; +#endif // defined(_WIN32) + +#if defined(__MACH__) || defined(__APPLE__) +#include +#include /* _NSGetExecutablePath */ + +int uv_exepath(char *buffer, int *size) { + /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */ + char abspath[PATH_MAX * 2 + 1]; + char exepath[PATH_MAX + 1]; + uint32_t exepath_size; + size_t abspath_size; + + if (buffer == nullptr || size == nullptr || *size == 0) + return -EINVAL; + + exepath_size = sizeof(exepath); + if (_NSGetExecutablePath(exepath, &exepath_size)) + return -EIO; + + if (realpath(exepath, abspath) != abspath) + return -errno; + + abspath_size = strlen(abspath); + if (abspath_size == 0) + return -EIO; + + *size -= 1; + if ((size_t) *size > abspath_size) + *size = abspath_size; + + memcpy(buffer, abspath, *size); + buffer[*size] = '\0'; + + return 0; +} + +#endif //defined(__MACH__) || defined(__APPLE__) + +using namespace std; + +namespace toolkit { + +static constexpr char CCH[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +string makeRandStr(int sz, bool printable) { + string ret; + ret.resize(sz); + std::mt19937 rng(std::random_device{}()); + for (int i = 0; i < sz; ++i) { + if (printable) { + uint32_t x = rng() % (sizeof(CCH) - 1); + ret[i] = CCH[x]; + } else { + ret[i] = rng() % 0xFF; + } + } + return ret; +} + +bool is_safe(uint8_t b) { + return b >= ' ' && b < 128; +} + +string hexdump(const void *buf, size_t len) { + string ret("\r\n"); + char tmp[8]; + const uint8_t *data = (const uint8_t *) buf; + for (size_t i = 0; i < len; i += 16) { + for (int j = 0; j < 16; ++j) { + if (i + j < len) { + int sz = snprintf(tmp, sizeof(tmp), "%.2x ", data[i + j]); + ret.append(tmp, sz); + } else { + int sz = snprintf(tmp, sizeof(tmp), " "); + ret.append(tmp, sz); + } + } + for (int j = 0; j < 16; ++j) { + if (i + j < len) { + ret += (is_safe(data[i + j]) ? data[i + j] : '.'); + } else { + ret += (' '); + } + } + ret += ('\n'); + } + return ret; +} + +string hexmem(const void *buf, size_t len) { + string ret; + char tmp[8]; + const uint8_t *data = (const uint8_t *) buf; + for (size_t i = 0; i < len; ++i) { + int sz = sprintf(tmp, "%.2x ", data[i]); + ret.append(tmp, sz); + } + return ret; +} + +string exePath(bool isExe /*= true*/) { + char buffer[PATH_MAX * 2 + 1] = {0}; + int n = -1; +#if defined(_WIN32) + n = GetModuleFileNameA(isExe?nullptr:(HINSTANCE)&__ImageBase, buffer, sizeof(buffer)); +#elif defined(__MACH__) || defined(__APPLE__) + n = sizeof(buffer); + if (uv_exepath(buffer, &n) != 0) { + n = -1; + } +#elif defined(__linux__) + n = readlink("/proc/self/exe", buffer, sizeof(buffer)); +#endif + + string filePath; + if (n <= 0) { + filePath = "./"; + } else { + filePath = buffer; + } + +#if defined(_WIN32) + //windows下把路径统一转换层unix风格,因为后续都是按照unix风格处理的 + for (auto &ch : filePath) { + if (ch == '\\') { + ch = '/'; + } + } +#endif //defined(_WIN32) + + return filePath; +} + +string exeDir(bool isExe /*= true*/) { + auto path = exePath(isExe); + return path.substr(0, path.rfind('/') + 1); +} + +string exeName(bool isExe /*= true*/) { + auto path = exePath(isExe); + return path.substr(path.rfind('/') + 1); +} + +// string转小写 +std::string &strToLower(std::string &str) { + transform(str.begin(), str.end(), str.begin(), towlower); + return str; +} + +// string转大写 +std::string &strToUpper(std::string &str) { + transform(str.begin(), str.end(), str.begin(), towupper); + return str; +} + +// string转小写 +std::string strToLower(std::string &&str) { + transform(str.begin(), str.end(), str.begin(), towlower); + return std::move(str); +} + +// string转大写 +std::string strToUpper(std::string &&str) { + transform(str.begin(), str.end(), str.begin(), towupper); + return std::move(str); +} + +vector split(const string &s, const char *delim) { + vector ret; + size_t last = 0; + auto index = s.find(delim, last); + while (index != string::npos) { + if (index - last > 0) { + ret.push_back(s.substr(last, index - last)); + } + last = index + strlen(delim); + index = s.find(delim, last); + } + if (!s.size() || s.size() - last > 0) { + ret.push_back(s.substr(last)); + } + return ret; +} + +#define TRIM(s, chars) \ +do{ \ + string map(0xFF, '\0'); \ + for (auto &ch : chars) { \ + map[(unsigned char &)ch] = '\1'; \ + } \ + while( s.size() && map.at((unsigned char &)s.back())) s.pop_back(); \ + while( s.size() && map.at((unsigned char &)s.front())) s.erase(0,1); \ +}while(0); + +//去除前后的空格、回车符、制表符 +std::string &trim(std::string &s, const string &chars) { + TRIM(s, chars); + return s; +} + +std::string trim(std::string &&s, const string &chars) { + TRIM(s, chars); + return std::move(s); +} + +void replace(string &str, const string &old_str, const string &new_str,std::string::size_type b_pos) { + if (old_str.empty() || old_str == new_str) { + return; + } + auto pos = str.find(old_str,b_pos); + if (pos == string::npos) { + return; + } + str.replace(pos, old_str.size(), new_str); + replace(str, old_str, new_str,pos + new_str.length()); +} + +bool start_with(const string &str, const string &substr) { + return str.find(substr) == 0; +} + +bool end_with(const string &str, const string &substr) { + auto pos = str.rfind(substr); + return pos != string::npos && pos == str.size() - substr.size(); +} + +bool isIP(const char *str) { + return SockUtil::is_ipv4(str) || SockUtil::is_ipv6(str); +} + +#if defined(_WIN32) +void sleep(int second) { + Sleep(1000 * second); +} +void usleep(int micro_seconds) { + this_thread::sleep_for(std::chrono::microseconds(micro_seconds)); +} + +int gettimeofday(struct timeval *tp, void *tzp) { + auto now_stamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + tp->tv_sec = (decltype(tp->tv_sec))(now_stamp / 1000000LL); + tp->tv_usec = now_stamp % 1000000LL; + return 0; +} + +const char *strcasestr(const char *big, const char *little){ + string big_str = big; + string little_str = little; + strToLower(big_str); + strToLower(little_str); + auto pos = strstr(big_str.data(), little_str.data()); + if (!pos){ + return nullptr; + } + return big + (pos - big_str.data()); +} + +int vasprintf(char **strp, const char *fmt, va_list ap) { + // _vscprintf tells you how big the buffer needs to be + int len = _vscprintf(fmt, ap); + if (len == -1) { + return -1; + } + size_t size = (size_t)len + 1; + char *str = (char*)malloc(size); + if (!str) { + return -1; + } + // _vsprintf_s is the "secure" version of vsprintf + int r = vsprintf_s(str, len + 1, fmt, ap); + if (r == -1) { + free(str); + return -1; + } + *strp = str; + return r; +} + + int asprintf(char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vasprintf(strp, fmt, ap); + va_end(ap); + return r; +} + +#endif //WIN32 + +static long s_gmtoff = 0; //时间差 +static onceToken s_token([]() { +#ifdef _WIN32 + TIME_ZONE_INFORMATION tzinfo; + DWORD dwStandardDaylight; + long bias; + dwStandardDaylight = GetTimeZoneInformation(&tzinfo); + bias = tzinfo.Bias; + if (dwStandardDaylight == TIME_ZONE_ID_STANDARD) { + bias += tzinfo.StandardBias; + } + if (dwStandardDaylight == TIME_ZONE_ID_DAYLIGHT) { + bias += tzinfo.DaylightBias; + } + s_gmtoff = -bias * 60; //时间差(分钟) +#else + local_time_init(); + s_gmtoff = getLocalTime(time(nullptr)).tm_gmtoff; +#endif // _WIN32 +}); + +long getGMTOff() { + return s_gmtoff; +} + +static inline uint64_t getCurrentMicrosecondOrigin() { +#if !defined(_WIN32) + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec * 1000000LL + tv.tv_usec; +#else + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +#endif +} + +static atomic s_currentMicrosecond(0); +static atomic s_currentMillisecond(0); +static atomic s_currentMicrosecond_system(getCurrentMicrosecondOrigin()); +static atomic s_currentMillisecond_system(getCurrentMicrosecondOrigin() / 1000); + +static inline bool initMillisecondThread() { + static std::thread s_thread([]() { + setThreadName("stamp thread"); + DebugL << "Stamp thread started"; + uint64_t last = getCurrentMicrosecondOrigin(); + uint64_t now; + uint64_t microsecond = 0; + while (true) { + now = getCurrentMicrosecondOrigin(); + //记录系统时间戳,可回退 + s_currentMicrosecond_system.store(now, memory_order_release); + s_currentMillisecond_system.store(now / 1000, memory_order_release); + + //记录流逝时间戳,不可回退 + int64_t expired = now - last; + last = now; + if (expired > 0 && expired < 1000 * 1000) { + //流逝时间处于0~1000ms之间,那么是合理的,说明没有调整系统时间 + microsecond += expired; + s_currentMicrosecond.store(microsecond, memory_order_release); + s_currentMillisecond.store(microsecond / 1000, memory_order_release); + } else if (expired != 0) { + WarnL << "Stamp expired is abnormal: " << expired; + } + //休眠0.5 ms + usleep(500); + } + }); + static onceToken s_token([]() { + s_thread.detach(); + }); + return true; +} + +uint64_t getCurrentMillisecond(bool system_time) { + static bool flag = initMillisecondThread(); + if (system_time) { + return s_currentMillisecond_system.load(memory_order_acquire); + } + return s_currentMillisecond.load(memory_order_acquire); +} + +uint64_t getCurrentMicrosecond(bool system_time) { + static bool flag = initMillisecondThread(); + if (system_time) { + return s_currentMicrosecond_system.load(memory_order_acquire); + } + return s_currentMicrosecond.load(memory_order_acquire); +} + +string getTimeStr(const char *fmt, time_t time) { + if (!time) { + time = ::time(nullptr); + } + auto tm = getLocalTime(time); + size_t size = strlen(fmt) + 64; + string ret; + ret.resize(size); + size = std::strftime(&ret[0], size, fmt, &tm); + if (size > 0) { + ret.resize(size); + } + else{ + ret = fmt; + } + return ret; +} + + +struct tm getLocalTime(time_t sec) { + struct tm tm; +#ifdef _WIN32 + localtime_s(&tm, &sec); +#else + no_locks_localtime(&tm, sec); +#endif //_WIN32 + return tm; +} + +static thread_local string thread_name; + +static string limitString(const char *name, size_t max_size) { + string str = name; + if (str.size() + 1 > max_size) { + auto erased = str.size() + 1 - max_size + 3; + str.replace(5, erased, "..."); + } + return str; +} + +void setThreadName(const char *name) { + assert(name); +#if defined(__linux) || defined(__linux__) || defined(__MINGW32__) + pthread_setname_np(pthread_self(), limitString(name, 16).data()); +#elif defined(__MACH__) || defined(__APPLE__) + pthread_setname_np(limitString(name, 32).data()); +#elif defined(_MSC_VER) + // SetThreadDescription was added in 1607 (aka RS1). Since we can't guarantee the user is running 1607 or later, we need to ask for the function from the kernel. + using SetThreadDescriptionFunc = HRESULT(WINAPI * )(_In_ HANDLE hThread, _In_ PCWSTR lpThreadDescription); + static auto setThreadDescription = reinterpret_cast(::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "SetThreadDescription")); + if (setThreadDescription) { + // Convert the thread name to Unicode + wchar_t threadNameW[MAX_PATH]; + size_t numCharsConverted; + errno_t wcharResult = mbstowcs_s(&numCharsConverted, threadNameW, name, MAX_PATH - 1); + if (wcharResult == 0) { + HRESULT hr = setThreadDescription(::GetCurrentThread(), threadNameW); + if (!SUCCEEDED(hr)) { + int i = 0; + i++; + } + } + } else { + // For understanding the types and values used here, please see: + // https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code + + const DWORD MS_VC_EXCEPTION = 0x406D1388; +#pragma pack(push, 8) + struct THREADNAME_INFO { + DWORD dwType = 0x1000; // Must be 0x1000 + LPCSTR szName; // Pointer to name (in user address space) + DWORD dwThreadID; // Thread ID (-1 for caller thread) + DWORD dwFlags = 0; // Reserved for future use; must be zero + }; +#pragma pack(pop) + + THREADNAME_INFO info; + info.szName = name; + info.dwThreadID = (DWORD) - 1; + + __try{ + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), reinterpret_cast(&info)); + } __except(GetExceptionCode() == MS_VC_EXCEPTION ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER) { + } + } +#else + thread_name = name ? name : ""; +#endif +} + +string getThreadName() { +#if ((defined(__linux) || defined(__linux__)) && !defined(ANDROID)) || (defined(__MACH__) || defined(__APPLE__)) || (defined(ANDROID) && __ANDROID_API__ >= 26) || defined(__MINGW32__) + string ret; + ret.resize(32); + auto tid = pthread_self(); + pthread_getname_np(tid, (char *) ret.data(), ret.size()); + if (ret[0]) { + ret.resize(strlen(ret.data())); + return ret; + } + return to_string((uint64_t) tid); +#elif defined(_MSC_VER) + using GetThreadDescriptionFunc = HRESULT(WINAPI * )(_In_ HANDLE hThread, _In_ PWSTR * ppszThreadDescription); + static auto getThreadDescription = reinterpret_cast(::GetProcAddress(::GetModuleHandleA("Kernel32.dll"), "GetThreadDescription")); + + if (!getThreadDescription) { + std::ostringstream ss; + ss << std::this_thread::get_id(); + return ss.str(); + } else { + PWSTR data; + HRESULT hr = getThreadDescription(GetCurrentThread(), &data); + if (SUCCEEDED(hr) && data[0] != '\0') { + char threadName[MAX_PATH]; + size_t numCharsConverted; + errno_t charResult = wcstombs_s(&numCharsConverted, threadName, data, MAX_PATH - 1); + if (charResult == 0) { + LocalFree(data); + std::ostringstream ss; + ss << threadName; + return ss.str(); + } else { + if (data) { + LocalFree(data); + } + return to_string((uint64_t) GetCurrentThreadId()); + } + } else { + if (data) { + LocalFree(data); + } + return to_string((uint64_t) GetCurrentThreadId()); + } + } +#else + if (!thread_name.empty()) { + return thread_name; + } + std::ostringstream ss; + ss << std::this_thread::get_id(); + return ss.str(); +#endif +} + +bool setThreadAffinity(int i) { +#if (defined(__linux) || defined(__linux__)) && !defined(ANDROID) + cpu_set_t mask; + CPU_ZERO(&mask); + if (i >= 0) { + CPU_SET(i, &mask); + } else { + for (auto j = 0u; j < thread::hardware_concurrency(); ++j) { + CPU_SET(j, &mask); + } + } + if (!pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask)) { + return true; + } + WarnL << "pthread_setaffinity_np failed: " << get_uv_errmsg(); +#endif + return false; +} + +#ifndef HAS_CXA_DEMANGLE +// We only support some compilers that support __cxa_demangle. +// TODO: Checks if Android NDK has fixed this issue or not. +#if defined(__ANDROID__) && (defined(__i386__) || defined(__x86_64__)) +#define HAS_CXA_DEMANGLE 0 +#elif (__GNUC__ >= 4 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4)) && \ + !defined(__mips__) +#define HAS_CXA_DEMANGLE 1 +#elif defined(__clang__) && !defined(_MSC_VER) +#define HAS_CXA_DEMANGLE 1 +#else +#define HAS_CXA_DEMANGLE 0 +#endif +#endif +#if HAS_CXA_DEMANGLE +#include +#endif + +// Demangle a mangled symbol name and return the demangled name. +// If 'mangled' isn't mangled in the first place, this function +// simply returns 'mangled' as is. +// +// This function is used for demangling mangled symbol names such as +// '_Z3bazifdPv'. It uses abi::__cxa_demangle() if your compiler has +// the API. Otherwise, this function simply returns 'mangled' as is. +// +// Currently, we support only GCC 3.4.x or later for the following +// reasons. +// +// - GCC 2.95.3 doesn't have cxxabi.h +// - GCC 3.3.5 and ICC 9.0 have a bug. Their abi::__cxa_demangle() +// returns junk values for non-mangled symbol names (ex. function +// names in C linkage). For example, +// abi::__cxa_demangle("main", 0, 0, &status) +// returns "unsigned long" and the status code is 0 (successful). +// +// Also, +// +// - MIPS is not supported because abi::__cxa_demangle() is not defined. +// - Android x86 is not supported because STLs don't define __cxa_demangle +// +string demangle(const char *mangled) { + int status = 0; + char *demangled = nullptr; +#if HAS_CXA_DEMANGLE + demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); +#endif + string out; + if (status == 0 && demangled) { // Demangling succeeeded. + out.append(demangled); +#ifdef ASAN_USE_DELETE + delete [] demangled; // 开启asan后,用free会卡死 +#else + free(demangled); +#endif + } else { + out.append(mangled); + } + return out; +} + +string getEnv(const string &key) { + auto ekey = key.c_str(); + if (*ekey == '$') { + ++ekey; + } + auto value = *ekey ? getenv(ekey) : nullptr; + return value ? value : ""; +} + + +void Creator::onDestoryException(const type_info &info, const exception &ex) { + ErrorL << "Invoke " << demangle(info.name()) << "::onDestory throw a exception: " << ex.what(); +} + +} // namespace toolkit + + +extern "C" { +void Assert_Throw(int failed, const char *exp, const char *func, const char *file, int line, const char *str) { + if (failed) { + toolkit::_StrPrinter printer; + printer << "Assertion failed: (" << exp ; + if(str && *str){ + printer << ", " << str; + } + printer << "), function " << func << ", file " << file << ", line " << line << "."; + throw toolkit::AssertFailedException(printer); + } +} +} \ No newline at end of file diff --git a/ToolKit/Util/util.h b/ToolKit/Util/util.h new file mode 100644 index 0000000..158d6b9 --- /dev/null +++ b/ToolKit/Util/util.h @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_UTIL_H_ +#define UTIL_UTIL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "function_traits.h" +#if defined(_WIN32) +#undef FD_SETSIZE +//修改默认64为1024路 +#define FD_SETSIZE 1024 +#include +#pragma comment (lib,"WS2_32") +#else +#include +#include +#include +#include +#endif // defined(_WIN32) + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#if TARGET_IPHONE_SIMULATOR +#define OS_IPHONE +#elif TARGET_OS_IPHONE +#define OS_IPHONE +#endif +#endif //__APPLE__ + +#define INSTANCE_IMP(class_name, ...) \ +class_name &class_name::Instance() { \ + static std::shared_ptr s_instance(new class_name(__VA_ARGS__)); \ + static class_name &s_instance_ref = *s_instance; \ + return s_instance_ref; \ +} + +namespace toolkit { + +#define StrPrinter ::toolkit::_StrPrinter() +class _StrPrinter : public std::string { +public: + _StrPrinter() {} + + template + _StrPrinter& operator <<(T && data) { + _stream << std::forward(data); + this->std::string::operator=(_stream.str()); + return *this; + } + + std::string operator <<(std::ostream&(*f)(std::ostream&)) const { + return *this; + } + +private: + std::stringstream _stream; +}; + +//禁止拷贝基类 +class noncopyable { +protected: + noncopyable() {} + ~noncopyable() {} +private: + //禁止拷贝 + noncopyable(const noncopyable &that) = delete; + noncopyable(noncopyable &&that) = delete; + noncopyable &operator=(const noncopyable &that) = delete; + noncopyable &operator=(noncopyable &&that) = delete; +}; + +#ifndef CLASS_FUNC_TRAITS +#define CLASS_FUNC_TRAITS(func_name) \ +template \ +constexpr bool Has_##func_name(decltype(&T::on##func_name) /*unused*/) { \ + using RET = typename function_traits::return_type; \ + using FuncType = RET (T::*)(ARGS...); \ + return std::is_same::value; \ +} \ +\ +template \ +constexpr bool Has_##func_name(...) { \ + return false; \ +} \ +\ +template \ +static void InvokeFunc_##func_name(typename std::enable_if(nullptr), T>::type &obj, ARGS ...args) {} \ +\ +template\ +static typename function_traits::return_type InvokeFunc_##func_name(typename std::enable_if(nullptr), T>::type &obj, ARGS ...args) {\ + return obj.on##func_name(std::forward(args)...);\ +} +#endif //CLASS_FUNC_TRAITS + +#ifndef CLASS_FUNC_INVOKE +#define CLASS_FUNC_INVOKE(T, obj, func_name, ...) InvokeFunc_##func_name(obj, ##__VA_ARGS__) +#endif //CLASS_FUNC_INVOKE + +CLASS_FUNC_TRAITS(Destory) +CLASS_FUNC_TRAITS(Create) + +/** + * 对象安全的构建和析构,构建后执行onCreate函数,析构前执行onDestory函数 + * 在函数onCreate和onDestory中可以执行构造或析构中不能调用的方法,比如说shared_from_this或者虚函数 + * @warning onDestory函数确保参数个数为0;否则会被忽略调用 + */ +class Creator { +public: + /** + * 创建对象,用空参数执行onCreate和onDestory函数 + * @param args 对象构造函数参数列表 + * @return args对象的智能指针 + */ + template + static std::shared_ptr create(ArgsType &&...args) { + std::shared_ptr ret(new C(std::forward(args)...), [](C *ptr) { + try { + CLASS_FUNC_INVOKE(C, *ptr, Destory); + } catch (std::exception &ex){ + onDestoryException(typeid(C), ex); + } + delete ptr; + }); + CLASS_FUNC_INVOKE(C, *ret, Create); + return ret; + } + + /** + * 创建对象,用指定参数执行onCreate函数 + * @param args 对象onCreate函数参数列表 + * @warning args参数类型和个数必须与onCreate函数类型匹配(不可忽略默认参数),否则会由于模板匹配失败导致忽略调用 + * @return args对象的智能指针 + */ + template + static std::shared_ptr create2(ArgsType &&...args) { + std::shared_ptr ret(new C(), [](C *ptr) { + try { + CLASS_FUNC_INVOKE(C, *ptr, Destory); + } catch (std::exception &ex){ + onDestoryException(typeid(C), ex); + } + delete ptr; + }); + CLASS_FUNC_INVOKE(C, *ret, Create, std::forward(args)...); + return ret; + } + +private: + static void onDestoryException(const std::type_info &info, const std::exception &ex); + +private: + Creator() = default; + ~Creator() = default; +}; + +template +class ObjectStatistic{ +public: + ObjectStatistic(){ + ++getCounter(); + } + + ~ObjectStatistic(){ + --getCounter(); + } + + static size_t count(){ + return getCounter().load(); + } + +private: + static std::atomic & getCounter(); +}; + +#define StatisticImp(Type) \ + template<> \ + std::atomic& ObjectStatistic::getCounter(){ \ + static std::atomic instance(0); \ + return instance; \ + } + +class AssertFailedException : public std::runtime_error { +public: + template + AssertFailedException(T && ...args) : std::runtime_error(std::forward(args)...) {} +}; + +std::string makeRandStr(int sz, bool printable = true); +std::string hexdump(const void *buf, size_t len); +std::string hexmem(const void* buf, size_t len); +std::string exePath(bool isExe = true); +std::string exeDir(bool isExe = true); +std::string exeName(bool isExe = true); + +std::vector split(const std::string& s, const char *delim); +//去除前后的空格、回车符、制表符... +std::string& trim(std::string &s,const std::string &chars=" \r\n\t"); +std::string trim(std::string &&s,const std::string &chars=" \r\n\t"); +// string转小写 +std::string &strToLower(std::string &str); +std::string strToLower(std::string &&str); +// string转大写 +std::string &strToUpper(std::string &str); +std::string strToUpper(std::string &&str); +//替换子字符串 +void replace(std::string &str, const std::string &old_str, const std::string &new_str, std::string::size_type b_pos = 0) ; +//判断是否为ip +bool isIP(const char *str); +//字符串是否以xx开头 +bool start_with(const std::string &str, const std::string &substr); +//字符串是否以xx结尾 +bool end_with(const std::string &str, const std::string &substr); +//拼接格式字符串 +template +std::string str_format(const std::string &format, Args... args) { + + // Calculate the buffer size + auto size_buf = snprintf(nullptr, 0, format.c_str(), args ...) + 1; + // Allocate the buffer +#if __cplusplus >= 201703L + // C++17 + auto buf = std::make_unique(size_buf); +#else + // C++11 + std:: unique_ptr buf(new(std::nothrow) char[size_buf]); +#endif + // Check if the allocation is successful + if (buf == nullptr) { + return {}; + } + // Fill the buffer with formatted string + auto result = snprintf(buf.get(), size_buf, format.c_str(), args ...); + // Return the formatted string + return std::string(buf.get(), buf.get() + result); +} + +#ifndef bzero +#define bzero(ptr,size) memset((ptr),0,(size)); +#endif //bzero + +#if defined(ANDROID) +template +std::string to_string(T value){ + std::ostringstream os ; + os << std::forward(value); + return os.str() ; +} +#endif//ANDROID + +#if defined(_WIN32) +int gettimeofday(struct timeval *tp, void *tzp); +void usleep(int micro_seconds); +void sleep(int second); +int vasprintf(char **strp, const char *fmt, va_list ap); +int asprintf(char **strp, const char *fmt, ...); +const char *strcasestr(const char *big, const char *little); + +#if !defined(strcasecmp) + #define strcasecmp _stricmp +#endif + +#if !defined(strncasecmp) +#define strncasecmp _strnicmp +#endif + +#ifndef ssize_t + #ifdef _WIN64 + #define ssize_t int64_t + #else + #define ssize_t int32_t + #endif +#endif +#endif //WIN32 + +/** + * 获取时间差, 返回值单位为秒 + */ +long getGMTOff(); + +/** + * 获取1970年至今的毫秒数 + * @param system_time 是否为系统时间(系统时间可以回退),否则为程序启动时间(不可回退) + */ +uint64_t getCurrentMillisecond(bool system_time = false); + +/** + * 获取1970年至今的微秒数 + * @param system_time 是否为系统时间(系统时间可以回退),否则为程序启动时间(不可回退) + */ +uint64_t getCurrentMicrosecond(bool system_time = false); + +/** + * 获取时间字符串 + * @param fmt 时间格式,譬如%Y-%m-%d %H:%M:%S + * @return 时间字符串 + */ +std::string getTimeStr(const char *fmt,time_t time = 0); + +/** + * 根据unix时间戳获取本地时间 + * @param sec unix时间戳 + * @return tm结构体 + */ +struct tm getLocalTime(time_t sec); + +/** + * 设置线程名 + */ +void setThreadName(const char *name); + +/** + * 获取线程名 + */ +std::string getThreadName(); + +/** + * 设置当前线程cpu亲和性 + * @param i cpu索引,如果为-1,那么取消cpu亲和性 + * @return 是否成功,目前只支持linux + */ +bool setThreadAffinity(int i); + +/** + * 根据typeid(class).name()获取类名 + */ +std::string demangle(const char *mangled); + +/** + * 获取环境变量内容,以'$'开头 + */ +std::string getEnv(const std::string &key); + +// 可以保存任意的对象 +class Any { +public: + using Ptr = std::shared_ptr; + + Any() = default; + ~Any() = default; + + Any(const Any &that) = default; + Any(Any &&that) { + _type = that._type; + _data = std::move(that._data); + } + + Any &operator=(const Any &that) = default; + Any &operator=(Any &&that) { + _type = that._type; + _data = std::move(that._data); + return *this; + } + + template + void set(ArgsType &&...args) { + _type = &typeid(T); + _data.reset(new T(std::forward(args)...), [](void *ptr) { delete (T *)ptr; }); + } + + template + void set(std::shared_ptr data) { + if (data) { + _type = &typeid(T); + _data = std::move(data); + } else { + reset(); + } + } + + template + T &get(bool safe = true) { + if (!_data) { + throw std::invalid_argument("Any is empty"); + } + if (safe && !is()) { + throw std::invalid_argument("Any::get(): " + demangle(_type->name()) + " unable cast to " + demangle(typeid(T).name())); + } + return *((T *)_data.get()); + } + + template + const T &get(bool safe = true) const { + return const_cast(*this).get(safe); + } + + template + bool is() const { + return _type && typeid(T) == *_type; + } + + operator bool() const { return _data.operator bool(); } + bool empty() const { return !bool(); } + + void reset() { + _type = nullptr; + _data = nullptr; + } + + Any &operator=(std::nullptr_t) { + reset(); + return *this; + } + + std::string type_name() const { + if (!_type) { + return ""; + } + return demangle(_type->name()); + } + +private: + const std::type_info* _type = nullptr; + std::shared_ptr _data; +}; + +// 用于保存一些外加属性 +class AnyStorage : public std::unordered_map { +public: + AnyStorage() = default; + ~AnyStorage() = default; + using Ptr = std::shared_ptr; +}; + +} // namespace toolkit + +#ifdef __cplusplus +extern "C" { +#endif +extern void Assert_Throw(int failed, const char *exp, const char *func, const char *file, int line, const char *str); +#ifdef __cplusplus +} +#endif + +#endif /* UTIL_UTIL_H_ */ diff --git a/ToolKit/Util/uv_errno.cpp b/ToolKit/Util/uv_errno.cpp new file mode 100644 index 0000000..1b365fc --- /dev/null +++ b/ToolKit/Util/uv_errno.cpp @@ -0,0 +1,188 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. +* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ +#include "uv_errno.h" +#include + +#if defined(_WIN32) +#define FD_SETSIZE 1024 //修改默认64为1024路 +#include +#include +#else +#include +#endif // defined(_WIN32) + +namespace toolkit { + +static const char *uv__unknown_err_code(int err) { + static char buf[32]; + snprintf(buf, sizeof(buf), "Unknown system error %d", err); + return buf; +} + +#define UV_ERR_NAME_GEN(name, _) case UV_ ## name: return #name; +const char *uv_err_name(int err) { + switch (err) { + UV_ERRNO_MAP(UV_ERR_NAME_GEN) + } + return uv__unknown_err_code(err); +} +#undef UV_ERR_NAME_GEN + +#define UV_STRERROR_GEN(name, msg) case UV_ ## name: return msg; +const char *uv_strerror(int err) { + switch (err) { + UV_ERRNO_MAP(UV_STRERROR_GEN) + } + return uv__unknown_err_code(err); +} +#undef UV_STRERROR_GEN + +int uv_translate_posix_error(int err) { + if (err <= 0) { + return err; + } + switch (err) { + //为了兼容windows/unix平台,信号EINPROGRESS ,EAGAIN,EWOULDBLOCK,ENOBUFS 全部统一成EAGAIN处理 + case ENOBUFS://在mac系统下实测发现会有此信号发生 + case EINPROGRESS: + case EWOULDBLOCK: err = EAGAIN; break; + default: break; + } + return -err; +} + +int get_uv_error(bool netErr) { +#if defined(_WIN32) + auto errCode = netErr ? WSAGetLastError() : GetLastError(); + switch (errCode) { + case ERROR_NOACCESS: return UV_EACCES; + case WSAEACCES: return UV_EACCES; +#if defined(ERROR_ELEVATION_REQUIRED) + case ERROR_ELEVATION_REQUIRED: return UV_EACCES; +#endif //ERROR_ELEVATION_REQUIRED + case ERROR_ADDRESS_ALREADY_ASSOCIATED: return UV_EADDRINUSE; + case WSAEADDRINUSE: return UV_EADDRINUSE; + case WSAEADDRNOTAVAIL: return UV_EADDRNOTAVAIL; + case WSAEAFNOSUPPORT: return UV_EAFNOSUPPORT; + case WSAEWOULDBLOCK: return UV_EAGAIN; + case WSAEALREADY: return UV_EALREADY; + case ERROR_INVALID_FLAGS: return UV_EBADF; + case ERROR_INVALID_HANDLE: return UV_EBADF; + case ERROR_LOCK_VIOLATION: return UV_EBUSY; + case ERROR_PIPE_BUSY: return UV_EBUSY; + case ERROR_SHARING_VIOLATION: return UV_EBUSY; + case ERROR_OPERATION_ABORTED: return UV_ECANCELED; + case WSAEINTR: return UV_ECANCELED; + case ERROR_NO_UNICODE_TRANSLATION: return UV_ECHARSET; + case ERROR_CONNECTION_ABORTED: return UV_ECONNABORTED; + case WSAECONNABORTED: return UV_ECONNABORTED; + case ERROR_CONNECTION_REFUSED: return UV_ECONNREFUSED; + case WSAECONNREFUSED: return UV_ECONNREFUSED; + case ERROR_NETNAME_DELETED: return UV_ECONNRESET; + case WSAECONNRESET: return UV_ECONNRESET; + case ERROR_ALREADY_EXISTS: return UV_EEXIST; + case ERROR_FILE_EXISTS: return UV_EEXIST; + case ERROR_BUFFER_OVERFLOW: return UV_EFAULT; + case WSAEFAULT: return UV_EFAULT; + case ERROR_HOST_UNREACHABLE: return UV_EHOSTUNREACH; + case WSAEHOSTUNREACH: return UV_EHOSTUNREACH; + case ERROR_INSUFFICIENT_BUFFER: return UV_EINVAL; + case ERROR_INVALID_DATA: return UV_EINVAL; + case ERROR_INVALID_PARAMETER: return UV_EINVAL; +#if defined(ERROR_SYMLINK_NOT_SUPPORTED) + case ERROR_SYMLINK_NOT_SUPPORTED: return UV_EINVAL; +#endif //ERROR_SYMLINK_NOT_SUPPORTED + case WSAEINVAL: return UV_EINVAL; + case WSAEPFNOSUPPORT: return UV_EINVAL; + case WSAESOCKTNOSUPPORT: return UV_EINVAL; + case ERROR_BEGINNING_OF_MEDIA: return UV_EIO; + case ERROR_BUS_RESET: return UV_EIO; + case ERROR_CRC: return UV_EIO; + case ERROR_DEVICE_DOOR_OPEN: return UV_EIO; + case ERROR_DEVICE_REQUIRES_CLEANING: return UV_EIO; + case ERROR_DISK_CORRUPT: return UV_EIO; + case ERROR_EOM_OVERFLOW: return UV_EIO; + case ERROR_FILEMARK_DETECTED: return UV_EIO; + case ERROR_GEN_FAILURE: return UV_EIO; + case ERROR_INVALID_BLOCK_LENGTH: return UV_EIO; + case ERROR_IO_DEVICE: return UV_EIO; + case ERROR_NO_DATA_DETECTED: return UV_EIO; + case ERROR_NO_SIGNAL_SENT: return UV_EIO; + case ERROR_OPEN_FAILED: return UV_EIO; + case ERROR_SETMARK_DETECTED: return UV_EIO; + case ERROR_SIGNAL_REFUSED: return UV_EIO; + case WSAEISCONN: return UV_EISCONN; + case ERROR_CANT_RESOLVE_FILENAME: return UV_ELOOP; + case ERROR_TOO_MANY_OPEN_FILES: return UV_EMFILE; + case WSAEMFILE: return UV_EMFILE; + case WSAEMSGSIZE: return UV_EMSGSIZE; + case ERROR_FILENAME_EXCED_RANGE: return UV_ENAMETOOLONG; + case ERROR_NETWORK_UNREACHABLE: return UV_ENETUNREACH; + case WSAENETUNREACH: return UV_ENETUNREACH; + case WSAENOBUFS: return UV_ENOBUFS; + case ERROR_BAD_PATHNAME: return UV_ENOENT; + case ERROR_DIRECTORY: return UV_ENOENT; + case ERROR_FILE_NOT_FOUND: return UV_ENOENT; + case ERROR_INVALID_NAME: return UV_ENOENT; + case ERROR_INVALID_DRIVE: return UV_ENOENT; + case ERROR_INVALID_REPARSE_DATA: return UV_ENOENT; + case ERROR_MOD_NOT_FOUND: return UV_ENOENT; + case ERROR_PATH_NOT_FOUND: return UV_ENOENT; + case WSAHOST_NOT_FOUND: return UV_ENOENT; + case WSANO_DATA: return UV_ENOENT; + case ERROR_NOT_ENOUGH_MEMORY: return UV_ENOMEM; + case ERROR_OUTOFMEMORY: return UV_ENOMEM; + case ERROR_CANNOT_MAKE: return UV_ENOSPC; + case ERROR_DISK_FULL: return UV_ENOSPC; + case ERROR_EA_TABLE_FULL: return UV_ENOSPC; + case ERROR_END_OF_MEDIA: return UV_ENOSPC; + case ERROR_HANDLE_DISK_FULL: return UV_ENOSPC; + case ERROR_NOT_CONNECTED: return UV_ENOTCONN; + case WSAENOTCONN: return UV_ENOTCONN; + case ERROR_DIR_NOT_EMPTY: return UV_ENOTEMPTY; + case WSAENOTSOCK: return UV_ENOTSOCK; + case ERROR_NOT_SUPPORTED: return UV_ENOTSUP; + case ERROR_BROKEN_PIPE: return UV_EOF; + case ERROR_ACCESS_DENIED: return UV_EPERM; + case ERROR_PRIVILEGE_NOT_HELD: return UV_EPERM; + case ERROR_BAD_PIPE: return UV_EPIPE; + case ERROR_NO_DATA: return UV_EPIPE; + case ERROR_PIPE_NOT_CONNECTED: return UV_EPIPE; + case WSAESHUTDOWN: return UV_EPIPE; + case WSAEPROTONOSUPPORT: return UV_EPROTONOSUPPORT; + case ERROR_WRITE_PROTECT: return UV_EROFS; + case ERROR_SEM_TIMEOUT: return UV_ETIMEDOUT; + case WSAETIMEDOUT: return UV_ETIMEDOUT; + case ERROR_NOT_SAME_DEVICE: return UV_EXDEV; + case ERROR_INVALID_FUNCTION: return UV_EISDIR; + case ERROR_META_EXPANSION_TOO_LONG: return UV_E2BIG; + default: return errCode; + } +#else + return uv_translate_posix_error(errno); +#endif // defined(_WIN32) +} + +const char *get_uv_errmsg(bool netErr) { + return uv_strerror(get_uv_error(netErr)); +} + +}//namespace toolkit \ No newline at end of file diff --git a/ToolKit/Util/uv_errno.h b/ToolKit/Util/uv_errno.h new file mode 100644 index 0000000..e5e93f2 --- /dev/null +++ b/ToolKit/Util/uv_errno.h @@ -0,0 +1,524 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. +* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + +#ifndef UV_ERRNO_H_ +#define UV_ERRNO_H_ + +#include + +#define UV__EOF (-4095) +#define UV__UNKNOWN (-4094) + +#define UV__EAI_ADDRFAMILY (-3000) +#define UV__EAI_AGAIN (-3001) +#define UV__EAI_BADFLAGS (-3002) +#define UV__EAI_CANCELED (-3003) +#define UV__EAI_FAIL (-3004) +#define UV__EAI_FAMILY (-3005) +#define UV__EAI_MEMORY (-3006) +#define UV__EAI_NODATA (-3007) +#define UV__EAI_NONAME (-3008) +#define UV__EAI_OVERFLOW (-3009) +#define UV__EAI_SERVICE (-3010) +#define UV__EAI_SOCKTYPE (-3011) +#define UV__EAI_BADHINTS (-3013) +#define UV__EAI_PROTOCOL (-3014) + +/* Only map to the system errno on non-Windows platforms. It's apparently +* a fairly common practice for Windows programmers to redefine errno codes. +*/ +#if defined(E2BIG) && !defined(_WIN32) +# define UV__E2BIG (-E2BIG) +#else +# define UV__E2BIG (-4093) +#endif + +#if defined(EACCES) && !defined(_WIN32) +# define UV__EACCES (-EACCES) +#else +# define UV__EACCES (-4092) +#endif + +#if defined(EADDRINUSE) && !defined(_WIN32) +# define UV__EADDRINUSE (-EADDRINUSE) +#else +# define UV__EADDRINUSE (-4091) +#endif + +#if defined(EADDRNOTAVAIL) && !defined(_WIN32) +# define UV__EADDRNOTAVAIL (-EADDRNOTAVAIL) +#else +# define UV__EADDRNOTAVAIL (-4090) +#endif + +#if defined(EAFNOSUPPORT) && !defined(_WIN32) +# define UV__EAFNOSUPPORT (-EAFNOSUPPORT) +#else +# define UV__EAFNOSUPPORT (-4089) +#endif + +#if defined(EAGAIN) && !defined(_WIN32) +# define UV__EAGAIN (-EAGAIN) +#else +# define UV__EAGAIN (-4088) +#endif + +#if defined(EALREADY) && !defined(_WIN32) +# define UV__EALREADY (-EALREADY) +#else +# define UV__EALREADY (-4084) +#endif + +#if defined(EBADF) && !defined(_WIN32) +# define UV__EBADF (-EBADF) +#else +# define UV__EBADF (-4083) +#endif + +#if defined(EBUSY) && !defined(_WIN32) +# define UV__EBUSY (-EBUSY) +#else +# define UV__EBUSY (-4082) +#endif + +#if defined(ECANCELED) && !defined(_WIN32) +# define UV__ECANCELED (-ECANCELED) +#else +# define UV__ECANCELED (-4081) +#endif + +#if defined(ECHARSET) && !defined(_WIN32) +# define UV__ECHARSET (-ECHARSET) +#else +# define UV__ECHARSET (-4080) +#endif + +#if defined(ECONNABORTED) && !defined(_WIN32) +# define UV__ECONNABORTED (-ECONNABORTED) +#else +# define UV__ECONNABORTED (-4079) +#endif + +#if defined(ECONNREFUSED) && !defined(_WIN32) +# define UV__ECONNREFUSED (-ECONNREFUSED) +#else +# define UV__ECONNREFUSED (-4078) +#endif + +#if defined(ECONNRESET) && !defined(_WIN32) +# define UV__ECONNRESET (-ECONNRESET) +#else +# define UV__ECONNRESET (-4077) +#endif + +#if defined(EDESTADDRREQ) && !defined(_WIN32) +# define UV__EDESTADDRREQ (-EDESTADDRREQ) +#else +# define UV__EDESTADDRREQ (-4076) +#endif + +#if defined(EEXIST) && !defined(_WIN32) +# define UV__EEXIST (-EEXIST) +#else +# define UV__EEXIST (-4075) +#endif + +#if defined(EFAULT) && !defined(_WIN32) +# define UV__EFAULT (-EFAULT) +#else +# define UV__EFAULT (-4074) +#endif + +#if defined(EHOSTUNREACH) && !defined(_WIN32) +# define UV__EHOSTUNREACH (-EHOSTUNREACH) +#else +# define UV__EHOSTUNREACH (-4073) +#endif + +#if defined(EINTR) && !defined(_WIN32) +# define UV__EINTR (-EINTR) +#else +# define UV__EINTR (-4072) +#endif + +#if defined(EINVAL) && !defined(_WIN32) +# define UV__EINVAL (-EINVAL) +#else +# define UV__EINVAL (-4071) +#endif + +#if defined(EIO) && !defined(_WIN32) +# define UV__EIO (-EIO) +#else +# define UV__EIO (-4070) +#endif + +#if defined(EISCONN) && !defined(_WIN32) +# define UV__EISCONN (-EISCONN) +#else +# define UV__EISCONN (-4069) +#endif + +#if defined(EISDIR) && !defined(_WIN32) +# define UV__EISDIR (-EISDIR) +#else +# define UV__EISDIR (-4068) +#endif + +#if defined(ELOOP) && !defined(_WIN32) +# define UV__ELOOP (-ELOOP) +#else +# define UV__ELOOP (-4067) +#endif + +#if defined(EMFILE) && !defined(_WIN32) +# define UV__EMFILE (-EMFILE) +#else +# define UV__EMFILE (-4066) +#endif + +#if defined(EMSGSIZE) && !defined(_WIN32) +# define UV__EMSGSIZE (-EMSGSIZE) +#else +# define UV__EMSGSIZE (-4065) +#endif + +#if defined(ENAMETOOLONG) && !defined(_WIN32) +# define UV__ENAMETOOLONG (-ENAMETOOLONG) +#else +# define UV__ENAMETOOLONG (-4064) +#endif + +#if defined(ENETDOWN) && !defined(_WIN32) +# define UV__ENETDOWN (-ENETDOWN) +#else +# define UV__ENETDOWN (-4063) +#endif + +#if defined(ENETUNREACH) && !defined(_WIN32) +# define UV__ENETUNREACH (-ENETUNREACH) +#else +# define UV__ENETUNREACH (-4062) +#endif + +#if defined(ENFILE) && !defined(_WIN32) +# define UV__ENFILE (-ENFILE) +#else +# define UV__ENFILE (-4061) +#endif + +#if defined(ENOBUFS) && !defined(_WIN32) +# define UV__ENOBUFS (-ENOBUFS) +#else +# define UV__ENOBUFS (-4060) +#endif + +#if defined(ENODEV) && !defined(_WIN32) +# define UV__ENODEV (-ENODEV) +#else +# define UV__ENODEV (-4059) +#endif + +#if defined(ENOENT) && !defined(_WIN32) +# define UV__ENOENT (-ENOENT) +#else +# define UV__ENOENT (-4058) +#endif + +#if defined(ENOMEM) && !defined(_WIN32) +# define UV__ENOMEM (-ENOMEM) +#else +# define UV__ENOMEM (-4057) +#endif + +#if defined(ENONET) && !defined(_WIN32) +# define UV__ENONET (-ENONET) +#else +# define UV__ENONET (-4056) +#endif + +#if defined(ENOSPC) && !defined(_WIN32) +# define UV__ENOSPC (-ENOSPC) +#else +# define UV__ENOSPC (-4055) +#endif + +#if defined(ENOSYS) && !defined(_WIN32) +# define UV__ENOSYS (-ENOSYS) +#else +# define UV__ENOSYS (-4054) +#endif + +#if defined(ENOTCONN) && !defined(_WIN32) +# define UV__ENOTCONN (-ENOTCONN) +#else +# define UV__ENOTCONN (-4053) +#endif + +#if defined(ENOTDIR) && !defined(_WIN32) +# define UV__ENOTDIR (-ENOTDIR) +#else +# define UV__ENOTDIR (-4052) +#endif + +#if defined(ENOTEMPTY) && !defined(_WIN32) +# define UV__ENOTEMPTY (-ENOTEMPTY) +#else +# define UV__ENOTEMPTY (-4051) +#endif + +#if defined(ENOTSOCK) && !defined(_WIN32) +# define UV__ENOTSOCK (-ENOTSOCK) +#else +# define UV__ENOTSOCK (-4050) +#endif + +#if defined(ENOTSUP) && !defined(_WIN32) +# define UV__ENOTSUP (-ENOTSUP) +#else +# define UV__ENOTSUP (-4049) +#endif + +#if defined(EPERM) && !defined(_WIN32) +# define UV__EPERM (-EPERM) +#else +# define UV__EPERM (-4048) +#endif + +#if defined(EPIPE) && !defined(_WIN32) +# define UV__EPIPE (-EPIPE) +#else +# define UV__EPIPE (-4047) +#endif + +#if defined(EPROTO) && !defined(_WIN32) +# define UV__EPROTO (-EPROTO) +#else +# define UV__EPROTO (-4046) +#endif + +#if defined(EPROTONOSUPPORT) && !defined(_WIN32) +# define UV__EPROTONOSUPPORT (-EPROTONOSUPPORT) +#else +# define UV__EPROTONOSUPPORT (-4045) +#endif + +#if defined(EPROTOTYPE) && !defined(_WIN32) +# define UV__EPROTOTYPE (-EPROTOTYPE) +#else +# define UV__EPROTOTYPE (-4044) +#endif + +#if defined(EROFS) && !defined(_WIN32) +# define UV__EROFS (-EROFS) +#else +# define UV__EROFS (-4043) +#endif + +#if defined(ESHUTDOWN) && !defined(_WIN32) +# define UV__ESHUTDOWN (-ESHUTDOWN) +#else +# define UV__ESHUTDOWN (-4042) +#endif + +#if defined(ESPIPE) && !defined(_WIN32) +# define UV__ESPIPE (-ESPIPE) +#else +# define UV__ESPIPE (-4041) +#endif + +#if defined(ESRCH) && !defined(_WIN32) +# define UV__ESRCH (-ESRCH) +#else +# define UV__ESRCH (-4040) +#endif + +#if defined(ETIMEDOUT) && !defined(_WIN32) +# define UV__ETIMEDOUT (-ETIMEDOUT) +#else +# define UV__ETIMEDOUT (-4039) +#endif + +#if defined(ETXTBSY) && !defined(_WIN32) +# define UV__ETXTBSY (-ETXTBSY) +#else +# define UV__ETXTBSY (-4038) +#endif + +#if defined(EXDEV) && !defined(_WIN32) +# define UV__EXDEV (-EXDEV) +#else +# define UV__EXDEV (-4037) +#endif + +#if defined(EFBIG) && !defined(_WIN32) +# define UV__EFBIG (-EFBIG) +#else +# define UV__EFBIG (-4036) +#endif + +#if defined(ENOPROTOOPT) && !defined(_WIN32) +# define UV__ENOPROTOOPT (-ENOPROTOOPT) +#else +# define UV__ENOPROTOOPT (-4035) +#endif + +#if defined(ERANGE) && !defined(_WIN32) +# define UV__ERANGE (-ERANGE) +#else +# define UV__ERANGE (-4034) +#endif + +#if defined(ENXIO) && !defined(_WIN32) +# define UV__ENXIO (-ENXIO) +#else +# define UV__ENXIO (-4033) +#endif + +#if defined(EMLINK) && !defined(_WIN32) +# define UV__EMLINK (-EMLINK) +#else +# define UV__EMLINK (-4032) +#endif + +/* EHOSTDOWN is not visible on BSD-like systems when _POSIX_C_SOURCE is +* defined. Fortunately, its value is always 64 so it's possible albeit +* icky to hard-code it. +*/ +#if defined(EHOSTDOWN) && !defined(_WIN32) +# define UV__EHOSTDOWN (-EHOSTDOWN) +#elif defined(__APPLE__) || \ + defined(__DragonFly__) || \ + defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) || \ + defined(__OpenBSD__) +# define UV__EHOSTDOWN (-64) +#else +# define UV__EHOSTDOWN (-4031) +#endif + +#if defined(EREMOTEIO) && !defined(_WIN32) +# define UV__EREMOTEIO (-EREMOTEIO) +#else +# define UV__EREMOTEIO (-4030) +#endif + + +#define UV_ERRNO_MAP(XX) \ + XX(E2BIG, "argument list too long") \ + XX(EACCES, "permission denied") \ + XX(EADDRINUSE, "address already in use") \ + XX(EADDRNOTAVAIL, "address not available") \ + XX(EAFNOSUPPORT, "address family not supported") \ + XX(EAGAIN, "resource temporarily unavailable") \ + XX(EAI_ADDRFAMILY, "address family not supported") \ + XX(EAI_AGAIN, "temporary failure") \ + XX(EAI_BADFLAGS, "bad ai_flags value") \ + XX(EAI_BADHINTS, "invalid value for hints") \ + XX(EAI_CANCELED, "request canceled") \ + XX(EAI_FAIL, "permanent failure") \ + XX(EAI_FAMILY, "ai_family not supported") \ + XX(EAI_MEMORY, "out of memory") \ + XX(EAI_NODATA, "no address") \ + XX(EAI_NONAME, "unknown node or service") \ + XX(EAI_OVERFLOW, "argument buffer overflow") \ + XX(EAI_PROTOCOL, "resolved protocol is unknown") \ + XX(EAI_SERVICE, "service not available for socket type") \ + XX(EAI_SOCKTYPE, "socket type not supported") \ + XX(EALREADY, "connection already in progress") \ + XX(EBADF, "bad file descriptor") \ + XX(EBUSY, "resource busy or locked") \ + XX(ECANCELED, "operation canceled") \ + XX(ECHARSET, "invalid Unicode character") \ + XX(ECONNABORTED, "software caused connection abort") \ + XX(ECONNREFUSED, "connection refused") \ + XX(ECONNRESET, "connection reset by peer") \ + XX(EDESTADDRREQ, "destination address required") \ + XX(EEXIST, "file already exists") \ + XX(EFAULT, "bad address in system call argument") \ + XX(EFBIG, "file too large") \ + XX(EHOSTUNREACH, "host is unreachable") \ + XX(EINTR, "interrupted system call") \ + XX(EINVAL, "invalid argument") \ + XX(EIO, "i/o error") \ + XX(EISCONN, "socket is already connected") \ + XX(EISDIR, "illegal operation on a directory") \ + XX(ELOOP, "too many symbolic links encountered") \ + XX(EMFILE, "too many open files") \ + XX(EMSGSIZE, "message too long") \ + XX(ENAMETOOLONG, "name too long") \ + XX(ENETDOWN, "network is down") \ + XX(ENETUNREACH, "network is unreachable") \ + XX(ENFILE, "file table overflow") \ + XX(ENOBUFS, "no buffer space available") \ + XX(ENODEV, "no such device") \ + XX(ENOENT, "no such file or directory") \ + XX(ENOMEM, "not enough memory") \ + XX(ENONET, "machine is not on the network") \ + XX(ENOPROTOOPT, "protocol not available") \ + XX(ENOSPC, "no space left on device") \ + XX(ENOSYS, "function not implemented") \ + XX(ENOTCONN, "socket is not connected") \ + XX(ENOTDIR, "not a directory") \ + XX(ENOTEMPTY, "directory not empty") \ + XX(ENOTSOCK, "socket operation on non-socket") \ + XX(ENOTSUP, "operation not supported on socket") \ + XX(EPERM, "operation not permitted") \ + XX(EPIPE, "broken pipe") \ + XX(EPROTO, "protocol error") \ + XX(EPROTONOSUPPORT, "protocol not supported") \ + XX(EPROTOTYPE, "protocol wrong type for socket") \ + XX(ERANGE, "result too large") \ + XX(EROFS, "read-only file system") \ + XX(ESHUTDOWN, "cannot send after transport endpoint shutdown") \ + XX(ESPIPE, "invalid seek") \ + XX(ESRCH, "no such process") \ + XX(ETIMEDOUT, "connection timed out") \ + XX(ETXTBSY, "text file is busy") \ + XX(EXDEV, "cross-device link not permitted") \ + XX(UNKNOWN, "unknown error") \ + XX(EOF, "end of file") \ + XX(ENXIO, "no such device or address") \ + XX(EMLINK, "too many links") \ + XX(EHOSTDOWN, "host is down") \ + XX(EREMOTEIO, "remote I/O error") \ + +namespace toolkit { + +typedef enum { +#define XX(code, _) UV_ ## code = UV__ ## code, + UV_ERRNO_MAP(XX) +#undef XX + UV_ERRNO_MAX = UV__EOF - 1 +} uv_errno_t; + +const char *uv_err_name(int err); +const char *uv_strerror(int err); +int uv_translate_posix_error(int err); +//netErr参数在windows平台下才有效 +int get_uv_error(bool netErr = true); +//netErr参数在windows平台下才有效 +const char *get_uv_errmsg(bool netErr = true); + +}//namespace toolkit + +#endif /* UV_ERRNO_H_ */ \ No newline at end of file