diff --git a/README.md b/README.md index 38731a39..c62cf3c8 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,25 @@ git submodule update --init - 2、如果您的问题还没解决,可以提issue. - 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提. - 4、QQ私聊一般不接受无偿技术咨询和支持(谈谈人生理想还是可以的😂),毕竟精力有限,谢谢理解. + +## 致谢 +感谢以下各位对本项目包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后: + +[老陈](https://github.com/ireader) +[Gemfield](https://github.com/gemfield) +[南冠彤](https://github.com/nanguantong2) +[凹凸慢](https://github.com/tsingeye) +[chenxiaolei](https://github.com/chenxiaolei) +[史前小虫](https://github.com/zqsong) +[清涩绿茶](https://github.com/baiyfcu) +[3503207480](https://github.com/3503207480) +[DroidChow](https://github.com/DroidChow) +[阿塞](https://github.com/HuoQiShuai) +[火宣](https://github.com/ChinaCCF) +[γ瑞γミ](https://github.com/JerryLinGd) +[linkingvision](https://www.linkingvision.com/) +[茄子](https://github.com/taotaobujue2008) +[好心情](<409257224@qq.com>) ## 捐赠 欢迎捐赠以便更好的推动项目的发展,谢谢您的支持! diff --git a/api/include/mk_media.h b/api/include/mk_media.h index d8162f6e..16516de4 100755 --- a/api/include/mk_media.h +++ b/api/include/mk_media.h @@ -87,7 +87,7 @@ API_EXPORT void API_CALL mk_media_init_aac(mk_media ctx, int channel, int sample * @param data 单帧H264数据 * @param len 单帧H264数据字节数 * @param dts 解码时间戳,单位毫秒 - * @param dts 播放时间戳,单位毫秒 + * @param pts 播放时间戳,单位毫秒 */ API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts); @@ -97,7 +97,7 @@ API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, * @param data 单帧H265数据 * @param len 单帧H265数据字节数 * @param dts 解码时间戳,单位毫秒 - * @param dts 播放时间戳,单位毫秒 + * @param pts 播放时间戳,单位毫秒 */ API_EXPORT void API_CALL mk_media_input_h265(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts); diff --git a/conf/config.ini b/conf/config.ini index 8010e4e8..c158d843 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -191,8 +191,6 @@ keepAliveSecond=15 port=554 #rtsps服务器监听地址 sslport=322 -#在接收rtsp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂) -modifyStamp=0 [shell] #调试telnet服务器接受最大bufffer大小 diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 4eb54eec..e2e7824e 100644 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -438,7 +438,7 @@ void installWebHook(){ //如果没有url参数,客户端又不支持cookie,那么会根据ip和端口追踪用户 //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ - if(sender.get_peer_ip() == "127.0.0.1" && parser.Params() == hook_adminparams){ + if(sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams){ //如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 invoker("","",60 * 60); return; diff --git a/src/Common/Device.cpp b/src/Common/Device.cpp index a04dba72..58fd53cb 100644 --- a/src/Common/Device.cpp +++ b/src/Common/Device.cpp @@ -104,7 +104,14 @@ void DevChannel::inputH264(const char* pcData, int iDataLen, uint32_t dts,uint32 } else { prefixeSize = 0; } - inputFrame(std::make_shared((char *)pcData,iDataLen,dts,pts,prefixeSize)); + + H264Frame::Ptr frame = std::make_shared(); + frame->_dts = dts; + frame->_pts = pts; + frame->_buffer.assign("\x00\x00\x00\x01",4); + frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize); + frame->_prefix_size = 4; + inputFrame(frame); } void DevChannel::inputH265(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) { @@ -122,7 +129,14 @@ void DevChannel::inputH265(const char* pcData, int iDataLen, uint32_t dts,uint32 } else { prefixeSize = 0; } - inputFrame(std::make_shared((char *)pcData,iDataLen,dts,pts,prefixeSize)); + + H265Frame::Ptr frame = std::make_shared(); + frame->_dts = dts; + frame->_pts = pts; + frame->_buffer.assign("\x00\x00\x00\x01",4); + frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize); + frame->_prefix_size = 4; + inputFrame(frame); } void DevChannel::inputAAC(const char* pcData, int iDataLen, uint32_t uiStamp,bool withAdtsHeader) { diff --git a/src/Common/MediaSink.cpp b/src/Common/MediaSink.cpp index 64049dc5..eb7cf533 100644 --- a/src/Common/MediaSink.cpp +++ b/src/Common/MediaSink.cpp @@ -26,7 +26,11 @@ #include "MediaSink.h" //最多等待未初始化的Track 10秒,超时之后会忽略未初始化的Track -#define MAX_WAIT_MS 10000 +#define MAX_WAIT_MS_READY 10000 + +//如果添加Track,最多等待3秒 +#define MAX_WAIT_MS_ADD_TRACK 3000 + namespace mediakit{ @@ -34,23 +38,16 @@ void MediaSink::addTrack(const Track::Ptr &track_in) { lock_guard lck(_mtx); //克隆Track,只拷贝其数据,不拷贝其数据转发关系 auto track = track_in->clone(); - auto codec_id = track->getCodecId(); _track_map[codec_id] = track; - auto lam = [this,track](){ + _allTrackReady = false; + _trackReadyCallback[codec_id] = [this, track]() { onTrackReady(track); }; - if(track->ready()){ - lam(); - }else{ - _anyTrackUnReady = true; - _allTrackReady = false; - _trackReadyCallback[codec_id] = lam; - _ticker.resetTime(); - } + _ticker.resetTime(); - track->addDelegate(std::make_shared([this](const Frame::Ptr &frame){ - if(!_anyTrackUnReady){ + track->addDelegate(std::make_shared([this](const Frame::Ptr &frame) { + if (_allTrackReady) { onTrackFrame(frame); } })); @@ -58,7 +55,6 @@ void MediaSink::addTrack(const Track::Ptr &track_in) { void MediaSink::resetTracks() { lock_guard lck(_mtx); - _anyTrackUnReady = false; _allTrackReady = false; _track_map.clear(); _trackReadyCallback.clear(); @@ -83,26 +79,50 @@ void MediaSink::inputFrame(const Frame::Ptr &frame) { } } - if(!_allTrackReady && (_trackReadyCallback.empty() || _ticker.elapsedTime() > MAX_WAIT_MS)){ - _allTrackReady = true; - _anyTrackUnReady = false; - if(!_trackReadyCallback.empty()){ - //这是超时强制忽略未准备好的Track - _trackReadyCallback.clear(); - //移除未准备好的Track - for(auto it = _track_map.begin() ; it != _track_map.end() ; ){ - if(!it->second->ready()){ - it = _track_map.erase(it); - continue; - } - ++it; - } + if(!_allTrackReady){ + if(_ticker.elapsedTime() > MAX_WAIT_MS_READY){ + //如果超过规定时间,那么不再等待并忽略未准备好的Track + emitAllTrackReady(); + return; } - if(!_track_map.empty()){ - //最少有一个有效的Track - onAllTrackReady(); + if(!_trackReadyCallback.empty()){ + //在超时时间内,如果存在未准备好的Track,那么继续等待 + return; } + + if(_track_map.size() == 2){ + //如果已经添加了音视频Track,并且不存在未准备好的Track,那么说明所有Track都准备好了 + emitAllTrackReady(); + return; + } + + if(_track_map.size() == 1 && _ticker.elapsedTime() > MAX_WAIT_MS_ADD_TRACK){ + //如果只有一个Track,那么在该Track添加后,我们最多还等待若干时间(可能后面还会添加Track) + emitAllTrackReady(); + return; + } + } +} + +void MediaSink::emitAllTrackReady() { + _allTrackReady = true; + if(!_trackReadyCallback.empty()){ + //这是超时强制忽略未准备好的Track + _trackReadyCallback.clear(); + //移除未准备好的Track + for(auto it = _track_map.begin() ; it != _track_map.end() ; ){ + if(!it->second->ready()){ + it = _track_map.erase(it); + continue; + } + ++it; + } + } + + if(!_track_map.empty()){ + //最少有一个有效的Track + onAllTrackReady(); } } diff --git a/src/Common/MediaSink.h b/src/Common/MediaSink.h index 64317385..e37980e1 100644 --- a/src/Common/MediaSink.h +++ b/src/Common/MediaSink.h @@ -109,12 +109,13 @@ protected: * @param frame */ virtual void onTrackFrame(const Frame::Ptr &frame) {}; +private: + void emitAllTrackReady(); private: mutable recursive_mutex _mtx; map _track_map; map > _trackReadyCallback; bool _allTrackReady = false; - bool _anyTrackUnReady = false; Ticker _ticker; }; diff --git a/src/Common/Stamp.cpp b/src/Common/Stamp.cpp index 9d2c2d45..a0017752 100644 --- a/src/Common/Stamp.cpp +++ b/src/Common/Stamp.cpp @@ -26,14 +26,18 @@ #include "Stamp.h" -#define MAX_DELTA_STAMP 300 +#define MAX_DELTA_STAMP 1000 +#define MAX_CTS 500 +#define ABS(x) ((x) > 0 ? (x) : (-x)) namespace mediakit { int64_t DeltaStamp::deltaStamp(int64_t stamp) { if(!_last_stamp){ //第一次计算时间戳增量,时间戳增量为0 - _last_stamp = stamp; + if(stamp){ + _last_stamp = stamp; + } return 0; } @@ -75,7 +79,7 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, dts_out = _relativeStamp; //////////////以下是播放时间戳的计算////////////////// - if(pts_dts_diff > MAX_DELTA_STAMP || pts_dts_diff < -MAX_DELTA_STAMP){ + if(ABS(pts_dts_diff) > MAX_CTS){ //如果差值太大,则认为由于回环导致时间戳错乱了 pts_dts_diff = 0; } @@ -96,4 +100,58 @@ int64_t Stamp::getRelativeStamp() const { } +bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){ + bool ret = false; + if(pts == _last_pts){ + //pts未变,返回上次结果 + if(_last_dts){ + dts = _last_dts; + ret = true; + } + return ret; + } + + ret = getDts_l(pts,dts); + if(ret){ + //保存本次结果 + _last_dts = dts; + } + //记录上次pts + _last_pts = pts; + return ret; +} + +bool DtsGenerator::getDts_l(uint32_t pts, uint32_t &dts){ + if(_sorter_max_size == 1){ + //没有B帧 + dts = pts; + return true; + } + + if(!_sorter_max_size){ + if(pts > _last_max_pts){ + if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){ + _sorter_max_size = _frames_since_last_max_pts; + _dts_pts_offset = (pts - _last_max_pts) / 2; + } + _frames_since_last_max_pts = 0; + _last_max_pts = pts; + } + ++_frames_since_last_max_pts; + } + + _pts_sorter.emplace(pts); + if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){ + auto it = _pts_sorter.begin(); + dts = *it + _dts_pts_offset; + if(dts > pts){ + //dts不能大于pts(基本不可能到达这个逻辑) + dts = pts; + } + _pts_sorter.erase(it); + return true; + } + return false; +} + }//namespace mediakit \ No newline at end of file diff --git a/src/Common/Stamp.h b/src/Common/Stamp.h index 09e77338..43d11159 100644 --- a/src/Common/Stamp.h +++ b/src/Common/Stamp.h @@ -27,8 +27,9 @@ #ifndef ZLMEDIAKIT_STAMP_H #define ZLMEDIAKIT_STAMP_H -#include "Util/TimeTicker.h" +#include #include +#include "Util/TimeTicker.h" using namespace toolkit; namespace mediakit { @@ -88,6 +89,27 @@ private: SmoothTicker _ticker; }; + +class DtsGenerator{ +public: + DtsGenerator() = default; + ~DtsGenerator() = default; + bool getDts(uint32_t pts, uint32_t &dts); +private: + bool getDts_l(uint32_t pts, uint32_t &dts); +private: + uint32_t _dts_pts_offset = 0; + uint32_t _last_dts = 0; + uint32_t _last_pts = 0; + uint32_t _last_max_pts = 0; + int _frames_since_last_max_pts = 0; + int _sorter_max_size = 0; + int _count_sorter_max_size = 0; + set _pts_sorter; + + +}; + }//namespace mediakit #endif //ZLMEDIAKIT_STAMP_H diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 74e92e72..89ad9bbf 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -158,7 +158,6 @@ 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 kModifyStamp = RTSP_FIELD"modifyStamp"; onceToken token([](){ //默认Md5方式认证 @@ -166,7 +165,6 @@ onceToken token([](){ mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; mINI::Instance()[kDirectProxy] = 1; - mINI::Instance()[kModifyStamp] = false; },nullptr); } //namespace Rtsp diff --git a/src/Common/config.h b/src/Common/config.h index 7cadbf70..d8d51e35 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -232,8 +232,6 @@ extern const string kKeepAliveSecond; //假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理 //默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的 extern const string kDirectProxy; -//rtsp推流是否修改时间戳 -extern const string kModifyStamp; } //namespace Rtsp ////////////RTMP服务器配置/////////// diff --git a/src/Extension/AAC.h b/src/Extension/AAC.h index fe78509e..063756f6 100644 --- a/src/Extension/AAC.h +++ b/src/Extension/AAC.h @@ -104,7 +104,6 @@ public: //表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据) unsigned int no_raw_data_blocks_in_frame; //2 uimsfb unsigned char buffer[2 * 1024 + 7]; - uint16_t sequence; uint32_t timeStamp; uint32_t iPrefixSize = 7; } ; diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index 71ea9e66..c3efddb1 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -102,7 +102,7 @@ void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { rtmpPkt->bodySize = rtmpPkt->strBuf.size(); rtmpPkt->chunkId = CHUNK_AUDIO; rtmpPkt->streamId = STREAM_MEDIA; - rtmpPkt->timeStamp = frame->stamp(); + rtmpPkt->timeStamp = frame->dts(); rtmpPkt->typeId = MSG_AUDIO; RtmpCodec::inputRtmp(rtmpPkt, false); } diff --git a/src/Extension/AACRtp.cpp b/src/Extension/AACRtp.cpp index 2f001959..a84c22be 100644 --- a/src/Extension/AACRtp.cpp +++ b/src/Extension/AACRtp.cpp @@ -41,7 +41,7 @@ AACRtpEncoder::AACRtpEncoder(uint32_t ui32Ssrc, void AACRtpEncoder::inputFrame(const Frame::Ptr &frame) { GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS); - auto uiStamp = frame->stamp(); + auto uiStamp = frame->dts(); auto pcData = frame->data() + frame->prefixSize(); auto iLen = frame->size() - frame->prefixSize(); @@ -149,7 +149,6 @@ bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) { memcpy(_adts->buffer + _adts->aac_frame_length, rtp_packet_payload + next_aac_payload_offset, cur_aac_payload_len); _adts->aac_frame_length += (cur_aac_payload_len); if (rtppack->mark == true) { - _adts->sequence = rtppack->sequence; _adts->timeStamp = rtppack->timeStamp; writeAdtsHeader(*_adts, _adts->buffer); onGetAAC(_adts); diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 5e5121f9..d4cbca6b 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -81,13 +81,6 @@ class Frame : public Buffer, public CodecInfo { public: typedef std::shared_ptr Ptr; virtual ~Frame(){} - /** - * 时间戳,已经废弃,请使用dts() 、pts()接口 - */ - inline uint32_t stamp() const { - return dts(); - }; - /** * 返回解码时间戳,单位毫秒 diff --git a/src/Extension/H264.h b/src/Extension/H264.h index 4bb8ea1c..b4c71a6b 100644 --- a/src/Extension/H264.h +++ b/src/Extension/H264.h @@ -49,24 +49,25 @@ public: NAL_SPS = 7, NAL_PPS = 8, NAL_IDR = 5, + NAL_SEI = 6, } NalType; char *data() const override{ - return (char *)buffer.data(); + return (char *)_buffer.data(); } uint32_t size() const override { - return buffer.size(); + return _buffer.size(); } uint32_t dts() const override { - return timeStamp; + return _dts; } uint32_t pts() const override { - return ptsStamp ? ptsStamp : timeStamp; + return _pts ? _pts : _dts; } uint32_t prefixSize() const override{ - return iPrefixSize; + return _prefix_size; } TrackType getTrackType() const override{ @@ -78,11 +79,11 @@ public: } bool keyFrame() const override { - return H264_TYPE(buffer[iPrefixSize]) == H264Frame::NAL_IDR; + return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR; } bool configFrame() const override{ - switch(H264_TYPE(buffer[iPrefixSize]) ){ + switch(H264_TYPE(_buffer[_prefix_size]) ){ case H264Frame::NAL_SPS: case H264Frame::NAL_PPS: return true; @@ -91,10 +92,10 @@ public: } } public: - uint32_t timeStamp; - uint32_t ptsStamp = 0; - string buffer; - uint32_t iPrefixSize = 4; + uint32_t _dts = 0; + uint32_t _pts = 0; + uint32_t _prefix_size = 4; + string _buffer; }; @@ -339,19 +340,19 @@ private: if(!_sps.empty()){ auto spsFrame = std::make_shared(); - spsFrame->iPrefixSize = 4; - spsFrame->buffer.assign("\x0\x0\x0\x1",4); - spsFrame->buffer.append(_sps); - spsFrame->timeStamp = frame->stamp(); + spsFrame->_prefix_size = 4; + spsFrame->_buffer.assign("\x0\x0\x0\x1",4); + spsFrame->_buffer.append(_sps); + spsFrame->_dts = frame->dts(); VideoTrack::inputFrame(spsFrame); } if(!_pps.empty()){ auto ppsFrame = std::make_shared(); - ppsFrame->iPrefixSize = 4; - ppsFrame->buffer.assign("\x0\x0\x0\x1",4); - ppsFrame->buffer.append(_pps); - ppsFrame->timeStamp = frame->stamp(); + ppsFrame->_prefix_size = 4; + ppsFrame->_buffer.assign("\x0\x0\x0\x1",4); + ppsFrame->_buffer.append(_pps); + ppsFrame->_dts = frame->dts(); VideoTrack::inputFrame(ppsFrame); } } diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index 3ab77554..c995317f 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -35,8 +35,8 @@ H264RtmpDecoder::H264RtmpDecoder() { H264Frame::Ptr H264RtmpDecoder::obtainFrame() { //从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 auto frame = obtainObj(); - frame->buffer.clear(); - frame->iPrefixSize = 4; + frame->_buffer.clear(); + frame->_prefix_size = 4; return frame; } @@ -78,10 +78,10 @@ bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) { inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) { #if 1 - _h264frame->timeStamp = dts; - _h264frame->ptsStamp = pts; - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); //添加264头 - _h264frame->buffer.append(pcData, iLen); + _h264frame->_dts = dts; + _h264frame->_pts = pts; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); //添加264头 + _h264frame->_buffer.append(pcData, iLen); //写入环形缓存 RtmpCodec::inputFrame(_h264frame); @@ -140,7 +140,11 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { } } - if(_lastPacket && _lastPacket->timeStamp != frame->stamp()) { + if(type == H264Frame::NAL_SEI){ + return; + } + + if(_lastPacket && _lastPacket->timeStamp != frame->dts()) { RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame()); _lastPacket = nullptr; } @@ -149,7 +153,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { //I or P or B frame int8_t flags = 7; //h.264 bool is_config = false; - flags |= ((frame->keyFrame() ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); + flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); _lastPacket = ResourcePoolHelper::obtainObj(); _lastPacket->strBuf.clear(); @@ -161,7 +165,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { _lastPacket->chunkId = CHUNK_VIDEO; _lastPacket->streamId = STREAM_MEDIA; - _lastPacket->timeStamp = frame->stamp(); + _lastPacket->timeStamp = frame->dts(); _lastPacket->typeId = MSG_VIDEO; } diff --git a/src/Extension/H264Rtp.cpp b/src/Extension/H264Rtp.cpp index c9861f16..f90f3c56 100644 --- a/src/Extension/H264Rtp.cpp +++ b/src/Extension/H264Rtp.cpp @@ -70,8 +70,8 @@ H264RtpDecoder::H264RtpDecoder() { H264Frame::Ptr H264RtpDecoder::obtainFrame() { //从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 auto frame = ResourcePoolHelper::obtainObj(); - frame->buffer.clear(); - frame->iPrefixSize = 4; + frame->_buffer.clear(); + frame->_prefix_size = 4; return frame; } @@ -113,9 +113,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (nal.type >= 0 && nal.type < 24) { //a full frame - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); - _h264frame->buffer.append((char *)frame, length); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h264frame->_buffer.append((char *)frame, length); + _h264frame->_pts = rtppack->timeStamp; auto key = _h264frame->keyFrame(); onGetH264(_h264frame); return (key); //i frame @@ -142,9 +142,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { //过小的帧丢弃 NALU nal; MakeNalu(ptr[0], nal); - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); - _h264frame->buffer.append((char *)ptr, len); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h264frame->_buffer.append((char *)ptr, len); + _h264frame->_pts = rtppack->timeStamp; if(nal.type == H264Frame::NAL_IDR){ haveIDR = true; } @@ -162,10 +162,10 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (fu.S) { //该帧的第一个rtp包 FU-A start char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type); - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); - _h264frame->buffer.push_back(tmp); - _h264frame->buffer.append((char *)frame + 2, length - 2); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h264frame->_buffer.push_back(tmp); + _h264frame->_buffer.append((char *)frame + 2, length - 2); + _h264frame->_pts = rtppack->timeStamp; //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return _h264frame->keyFrame(); @@ -173,22 +173,22 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) { //中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃 - _h264frame->buffer.clear(); + _h264frame->_buffer.clear(); WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃"; return false; } if (!fu.E) { //该帧的中间rtp包 FU-A mid - _h264frame->buffer.append((char *)frame + 2, length - 2); + _h264frame->_buffer.append((char *)frame + 2, length - 2); //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return false; } //该帧最后一个rtp包 FU-A end - _h264frame->buffer.append((char *)frame + 2, length - 2); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.append((char *)frame + 2, length - 2); + _h264frame->_pts = rtppack->timeStamp; auto key = _h264frame->keyFrame(); onGetH264(_h264frame); return key; @@ -209,8 +209,12 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { } void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) { - //写入环形缓存 - RtpCodec::inputFrame(frame); + //根据pts计算dts + auto flag = _dts_generator.getDts(frame->_pts,frame->_dts); + if(flag){ + //写入环形缓存 + RtpCodec::inputFrame(frame); + } _h264frame = obtainFrame(); } @@ -232,7 +236,7 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ui32Ssrc, void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS); auto pcData = frame->data() + frame->prefixSize(); - auto uiStamp = frame->stamp(); + auto uiStamp = frame->pts(); auto iLen = frame->size() - frame->prefixSize(); //获取NALU的5bit 帧类型 unsigned char naluType = H264_TYPE(pcData[0]); diff --git a/src/Extension/H264Rtp.h b/src/Extension/H264Rtp.h index 5ccac81d..da4aa2d6 100644 --- a/src/Extension/H264Rtp.h +++ b/src/Extension/H264Rtp.h @@ -30,6 +30,7 @@ #include "Rtsp/RtpCodec.h" #include "Util/ResourcePool.h" #include "Extension/H264.h" +#include "Common/Stamp.h" using namespace toolkit; namespace mediakit{ @@ -66,6 +67,7 @@ private: H264Frame::Ptr obtainFrame(); private: H264Frame::Ptr _h264frame; + DtsGenerator _dts_generator; int _lastSeq = 0; }; diff --git a/src/Extension/H265.h b/src/Extension/H265.h index 021ef3d1..80591e32 100644 --- a/src/Extension/H265.h +++ b/src/Extension/H265.h @@ -74,23 +74,23 @@ public: } NaleType; char *data() const override { - return (char *) buffer.data(); + return (char *) _buffer.data(); } uint32_t size() const override { - return buffer.size(); + return _buffer.size(); } uint32_t dts() const override { - return timeStamp; + return _dts; } uint32_t pts() const override { - return ptsStamp ? ptsStamp : timeStamp; + return _pts ? _pts : _dts; } uint32_t prefixSize() const override { - return iPrefixSize; + return _prefix_size; } TrackType getTrackType() const override { @@ -102,11 +102,11 @@ public: } bool keyFrame() const override { - return isKeyFrame(H265_TYPE(buffer[iPrefixSize])); + return isKeyFrame(H265_TYPE(_buffer[_prefix_size])); } bool configFrame() const override{ - switch(H265_TYPE(buffer[iPrefixSize])){ + switch(H265_TYPE(_buffer[_prefix_size])){ case H265Frame::NAL_VPS: case H265Frame::NAL_SPS: case H265Frame::NAL_PPS: @@ -131,10 +131,10 @@ public: } public: - uint32_t timeStamp; - uint32_t ptsStamp = 0; - string buffer; - uint32_t iPrefixSize = 4; + uint32_t _dts = 0; + uint32_t _pts = 0; + uint32_t _prefix_size = 4; + string _buffer; }; @@ -356,27 +356,27 @@ private: } if(!_vps.empty()){ auto vpsFrame = std::make_shared(); - vpsFrame->iPrefixSize = 4; - vpsFrame->buffer.assign("\x0\x0\x0\x1", 4); - vpsFrame->buffer.append(_vps); - vpsFrame->timeStamp = frame->stamp(); + vpsFrame->_prefix_size = 4; + vpsFrame->_buffer.assign("\x0\x0\x0\x1", 4); + vpsFrame->_buffer.append(_vps); + vpsFrame->_dts = frame->dts(); VideoTrack::inputFrame(vpsFrame); } if (!_sps.empty()) { auto spsFrame = std::make_shared(); - spsFrame->iPrefixSize = 4; - spsFrame->buffer.assign("\x0\x0\x0\x1", 4); - spsFrame->buffer.append(_sps); - spsFrame->timeStamp = frame->stamp(); + spsFrame->_prefix_size = 4; + spsFrame->_buffer.assign("\x0\x0\x0\x1", 4); + spsFrame->_buffer.append(_sps); + spsFrame->_dts = frame->dts(); VideoTrack::inputFrame(spsFrame); } if (!_pps.empty()) { auto ppsFrame = std::make_shared(); - ppsFrame->iPrefixSize = 4; - ppsFrame->buffer.assign("\x0\x0\x0\x1", 4); - ppsFrame->buffer.append(_pps); - ppsFrame->timeStamp = frame->stamp(); + ppsFrame->_prefix_size = 4; + ppsFrame->_buffer.assign("\x0\x0\x0\x1", 4); + ppsFrame->_buffer.append(_pps); + ppsFrame->_dts = frame->dts(); VideoTrack::inputFrame(ppsFrame); } } diff --git a/src/Extension/H265Rtp.cpp b/src/Extension/H265Rtp.cpp index 6d164dde..4b8df94c 100644 --- a/src/Extension/H265Rtp.cpp +++ b/src/Extension/H265Rtp.cpp @@ -70,8 +70,8 @@ H265RtpDecoder::H265RtpDecoder() { H265Frame::Ptr H265RtpDecoder::obtainFrame() { //从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 auto frame = ResourcePoolHelper::obtainObj(); - frame->buffer.clear(); - frame->iPrefixSize = 4; + frame->_buffer.clear(); + frame->_prefix_size = 4; return frame; } @@ -99,11 +99,11 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { MakeFU(frame[2], fu); if (fu.S) { //该帧的第一个rtp包 - _h265frame->buffer.assign("\x0\x0\x0\x1", 4); - _h265frame->buffer.push_back(fu.type << 1); - _h265frame->buffer.push_back(0x01); - _h265frame->buffer.append((char *) frame + 3, length - 3); - _h265frame->timeStamp = rtppack->timeStamp; + _h265frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h265frame->_buffer.push_back(fu.type << 1); + _h265frame->_buffer.push_back(0x01); + _h265frame->_buffer.append((char *) frame + 3, length - 3); + _h265frame->_pts = rtppack->timeStamp; //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return (_h265frame->keyFrame()); //i frame @@ -111,22 +111,22 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) { //中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃 - _h265frame->buffer.clear(); + _h265frame->_buffer.clear(); WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃"; return false; } if (!fu.E) { //该帧的中间rtp包 - _h265frame->buffer.append((char *) frame + 3, length - 3); + _h265frame->_buffer.append((char *) frame + 3, length - 3); //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return false; } //该帧最后一个rtp包 - _h265frame->buffer.append((char *) frame + 3, length - 3); - _h265frame->timeStamp = rtppack->timeStamp; + _h265frame->_buffer.append((char *) frame + 3, length - 3); + _h265frame->_pts = rtppack->timeStamp; auto key = _h265frame->keyFrame(); onGetH265(_h265frame); return key; @@ -134,9 +134,9 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { default: // 4.4.1. Single NAL Unit Packets (p24) //a full frame - _h265frame->buffer.assign("\x0\x0\x0\x1", 4); - _h265frame->buffer.append((char *)frame, length); - _h265frame->timeStamp = rtppack->timeStamp; + _h265frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h265frame->_buffer.append((char *)frame, length); + _h265frame->_pts = rtppack->timeStamp; auto key = _h265frame->keyFrame(); onGetH265(_h265frame); return key; @@ -144,8 +144,12 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { } void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) { - //写入环形缓存 - RtpCodec::inputFrame(frame); + //计算dts + auto flag = _dts_generator.getDts(frame->_pts,frame->_dts); + if(flag){ + //写入环形缓存 + RtpCodec::inputFrame(frame); + } _h265frame = obtainFrame(); } @@ -167,7 +171,7 @@ H265RtpEncoder::H265RtpEncoder(uint32_t ui32Ssrc, void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) { GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS); uint8_t *pcData = (uint8_t*)frame->data() + frame->prefixSize(); - auto uiStamp = frame->stamp(); + auto uiStamp = frame->pts(); auto iLen = frame->size() - frame->prefixSize(); unsigned char naluType = H265_TYPE(pcData[0]); //获取NALU的5bit 帧类型 uiStamp %= cycleMS; diff --git a/src/Extension/H265Rtp.h b/src/Extension/H265Rtp.h index 8b51f880..a2844092 100644 --- a/src/Extension/H265Rtp.h +++ b/src/Extension/H265Rtp.h @@ -30,6 +30,7 @@ #include "Rtsp/RtpCodec.h" #include "Util/ResourcePool.h" #include "Extension/H265.h" +#include "Common/Stamp.h" using namespace toolkit; @@ -67,6 +68,7 @@ private: H265Frame::Ptr obtainFrame(); private: H265Frame::Ptr _h265frame; + DtsGenerator _dts_generator; int _lastSeq = 0; }; diff --git a/src/Http/WebSocketClient.h b/src/Http/WebSocketClient.h index 85051fc3..e3d215a7 100644 --- a/src/Http/WebSocketClient.h +++ b/src/Http/WebSocketClient.h @@ -303,7 +303,7 @@ private: //拦截websocket数据接收 _onRecv = [this](const Buffer::Ptr &pBuf){ //解析websocket数据包 - WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size()); + this->WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size()); }; return; } diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index 5badcce0..6b5fca6e 100644 --- a/src/Player/PlayerProxy.cpp +++ b/src/Player/PlayerProxy.cpp @@ -219,16 +219,26 @@ public: virtual ~MuteAudioMaker(){} void inputFrame(const Frame::Ptr &frame) override { if(frame->getTrackType() == TrackVideo){ - auto iAudioIndex = frame->stamp() / MUTE_ADTS_DATA_MS; + auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS; if(_iAudioIndex != iAudioIndex){ _iAudioIndex = iAudioIndex; - auto aacFrame = std::make_shared((char *)MUTE_ADTS_DATA, - MUTE_ADTS_DATA_LEN, - _iAudioIndex * MUTE_ADTS_DATA_MS); + auto aacFrame = std::make_shared((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS); FrameDispatcher::inputFrame(aacFrame); } } } + +private: + class AACFrameCacheAble : public AACFrameNoCacheAble{ + public: + template + AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward(args)...){}; + virtual ~AACFrameCacheAble() = default; + + bool cacheAble() const override { + return true; + } + }; private: int _iAudioIndex = 0; }; diff --git a/src/Record/MP4Reader.cpp b/src/Record/MP4Reader.cpp index 9bf20525..020fa6bc 100644 --- a/src/Record/MP4Reader.cpp +++ b/src/Record/MP4Reader.cpp @@ -225,8 +225,10 @@ inline bool MP4Reader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) { uint32_t numBytes = _video_sample_max_size; MP4Duration pRenderingOffset; if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,&pRenderingOffset,&_bSyncSample)){ - if (!justSeekSyncFrame) { - uint32_t iOffset = 0; + if (!justSeekSyncFrame) { + uint32_t dts = (double) _video_ms * iIdx / _video_num_samples; + uint32_t pts = dts + pRenderingOffset / 90; + uint32_t iOffset = 0; while (iOffset < numBytes) { uint32_t iFrameLen; memcpy(&iFrameLen,pBytes + iOffset,4); @@ -235,8 +237,7 @@ inline bool MP4Reader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) { break; } memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4); - uint32_t dts = (double) _video_ms * iIdx / _video_num_samples; - writeH264(pBytes + iOffset, iFrameLen + 4, dts, dts + pRenderingOffset / 90); + writeH264(pBytes + iOffset, iFrameLen + 4, dts, pts); iOffset += (iFrameLen + 4); } }else if(_bSyncSample){ @@ -260,9 +261,10 @@ inline bool MP4Reader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) { uint8_t *pBytes = _adts.buffer + 7; if(MP4ReadSample(_hMP4File, _audio_trId, i + 1, &pBytes, &numBytes)){ if (!justSeekSyncFrame) { + uint32_t dts = (double) _audio_ms * i / _audio_num_samples; _adts.aac_frame_length = 7 + numBytes; writeAdtsHeader(_adts, _adts.buffer); - writeAAC(_adts.buffer, _adts.aac_frame_length, (double) _audio_ms * i / _audio_num_samples); + writeAAC(_adts.buffer, _adts.aac_frame_length, dts); } }else{ ErrorL << "读取音频失败:" << i+ 1; diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index 982d4401..928c9066 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -72,7 +72,6 @@ public: const string &stream_id, int ring_size = 0) : MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) { - _metadata = TitleMeta().getMetadata(); } virtual ~RtmpMediaSource() {} @@ -117,6 +116,9 @@ public: virtual void setMetaData(const AMFValue &metadata) { lock_guard lock(_mtx); _metadata = metadata; + if(_ring){ + regist(); + } } /** @@ -143,10 +145,9 @@ public: _ring = std::make_shared(_ring_size, std::move(lam)); onReaderChanged(0); - //如果输入了非config帧, - //那么说明不再可能获取config帧以及metadata, - //所以我们强制其为已注册 - regist(); + if(_metadata){ + regist(); + } } _track_stamps_map[pkt->typeId] = pkt->timeStamp; _ring->write(pkt, pkt->isVideoKeyFrame()); diff --git a/src/Rtmp/RtmpPlayerImp.h b/src/Rtmp/RtmpPlayerImp.h index b06be303..e90acc43 100644 --- a/src/Rtmp/RtmpPlayerImp.h +++ b/src/Rtmp/RtmpPlayerImp.h @@ -66,6 +66,7 @@ private: _pRtmpMediaSrc = dynamic_pointer_cast(_pMediaSrc); if(_pRtmpMediaSrc){ _pRtmpMediaSrc->setMetaData(val); + _set_meta_data = true; } _delegate.reset(new RtmpDemuxer); _delegate->loadMetaData(val); @@ -73,6 +74,10 @@ private: } void onMediaData(const RtmpPacket::Ptr &chunkData) override { if(_pRtmpMediaSrc){ + if(!_set_meta_data && !chunkData->isCfgFrame()){ + _set_meta_data = true; + _pRtmpMediaSrc->setMetaData(TitleMeta().getMetadata()); + } _pRtmpMediaSrc->onWrite(chunkData); } if(!_delegate){ @@ -83,6 +88,7 @@ private: } private: RtmpMediaSource::Ptr _pRtmpMediaSrc; + bool _set_meta_data = false; }; diff --git a/src/Rtmp/RtmpProtocol.cpp b/src/Rtmp/RtmpProtocol.cpp index ad21351f..4de21a47 100644 --- a/src/Rtmp/RtmpProtocol.cpp +++ b/src/Rtmp/RtmpProtocol.cpp @@ -332,7 +332,7 @@ void RtmpProtocol::handle_C0C1() { //complex handsharke handle_C1_complex(); #else - WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理!"; + WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理,flash播放器可能无法播放!"; handle_C1_simple(); #endif//ENABLE_OPENSSL } @@ -372,10 +372,10 @@ void RtmpProtocol::handle_C1_complex(){ check_C1_Digest(digest,c1_joined); send_complex_S0S1S2(0,digest); - InfoL << "schema0"; +// InfoL << "schema0"; }catch(std::exception &ex){ //貌似flash从来都不用schema1 - WarnL << "try rtmp complex schema0 failed:" << ex.what(); +// WarnL << "try rtmp complex schema0 failed:" << ex.what(); try{ /* c1s1 schema1 time: 4bytes @@ -389,9 +389,9 @@ void RtmpProtocol::handle_C1_complex(){ check_C1_Digest(digest,c1_joined); send_complex_S0S1S2(1,digest); - InfoL << "schema1"; +// InfoL << "schema1"; }catch(std::exception &ex){ - WarnL << "try rtmp complex schema1 failed:" << ex.what(); +// WarnL << "try rtmp complex schema1 failed:" << ex.what(); handle_C1_simple(); } } diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index e7e93688..061e512a 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -434,6 +434,7 @@ void RtmpSession::setMetaData(AMFDecoder &dec) { auto metadata = dec.load(); // dumpMetadata(metadata); _pPublisherSrc->setMetaData(metadata); + _set_meta_data = true; } void RtmpSession::onProcessCmd(AMFDecoder &dec) { @@ -452,7 +453,7 @@ void RtmpSession::onProcessCmd(AMFDecoder &dec) { 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; +// TraceP(this) << "can not support cmd:" << method; return; } _dNowReqID = dec.load(); @@ -473,10 +474,11 @@ void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) { case MSG_DATA3: { AMFDecoder dec(chunkData.strBuf, chunkData.typeId == MSG_CMD3 ? 1 : 0); std::string type = dec.load(); - TraceP(this) << "notify:" << type; if (type == "@setDataFrame") { setMetaData(dec); - } + }else{ + TraceP(this) << "unknown notify:" << type; + } } break; case MSG_AUDIO: @@ -490,6 +492,11 @@ void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) { _stamp[chunkData.typeId % 2].revise(chunkData.timeStamp, chunkData.timeStamp, dts_out, dts_out, true); chunkData.timeStamp = dts_out; } + + if(!_set_meta_data && !chunkData.isCfgFrame()){ + _set_meta_data = true; + _pPublisherSrc->setMetaData(TitleMeta().getMetadata()); + } _pPublisherSrc->onWrite(std::make_shared(std::move(chunkData))); } break; diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index 572d40f8..f5b6df79 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -95,6 +95,7 @@ private: std::string _strTcUrl; MediaInfo _mediaInfo; double _dNowReqID = 0; + bool _set_meta_data = false; Ticker _ticker;//数据接收时间 RingBuffer::RingReader::Ptr _pRingReader; std::shared_ptr _pPublisherSrc; diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index b9a3c424..6da40b06 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -932,12 +932,6 @@ inline void RtspSession::send_NotAcceptable() { void RtspSession::onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx) { - GET_CONFIG(bool,modify_stamp,Rtsp::kModifyStamp); - if(modify_stamp){ - int64_t dts_out; - _stamp[trackidx].revise(rtppt->timeStamp, rtppt->timeStamp, dts_out, dts_out, true); - rtppt->timeStamp = dts_out; - } _pushSrc->onWrite(rtppt, false); } inline void RtspSession::onRcvPeerUdpData(int intervaled, const Buffer::Ptr &pBuf, const struct sockaddr& addr) {