Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
xgj 2021-06-17 16:15:51 +08:00
commit e0d162c460
24 changed files with 235 additions and 194 deletions

View File

@ -179,7 +179,7 @@ API_EXPORT void API_CALL mk_media_set_on_regist(mk_media ctx, on_mk_media_source
typedef on_mk_media_source_send_rtp_result on_mk_media_send_rtp_result; typedef on_mk_media_source_send_rtp_result on_mk_media_send_rtp_result;
/** /**
* ps-rtp流 * ps-rtp流(ssrc区分多路)
* @param ctx * @param ctx
* @param dst_url ip或域名 * @param dst_url ip或域名
* @param dst_port * @param dst_port
@ -191,11 +191,12 @@ typedef on_mk_media_source_send_rtp_result on_mk_media_send_rtp_result;
API_EXPORT void API_CALL mk_media_start_send_rtp(mk_media ctx, const char *dst_url, uint16_t dst_port, const char *ssrc, int is_udp, on_mk_media_send_rtp_result cb, void *user_data); API_EXPORT void API_CALL mk_media_start_send_rtp(mk_media ctx, const char *dst_url, uint16_t dst_port, const char *ssrc, int is_udp, on_mk_media_send_rtp_result cb, void *user_data);
/** /**
* ps-rtp发送 * ps-rtp发送
* @param ctx * @param ctx
* @param ssrc rtp的ssrc10null或空字符串rtp推流
* @return 10 * @return 10
*/ */
API_EXPORT int API_CALL mk_media_stop_send_rtp(mk_media ctx); API_EXPORT int API_CALL mk_media_stop_send_rtp(mk_media ctx, const char *ssrc);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -59,7 +59,7 @@ API_EXPORT void API_CALL mk_proxy_player_play(mk_proxy_player ctx, const char *u
* mk_proxy_player_release函数MediaSource.close() * mk_proxy_player_release函数MediaSource.close()
* @param user_data mk_proxy_player_set_on_close函数设置 * @param user_data mk_proxy_player_set_on_close函数设置
*/ */
typedef void(API_CALL *on_mk_proxy_player_close)(void *user_data); typedef void(API_CALL *on_mk_proxy_player_close)(void *user_data, int err, const char *what, int sys_err);
/** /**
* MediaSource.close() * MediaSource.close()

View File

@ -193,16 +193,16 @@ API_EXPORT void API_CALL mk_media_start_send_rtp(mk_media ctx, const char *dst_u
assert(ctx && dst_url && ssrc); assert(ctx && dst_url && ssrc);
MediaHelper::Ptr* obj = (MediaHelper::Ptr*) ctx; MediaHelper::Ptr* obj = (MediaHelper::Ptr*) ctx;
//sender参数无用 //sender参数无用
(*obj)->getChannel()->startSendRtp(*(MediaSource *) 1, dst_url, dst_port, ssrc, is_udp, 0, [cb, user_data](uint16_t local_port, const SockException &ex){ (*obj)->getChannel()->startSendRtp(*MediaSource::NullMediaSource, dst_url, dst_port, ssrc, is_udp, 0, [cb, user_data](uint16_t local_port, const SockException &ex){
if (cb) { if (cb) {
cb(user_data, local_port, ex.getErrCode(), ex.what()); cb(user_data, local_port, ex.getErrCode(), ex.what());
} }
}); });
} }
API_EXPORT int API_CALL mk_media_stop_send_rtp(mk_media ctx){ API_EXPORT int API_CALL mk_media_stop_send_rtp(mk_media ctx, const char *ssrc){
assert(ctx); assert(ctx);
MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
//sender参数无用 //sender参数无用
return (*obj)->getChannel()->stopSendRtp(*(MediaSource *) 1, ""); return (*obj)->getChannel()->stopSendRtp(*MediaSource::NullMediaSource, ssrc ? ssrc : "");
} }

View File

@ -51,9 +51,9 @@ API_EXPORT void API_CALL mk_proxy_player_set_on_close(mk_proxy_player ctx, on_mk
PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx); PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx);
obj->getPoller()->async([obj,cb,user_data](){ obj->getPoller()->async([obj,cb,user_data](){
//切换线程再操作 //切换线程再操作
obj->setOnClose([cb,user_data](){ obj->setOnClose([cb,user_data](const SockException &ex){
if(cb){ if(cb){
cb(user_data); cb(user_data, ex.getErrCode(), ex.what(), ex.getCustomCode());
} }
}); });
}); });

View File

@ -672,7 +672,7 @@ void installWebApi() {
}); });
//被主动关闭拉流 //被主动关闭拉流
player->setOnClose([key](){ player->setOnClose([key](const SockException &ex){
lock_guard<recursive_mutex> lck(s_proxyMapMtx); lock_guard<recursive_mutex> lck(s_proxyMapMtx);
s_proxyMap.erase(key); s_proxyMap.erase(key);
}); });

View File

@ -192,11 +192,12 @@ private:
*/ */
class MediaSource: public TrackSource, public enable_shared_from_this<MediaSource> { class MediaSource: public TrackSource, public enable_shared_from_this<MediaSource> {
public: public:
typedef std::shared_ptr<MediaSource> Ptr; static constexpr MediaSource *NullMediaSource = nullptr;
typedef unordered_map<string, weak_ptr<MediaSource> > StreamMap; using Ptr = std::shared_ptr<MediaSource>;
typedef unordered_map<string, StreamMap > AppStreamMap; using StreamMap = unordered_map<string, weak_ptr<MediaSource> >;
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap; using AppStreamMap = unordered_map<string, StreamMap>;
typedef unordered_map<string, VhostAppStreamMap > SchemaVhostAppStreamMap; using VhostAppStreamMap = unordered_map<string, AppStreamMap>;
using SchemaVhostAppStreamMap = unordered_map<string, VhostAppStreamMap>;
MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id) ; MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id) ;
virtual ~MediaSource() ; virtual ~MediaSource() ;

View File

@ -338,7 +338,7 @@ bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type
return _muxer->isRecording(sender,type); return _muxer->isRecording(sender,type);
} }
void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, const string &ssrc, bool is_udp, uint16_t src_port, const function<void(uint16_t local_port, const SockException &ex)> &cb){ void MultiMediaSourceMuxer::startSendRtp(MediaSource &, const string &dst_url, uint16_t dst_port, const string &ssrc, bool is_udp, uint16_t src_port, const function<void(uint16_t local_port, const SockException &ex)> &cb){
#if defined(ENABLE_RTPPROXY) #if defined(ENABLE_RTPPROXY)
RtpSender::Ptr rtp_sender = std::make_shared<RtpSender>(atoi(ssrc.data())); RtpSender::Ptr rtp_sender = std::make_shared<RtpSender>(atoi(ssrc.data()));
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this(); weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
@ -362,10 +362,12 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const string &dst_
bool MultiMediaSourceMuxer::stopSendRtp(MediaSource &sender, const string &ssrc) { bool MultiMediaSourceMuxer::stopSendRtp(MediaSource &sender, const string &ssrc) {
#if defined(ENABLE_RTPPROXY) #if defined(ENABLE_RTPPROXY)
if (&sender != MediaSource::NullMediaSource) {
onceToken token(nullptr, [&]() { onceToken token(nullptr, [&]() {
//关闭rtp推流可能触发无人观看事件 //关闭rtp推流可能触发无人观看事件
MediaSourceEventInterceptor::onReaderChanged(sender, totalReaderCount()); MediaSourceEventInterceptor::onReaderChanged(sender, totalReaderCount());
}); });
}
if (ssrc.empty()) { if (ssrc.empty()) {
//关闭全部 //关闭全部
lock_guard<mutex> lck(_rtp_sender_mtx); lock_guard<mutex> lck(_rtp_sender_mtx);

View File

@ -108,45 +108,25 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
TrackType getTrackType(CodecId codecId) { TrackType getTrackType(CodecId codecId) {
switch (codecId) { switch (codecId) {
case CodecVP8: #define XX(name, type, value, str) case name : return type;
case CodecVP9: CODEC_MAP(XX)
case CodecH264: #undef XX
case CodecH265: return TrackVideo;
case CodecAAC:
case CodecG711A:
case CodecG711U:
case CodecOpus:
case CodecL16: return TrackAudio;
default : return TrackInvalid; default : return TrackInvalid;
} }
} }
const char *getCodecName(CodecId codec) { const char *getCodecName(CodecId codec) {
switch (codec) { switch (codec) {
case CodecH264 : return "H264"; #define XX(name, type, value, str) case name : return str;
case CodecH265 : return "H265"; CODEC_MAP(XX)
case CodecAAC : return "mpeg4-generic"; #undef XX
case CodecG711A : return "PCMA";
case CodecG711U : return "PCMU";
case CodecOpus : return "opus";
case CodecVP8 : return "VP8";
case CodecVP9 : return "VP9";
case CodecL16 : return "L16";
default : return "invalid"; default : return "invalid";
} }
} }
static map<string, CodecId, StrCaseCompare> codec_map = { #define XX(name, type, value, str) {str, name},
{"H264", CodecH264}, static map<string, CodecId, StrCaseCompare> codec_map = {CODEC_MAP(XX)};
{"H265", CodecH265}, #undef XX
{"mpeg4-generic", CodecAAC},
{"PCMA", CodecG711A},
{"PCMU", CodecG711U},
{"opus", CodecOpus},
{"VP8", CodecVP8},
{"VP9", CodecVP9},
{"L16", CodecL16}
};
CodecId getCodecId(const string &str){ CodecId getCodecId(const string &str){
auto it = codec_map.find(str); auto it = codec_map.find(str);
@ -209,11 +189,23 @@ bool FrameMerger::willFlush(const Frame::Ptr &frame) const{
//时间戳变化了 //时间戳变化了
return true; return true;
} }
if (frame->getCodecId() == CodecH264 && switch (frame->getCodecId()) {
H264_TYPE(frame->data()[frame->prefixSize()]) == H264Frame::NAL_B_P) { case CodecH264 : {
if (H264_TYPE(frame->data()[frame->prefixSize()]) == H264Frame::NAL_B_P) {
//如果是264的b/p帧那么也刷新输出 //如果是264的b/p帧那么也刷新输出
return true; return true;
} }
break;
}
case CodecH265 : {
if (H265_TYPE(frame->data()[frame->prefixSize()]) == H265Frame::NAL_TRAIL_R) {
//如果是265的TRAIL_R帧那么也刷新输出
return true;
}
break;
}
default : break;
}
return _frameCached.size() > kMaxFrameCacheSize; return _frameCached.size() > kMaxFrameCacheSize;
} }
default: /*不可达*/ assert(0); return true; default: /*不可达*/ assert(0); return true;

View File

@ -21,20 +21,6 @@ using namespace toolkit;
namespace mediakit{ namespace mediakit{
typedef enum {
CodecInvalid = -1,
CodecH264 = 0,
CodecH265,
CodecAAC,
CodecG711A,
CodecG711U,
CodecOpus,
CodecL16,
CodecVP8,
CodecVP9,
CodecMax
} CodecId;
typedef enum { typedef enum {
TrackInvalid = -1, TrackInvalid = -1,
TrackVideo = 0, TrackVideo = 0,
@ -44,6 +30,26 @@ typedef enum {
TrackMax TrackMax
} TrackType; } TrackType;
#define CODEC_MAP(XX) \
XX(CodecH264, TrackVideo, 0, "H264") \
XX(CodecH265, TrackVideo, 1, "H265") \
XX(CodecAAC, TrackAudio, 2, "mpeg4-generic") \
XX(CodecG711A, TrackAudio, 3, "PCMA") \
XX(CodecG711U, TrackAudio, 4, "PCMU") \
XX(CodecOpus, TrackAudio, 5, "opus") \
XX(CodecL16, TrackAudio, 6, "L16") \
XX(CodecVP8, TrackVideo, 7, "VP8") \
XX(CodecVP9, TrackVideo, 8, "VP9") \
XX(CodecAV1, TrackVideo, 9, "AV1X")
typedef enum {
CodecInvalid = -1,
#define XX(name, type, value, str) name = value,
CODEC_MAP(XX)
#undef XX
CodecMax
} CodecId;
/** /**
* *
*/ */

View File

@ -51,7 +51,7 @@ bool getHEVCInfo(const string &strVps, const string &strSps, int &iVideoWidth, i
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool H265Frame::keyFrame() const { bool H265Frame::keyFrame() const {
return isKeyFrame(H265_TYPE(_buffer[_prefix_size])); return isKeyFrame(H265_TYPE(_buffer[_prefix_size]), _buffer.data() + _prefix_size);
} }
bool H265Frame::configFrame() const { bool H265Frame::configFrame() const {
@ -63,9 +63,12 @@ bool H265Frame::configFrame() const {
} }
} }
bool H265Frame::isKeyFrame(int type) { bool H265Frame::isKeyFrame(int type, const char *ptr) {
if (!ptr || type != NAL_IDR_W_RADL) {
return type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23; return type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23;
} }
return (((*((uint8_t *) ptr + 2)) >> 7) & 0x01) == 1;
}
H265Frame::H265Frame(){ H265Frame::H265Frame(){
_codec_id = CodecH265; _codec_id = CodecH265;
@ -83,7 +86,7 @@ H265FrameNoCacheAble::H265FrameNoCacheAble(char *ptr, size_t size, uint32_t dts,
} }
bool H265FrameNoCacheAble::keyFrame() const { bool H265FrameNoCacheAble::keyFrame() const {
return H265Frame::isKeyFrame(H265_TYPE(((uint8_t *) _ptr)[_prefix_size])); return H265Frame::isKeyFrame(H265_TYPE(((uint8_t *) _ptr)[_prefix_size]), _ptr + _prefix_size);
} }
bool H265FrameNoCacheAble::configFrame() const { bool H265FrameNoCacheAble::configFrame() const {
@ -152,13 +155,12 @@ void H265Track::inputFrame(const Frame::Ptr &frame) {
void H265Track::inputFrame_l(const Frame::Ptr &frame) { void H265Track::inputFrame_l(const Frame::Ptr &frame) {
int type = H265_TYPE(((uint8_t *) frame->data() + frame->prefixSize())[0]); int type = H265_TYPE(((uint8_t *) frame->data() + frame->prefixSize())[0]);
if (H265Frame::isKeyFrame(type)) { if (H265Frame::isKeyFrame(type, frame->data() + frame->prefixSize())) {
insertConfigFrame(frame); insertConfigFrame(frame);
VideoTrack::inputFrame(frame); VideoTrack::inputFrame(frame);
_is_idr = true; _is_idr = true;
return; return;
} }
_is_idr = false; _is_idr = false;
//非idr帧 //非idr帧

View File

@ -61,7 +61,7 @@ public:
bool keyFrame() const override; bool keyFrame() const override;
bool configFrame() const override; bool configFrame() const override;
static bool isKeyFrame(int type); static bool isKeyFrame(int type, const char* ptr);
protected: protected:
friend class FrameImp; friend class FrameImp;

View File

@ -169,7 +169,7 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
return; return;
} }
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) { if (_lastPacket && (_lastPacket->time_stamp != frame->dts() || type == H265Frame::NAL_TRAIL_R)) {
RtmpCodec::inputRtmp(_lastPacket); RtmpCodec::inputRtmp(_lastPacket);
_lastPacket = nullptr; _lastPacket = nullptr;
} }

View File

@ -263,13 +263,13 @@ void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) {
//FU 第1个字节表明为FU //FU 第1个字节表明为FU
payload[0] = 49 << 1; payload[0] = 49 << 1;
//FU 第2个字节貌似固定为1 //FU 第2个字节貌似固定为1
payload[1] = 1; payload[1] = ptr[1];// 1;
//FU 第3个字节 //FU 第3个字节
payload[2] = s_e_flags; payload[2] = s_e_flags;
//H265 数据 //H265 数据
memcpy(payload + 3, ptr + offset, max_size); memcpy(payload + 3, ptr + offset, max_size);
//输入到rtp环形缓存 //输入到rtp环形缓存
RtpCodec::inputRtp(rtp, fu_start && H265Frame::isKeyFrame(nal_type)); RtpCodec::inputRtp(rtp, fu_start && H265Frame::isKeyFrame(nal_type, frame->data() + frame->prefixSize()));
} }
offset += max_size; offset += max_size;
@ -281,7 +281,7 @@ void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) {
} }
void H265RtpEncoder::makeH265Rtp(int nal_type,const void* data, size_t len, bool mark, bool first_packet, uint32_t uiStamp) { void H265RtpEncoder::makeH265Rtp(int nal_type,const void* data, size_t len, bool mark, bool first_packet, uint32_t uiStamp) {
RtpCodec::inputRtp(makeRtp(getTrackType(),data,len,mark,uiStamp),first_packet && H265Frame::isKeyFrame(nal_type)); RtpCodec::inputRtp(makeRtp(getTrackType(),data,len,mark,uiStamp),first_packet && H265Frame::isKeyFrame(nal_type, (const char*)data + prefixSize((const char*)data, len)));
} }
}//namespace mediakit }//namespace mediakit

View File

@ -111,7 +111,6 @@ protected:
* http头中带有Content-Length字段时 * http头中带有Content-Length字段时
*/ */
virtual ssize_t onResponseHeader(const string &status,const HttpHeader &headers){ virtual ssize_t onResponseHeader(const string &status,const HttpHeader &headers){
DebugL << status;
//无Content-Length字段时默认后面全是content //无Content-Length字段时默认后面全是content
return -1; return -1;
} }

View File

@ -55,14 +55,15 @@ PlayerProxy::PlayerProxy(const string &vhost, const string &app, const string &s
_enable_hls = enable_hls; _enable_hls = enable_hls;
_enable_mp4 = enable_mp4; _enable_mp4 = enable_mp4;
_retry_count = retry_count; _retry_count = retry_count;
_on_close = [](const SockException &) {};
} }
void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb) { void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb) {
_on_play = cb; _on_play = cb;
} }
void PlayerProxy::setOnClose(const function<void()> &cb){ void PlayerProxy::setOnClose(const function<void(const SockException &ex)> &cb) {
_on_close = cb; _on_close = cb ? cb : [](const SockException &) {};
} }
void PlayerProxy::play(const string &strUrlTmp) { void PlayerProxy::play(const string &strUrlTmp) {
@ -86,6 +87,9 @@ void PlayerProxy::play(const string &strUrlTmp) {
} else if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) { } else if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
// 播放失败,延时重试播放 // 播放失败,延时重试播放
strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++); strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++);
} else {
//达到了最大重试次数,回调关闭
strongSelf->_on_close(err);
} }
}); });
setOnShutdown([weakSelf, strUrlTmp, piFailedCnt](const SockException &err) { setOnShutdown([weakSelf, strUrlTmp, piFailedCnt](const SockException &err) {
@ -113,6 +117,9 @@ void PlayerProxy::play(const string &strUrlTmp) {
//播放异常中断,延时重试播放 //播放异常中断,延时重试播放
if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) { if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++); strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++);
} else {
//达到了最大重试次数,回调关闭
strongSelf->_on_close(err);
} }
}); });
MediaPlayer::play(strUrlTmp); MediaPlayer::play(strUrlTmp);
@ -174,9 +181,7 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
strongSelf->setMediaSource(nullptr); strongSelf->setMediaSource(nullptr);
strongSelf->teardown(); strongSelf->teardown();
}); });
if (_on_close) { _on_close(SockException(Err_shutdown, "closed by user"));
_on_close();
}
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
return true; return true;
} }

View File

@ -34,15 +34,15 @@ public:
/** /**
* play结果回调play执行之前有效 * play结果回调play执行之前有效
* @param cb * @param cb
*/ */
void setPlayCallbackOnce(const function<void(const SockException &ex)> &cb); void setPlayCallbackOnce(const function<void(const SockException &ex)> &cb);
/** /**
* *
* @param cb * @param cb
*/ */
void setOnClose(const function<void()> &cb); void setOnClose(const function<void(const SockException &ex)> &cb);
/** /**
* *
@ -76,7 +76,7 @@ private:
string _stream_id; string _stream_id;
string _pull_url; string _pull_url;
Timer::Ptr _timer; Timer::Ptr _timer;
function<void()> _on_close; function<void(const SockException &ex)> _on_close;
function<void(const SockException &ex)> _on_play; function<void(const SockException &ex)> _on_play;
MultiMediaSourceMuxer::Ptr _muxer; MultiMediaSourceMuxer::Ptr _muxer;
}; };

View File

@ -502,7 +502,6 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
void RtmpSession::onCmd_seek(AMFDecoder &dec) { void RtmpSession::onCmd_seek(AMFDecoder &dec) {
dec.load<AMFValue>();/* NULL */ dec.load<AMFValue>();/* NULL */
AMFValue status(AMF_OBJECT); AMFValue status(AMF_OBJECT);
AMFEncoder invoke;
status.set("level", "status"); status.set("level", "status");
status.set("code", "NetStream.Seek.Notify"); status.set("code", "NetStream.Seek.Notify");
status.set("description", "Seeking."); status.set("description", "Seeking.");

View File

@ -104,22 +104,19 @@ void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t byt
switch (codecid) { switch (codecid) {
case PSI_STREAM_H264: { case PSI_STREAM_H264: {
InfoL << "got video track: H264"; InfoL << "got video track: H264";
auto track = std::make_shared<H264Track>(); onTrack(std::make_shared<H264Track>());
onTrack(track);
break; break;
} }
case PSI_STREAM_H265: { case PSI_STREAM_H265: {
InfoL << "got video track: H265"; InfoL << "got video track: H265";
auto track = std::make_shared<H265Track>(); onTrack(std::make_shared<H265Track>());
onTrack(track);
break; break;
} }
case PSI_STREAM_AAC: { case PSI_STREAM_AAC: {
InfoL<< "got audio track: AAC"; InfoL<< "got audio track: AAC";
auto track = std::make_shared<AACTrack>(); onTrack(std::make_shared<AACTrack>());
onTrack(track);
break; break;
} }
@ -128,15 +125,13 @@ void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t byt
auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U; auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U;
InfoL << "got audio track: G711"; InfoL << "got audio track: G711";
//G711传统只支持 8000/1/16的规格FFmpeg貌似做了扩展但是这里不管它了 //G711传统只支持 8000/1/16的规格FFmpeg貌似做了扩展但是这里不管它了
auto track = std::make_shared<G711Track>(codec, 8000, 1, 16); onTrack(std::make_shared<G711Track>(codec, 8000, 1, 16));
onTrack(track);
break; break;
} }
case PSI_STREAM_AUDIO_OPUS: { case PSI_STREAM_AUDIO_OPUS: {
InfoL << "got audio track: opus"; InfoL << "got audio track: opus";
auto track = std::make_shared<OpusTrack>(); onTrack(std::make_shared<OpusTrack>());
onTrack(track);
break; break;
} }
@ -159,6 +154,9 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d
switch (codecid) { switch (codecid) {
case PSI_STREAM_H264: { case PSI_STREAM_H264: {
if (!_tracks[TrackVideo]) {
onTrack(std::make_shared<H264Track>());
}
auto frame = std::make_shared<H264FrameNoCacheAble>((char *) data, bytes, (uint32_t)dts, (uint32_t)pts, prefixSize((char *) data, bytes)); auto frame = std::make_shared<H264FrameNoCacheAble>((char *) data, bytes, (uint32_t)dts, (uint32_t)pts, prefixSize((char *) data, bytes));
_merger.inputFrame(frame,[this](uint32_t dts, uint32_t pts, const Buffer::Ptr &buffer, bool) { _merger.inputFrame(frame,[this](uint32_t dts, uint32_t pts, const Buffer::Ptr &buffer, bool) {
onFrame(std::make_shared<FrameWrapper<H264FrameNoCacheAble> >(buffer, dts, pts, prefixSize(buffer->data(), buffer->size()), 0)); onFrame(std::make_shared<FrameWrapper<H264FrameNoCacheAble> >(buffer, dts, pts, prefixSize(buffer->data(), buffer->size()), 0));
@ -167,6 +165,9 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d
} }
case PSI_STREAM_H265: { case PSI_STREAM_H265: {
if (!_tracks[TrackVideo]) {
onTrack(std::make_shared<H265Track>());
}
auto frame = std::make_shared<H265FrameNoCacheAble>((char *) data, bytes, (uint32_t)dts, (uint32_t)pts, prefixSize((char *) data, bytes)); auto frame = std::make_shared<H265FrameNoCacheAble>((char *) data, bytes, (uint32_t)dts, (uint32_t)pts, prefixSize((char *) data, bytes));
_merger.inputFrame(frame,[this](uint32_t dts, uint32_t pts, const Buffer::Ptr &buffer, bool) { _merger.inputFrame(frame,[this](uint32_t dts, uint32_t pts, const Buffer::Ptr &buffer, bool) {
onFrame(std::make_shared<FrameWrapper<H265FrameNoCacheAble> >(buffer, dts, pts, prefixSize(buffer->data(), buffer->size()), 0)); onFrame(std::make_shared<FrameWrapper<H265FrameNoCacheAble> >(buffer, dts, pts, prefixSize(buffer->data(), buffer->size()), 0));
@ -180,6 +181,9 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d
//这不是aac //这不是aac
break; break;
} }
if (!_tracks[TrackAudio]) {
onTrack(std::make_shared<AACTrack>());
}
onFrame(std::make_shared<FrameFromPtr>(CodecAAC, (char *) data, bytes, (uint32_t)dts, 0, ADTS_HEADER_LEN)); onFrame(std::make_shared<FrameFromPtr>(CodecAAC, (char *) data, bytes, (uint32_t)dts, 0, ADTS_HEADER_LEN));
break; break;
} }
@ -187,11 +191,18 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d
case PSI_STREAM_AUDIO_G711A: case PSI_STREAM_AUDIO_G711A:
case PSI_STREAM_AUDIO_G711U: { case PSI_STREAM_AUDIO_G711U: {
auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U; auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U;
if (!_tracks[TrackAudio]) {
//G711传统只支持 8000/1/16的规格FFmpeg貌似做了扩展但是这里不管它了
onTrack(std::make_shared<G711Track>(codec, 8000, 1, 16));
}
onFrame(std::make_shared<FrameFromPtr>(codec, (char *) data, bytes, (uint32_t)dts)); onFrame(std::make_shared<FrameFromPtr>(codec, (char *) data, bytes, (uint32_t)dts));
break; break;
} }
case PSI_STREAM_AUDIO_OPUS: { case PSI_STREAM_AUDIO_OPUS: {
if (!_tracks[TrackAudio]) {
onTrack(std::make_shared<OpusTrack>());
}
onFrame(std::make_shared<FrameFromPtr>(CodecOpus, (char *) data, bytes, (uint32_t)dts)); onFrame(std::make_shared<FrameFromPtr>(CodecOpus, (char *) data, bytes, (uint32_t)dts));
break; break;
} }
@ -212,6 +223,7 @@ void DecoderImp::onStream(int stream,int codecid,const void *extra,size_t bytes,
#endif #endif
void DecoderImp::onTrack(const Track::Ptr &track) { void DecoderImp::onTrack(const Track::Ptr &track) {
_tracks[track->getTrackType()] = track;
_sink->addTrack(track); _sink->addTrack(track);
} }

View File

@ -61,6 +61,7 @@ private:
MediaSinkInterface *_sink; MediaSinkInterface *_sink;
FrameMerger _merger{FrameMerger::none}; FrameMerger _merger{FrameMerger::none};
Ticker _last_unsported_print; Ticker _last_unsported_print;
Track::Ptr _tracks[TrackMax];
}; };
}//namespace mediakit }//namespace mediakit

View File

@ -61,7 +61,7 @@ bool RtpReceiver::handleOneRtp(int index, TrackType type, int sample_rate, uint8
_ssrc_alive[index].resetTime(); _ssrc_alive[index].resetTime();
} else { } else {
//ssrc错误 //ssrc错误
if (_ssrc_alive[index].elapsedTime() < 10 * 1000) { if (_ssrc_alive[index].elapsedTime() < 3 * 1000) {
//接受正确ssrc的rtp在10秒内那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp //接受正确ssrc的rtp在10秒内那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp
WarnL << "ssrc不匹配,rtp已丢弃:" << ssrc << " != " << _ssrc[index]; WarnL << "ssrc不匹配,rtp已丢弃:" << ssrc << " != " << _ssrc[index];
return false; return false;

View File

@ -99,11 +99,16 @@ void RtspSession::onManager() {
} }
} }
if ((_rtp_type == Rtsp::RTP_UDP || _push_src ) && _alive_ticker.elapsedTime() > keep_alive_sec * 1000 && _enable_send_rtp) { if (_push_src && _alive_ticker.elapsedTime() > keep_alive_sec * 1000) {
//如果是推流端或者rtp over udp类型的播放端那么就做超时检测 //推流超时
shutdown(SockException(Err_timeout,"rtp over udp session timeouted")); shutdown(SockException(Err_timeout, "pusher session timeouted"));
return; return;
} }
if (!_push_src && _rtp_type == Rtsp::RTP_UDP && _enable_send_rtp && _alive_ticker.elapsedTime() > keep_alive_sec * 4000) {
//rtp over udp播放器超时
shutdown(SockException(Err_timeout, "rtp over udp player timeouted"));
}
} }
void RtspSession::onRecv(const Buffer::Ptr &buf) { void RtspSession::onRecv(const Buffer::Ptr &buf) {

View File

@ -24,9 +24,9 @@ using namespace toolkit;
using namespace mediakit; using namespace mediakit;
//推流器,保持强引用 //推流器,保持强引用
MediaPusher::Ptr pusher; MediaPusher::Ptr g_pusher;
Timer::Ptr g_timer; Timer::Ptr g_timer;
MediaSource::Ptr g_src;
//声明函数 //声明函数
//推流失败或断开延迟2秒后重试推流 //推流失败或断开延迟2秒后重试推流
@ -46,27 +46,30 @@ void createPusher(const EventPoller::Ptr &poller,
const string &stream, const string &stream,
const string &filePath, const string &filePath,
const string &url) { const string &url) {
if (!g_src) {
//不限制APP名并且指定文件绝对路径 //不限制APP名并且指定文件绝对路径
auto src = MediaSource::createFromMP4(schema,vhost,app,stream,filePath, false); g_src = MediaSource::createFromMP4(schema, vhost, app, stream, filePath, false);
if(!src){ }
if (!g_src) {
//文件不存在 //文件不存在
WarnL << "MP4文件不存在:" << filePath; WarnL << "MP4文件不存在:" << filePath;
return; return;
} }
//创建推流器并绑定一个MediaSource //创建推流器并绑定一个MediaSource
pusher.reset(new MediaPusher(src,poller)); g_pusher.reset(new MediaPusher(g_src, poller));
//可以指定rtsp推流方式支持tcp和udp方式默认tcp //可以指定rtsp推流方式支持tcp和udp方式默认tcp
// (*pusher)[Client::kRtpType] = Rtsp::RTP_UDP; //(*g_pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
//设置推流中断处理逻辑 //设置推流中断处理逻辑
pusher->setOnShutdown([poller,schema,vhost,app,stream,filePath, url](const SockException &ex) { g_pusher->setOnShutdown([poller, schema, vhost, app, stream, filePath, url](const SockException &ex) {
WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what(); WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what();
//重新推流 //重新推流
rePushDelay(poller, schema, vhost, app, stream, filePath, url); rePushDelay(poller, schema, vhost, app, stream, filePath, url);
}); });
//设置发布结果处理逻辑 //设置发布结果处理逻辑
pusher->setOnPublished([poller,schema,vhost,app,stream,filePath, url](const SockException &ex) { g_pusher->setOnPublished([poller, schema, vhost, app, stream, filePath, url](const SockException &ex) {
if (ex) { if (ex) {
WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what(); WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what();
//如果发布失败,就重试 //如果发布失败,就重试
@ -75,7 +78,7 @@ void createPusher(const EventPoller::Ptr &poller,
InfoL << "Publish success,Please play with player:" << url; InfoL << "Publish success,Please play with player:" << url;
} }
}); });
pusher->publish(url); g_pusher->publish(url);
} }
//推流失败或断开延迟2秒后重试推流 //推流失败或断开延迟2秒后重试推流
@ -100,25 +103,30 @@ int domain(const string & filePath,const string & pushUrl){
//设置日志 //设置日志
Logger::Instance().add(std::make_shared<ConsoleChannel>()); Logger::Instance().add(std::make_shared<ConsoleChannel>());
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>()); Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
//循环点播mp4文件
mINI::Instance()[Record::kFileRepeat] = 1;
mINI::Instance()[General::kHlsDemand] = 1;
mINI::Instance()[General::kTSDemand] = 1;
mINI::Instance()[General::kFMP4Demand] = 1;
//mINI::Instance()[General::kRtspDemand] = 1;
//mINI::Instance()[General::kRtmpDemand] = 1;
auto poller = EventPollerPool::Instance().getPoller(); auto poller = EventPollerPool::Instance().getPoller();
//vhost/app/stream可以随便自己填现在不限制app应用名了 //vhost/app/stream可以随便自己填现在不限制app应用名了
createPusher(poller, FindField(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl); createPusher(poller, FindField(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl);
//设置退出信号处理函数 //设置退出信号处理函数
static semaphore sem; static semaphore sem;
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
sem.wait(); sem.wait();
pusher.reset(); g_pusher.reset();
g_timer.reset(); g_timer.reset();
return 0; return 0;
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
//可以使用test_server生成的mp4文件 //可以使用test_server生成的mp4文件
//文件使用绝对路径推流url支持rtsp和rtmp //文件使用绝对路径推流url支持rtsp和rtmp
return domain("/Users/xzl/git/ZLMediaKit/release/mac/Debug/www/record/live/rtsp_test1/2020-04-03/15-32-24.mp4","rtsp://127.0.0.1/live/rtsp_push"); return domain("/home/work/test2.mp4", "rtmp://127.0.0.1/live/rtsp_push");
} }

View File

@ -1421,7 +1421,7 @@ void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type){
} }
case TrackVideo: { case TrackVideo: {
//此处调整偏好的编码格式优先级 //此处调整偏好的编码格式优先级
preferred_codec = {CodecH264, CodecH265}; preferred_codec = {CodecH264, CodecH265, CodecAV1};
rtcp_fb = {SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb, "nack", "ccm fir", "nack pli"}; rtcp_fb = {SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb, "nack", "ccm fir", "nack pli"};
extmap = { extmap = {
RtpExtType::abs_send_time, RtpExtType::abs_send_time,

View File

@ -1,5 +1,5 @@
v=0 v=0
o=- 257973874652185302 2 IN IP4 127.0.0.1 o=- 8056465047193717905 2 IN IP4 127.0.0.1
s=- s=-
t=0 0 t=0 0
a=group:BUNDLE 0 1 a=group:BUNDLE 0 1
@ -8,10 +8,10 @@ a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0 c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:w2IN a=ice-ufrag:LtFR
a=ice-pwd:X7kCoPoI2NqW8kxuV9LHRR78 a=ice-pwd:sUVVlvhNoL2g/GL36TyfZGwP
a=ice-options:trickle a=ice-options:trickle
a=fingerprint:sha-256 7A:A7:A4:9A:BC:37:64:68:9C:48:E5:E9:9B:97:BD:88:17:3E:E5:44:29:4D:6D:BB:AB:2C:85:B8:DE:7A:15:B1 a=fingerprint:sha-256 21:21:07:E8:3C:D0:3B:45:87:9A:31:86:DE:4F:C1:BA:E1:0E:96:BA:41:36:6E:3A:3F:C6:C8:92:95:5B:71:5F
a=setup:actpass a=setup:actpass
a=mid:0 a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
@ -21,7 +21,7 @@ a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv a=sendrecv
a=msid:- 56049f63-4b19-45c4-aa0a-8895049b5430 a=msid:- 2ebeb64c-2eb3-4c4f-b5d5-d578245b969e
a=rtcp-mux a=rtcp-mux
a=rtpmap:111 opus/48000/2 a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc a=rtcp-fb:111 transport-cc
@ -38,17 +38,17 @@ a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000 a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000 a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000 a=rtpmap:126 telephone-event/8000
a=ssrc:3304267696 cname:sCv+hHL1+2UbfMTB a=ssrc:905965261 cname:7iEkMV0/MMfqSEce
a=ssrc:3304267696 msid:- 56049f63-4b19-45c4-aa0a-8895049b5430 a=ssrc:905965261 msid:- 2ebeb64c-2eb3-4c4f-b5d5-d578245b969e
a=ssrc:3304267696 mslabel:- a=ssrc:905965261 mslabel:-
a=ssrc:3304267696 label:56049f63-4b19-45c4-aa0a-8895049b5430 a=ssrc:905965261 label:2ebeb64c-2eb3-4c4f-b5d5-d578245b969e
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 118 114 115 116 m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123 118 114 115 116
c=IN IP4 0.0.0.0 c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:w2IN a=ice-ufrag:LtFR
a=ice-pwd:X7kCoPoI2NqW8kxuV9LHRR78 a=ice-pwd:sUVVlvhNoL2g/GL36TyfZGwP
a=ice-options:trickle a=ice-options:trickle
a=fingerprint:sha-256 7A:A7:A4:9A:BC:37:64:68:9C:48:E5:E9:9B:97:BD:88:17:3E:E5:44:29:4D:6D:BB:AB:2C:85:B8:DE:7A:15:B1 a=fingerprint:sha-256 21:21:07:E8:3C:D0:3B:45:87:9A:31:86:DE:4F:C1:BA:E1:0E:96:BA:41:36:6E:3A:3F:C6:C8:92:95:5B:71:5F
a=setup:actpass a=setup:actpass
a=mid:1 a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
@ -63,7 +63,7 @@ a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv a=sendrecv
a=msid:- a73b3f5f-007a-4a46-ac8b-582ca7fee460 a=msid:- f36bb41d-d05d-4310-b05b-7913d0029b18
a=rtcp-mux a=rtcp-mux
a=rtcp-rsize a=rtcp-rsize
a=rtpmap:96 VP8/90000 a=rtpmap:96 VP8/90000
@ -128,13 +128,21 @@ a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000 a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108 a=fmtp:109 apt=108
a=rtpmap:35 AV1X/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:124 H264/90000 a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032
a=rtpmap:119 rtx/90000 a=rtpmap:119 rtx/90000
a=fmtp:119 apt=124 a=fmtp:119 apt=124
a=rtpmap:123 H264/90000 a=rtpmap:123 H264/90000
@ -143,19 +151,19 @@ a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032
a=rtpmap:118 rtx/90000 a=rtpmap:118 rtx/90000
a=fmtp:118 apt=123 a=fmtp:118 apt=123
a=rtpmap:114 red/90000 a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000 a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114 a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000 a=rtpmap:116 ulpfec/90000
a=ssrc-group:FID 1128910219 3552306261 a=ssrc-group:FID 2678501654 361960375
a=ssrc:1128910219 cname:sCv+hHL1+2UbfMTB a=ssrc:2678501654 cname:7iEkMV0/MMfqSEce
a=ssrc:1128910219 msid:- a73b3f5f-007a-4a46-ac8b-582ca7fee460 a=ssrc:2678501654 msid:- f36bb41d-d05d-4310-b05b-7913d0029b18
a=ssrc:1128910219 mslabel:- a=ssrc:2678501654 mslabel:-
a=ssrc:1128910219 label:a73b3f5f-007a-4a46-ac8b-582ca7fee460 a=ssrc:2678501654 label:f36bb41d-d05d-4310-b05b-7913d0029b18
a=ssrc:3552306261 cname:sCv+hHL1+2UbfMTB a=ssrc:361960375 cname:7iEkMV0/MMfqSEce
a=ssrc:3552306261 msid:- a73b3f5f-007a-4a46-ac8b-582ca7fee460 a=ssrc:361960375 msid:- f36bb41d-d05d-4310-b05b-7913d0029b18
a=ssrc:3552306261 mslabel:- a=ssrc:361960375 mslabel:-
a=ssrc:3552306261 label:a73b3f5f-007a-4a46-ac8b-582ca7fee460 a=ssrc:361960375 label:f36bb41d-d05d-4310-b05b-7913d0029b18