Commit f5efd232 by ziyue

rtsp/rtmp推流支持断连续推: #1240, #1300

parent 262af8df
......@@ -112,6 +112,8 @@
- 支持虚拟主机,可以隔离不同域名
- 支持按需拉流,无人观看自动关断拉流
- 支持先拉流后推流,提高及时推流画面打开率
- 支持先播放后推流
- 支持推流异常断开重连续推播放器不断开
- 提供c api sdk
- 支持FFmpeg拉流代理任意格式的流
- 支持http api生成并返回实时截图
......
......@@ -89,6 +89,10 @@ wait_track_ready_ms=10000
wait_add_track_ms=3000
#如果track未就绪,我们先缓存帧数据,但是有最大个数限制,防止内存溢出
unready_frame_cache=100
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
#置0关闭此特性(推流断开会导致立即断开播放器)
#此参数不应大于播放器超时时间
continue_push_ms=15000
[hls]
#hls写文件的buf大小,调整参数可以提高文件io性能
......
......@@ -85,6 +85,20 @@ const string& MediaSource::getId() const {
return _stream_id;
}
std::shared_ptr<void> MediaSource::getOwnership() {
if (_owned.test_and_set()) {
//已经被所有
return nullptr;
}
weak_ptr<MediaSource> weak_self = shared_from_this();
return std::shared_ptr<void>(this, [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){
return _speed[TrackVideo].getSpeed() + _speed[TrackAudio].getSpeed();
......@@ -419,8 +433,12 @@ void MediaSource::regist() {
//减小互斥锁临界区
lock_guard<recursive_mutex> lock(s_media_source_mtx);
auto &ref = s_media_source_map[_schema][_vhost][_app][_stream_id];
// 增加判断, 防止当前流已注册时再次注册
if (ref.lock() && ref.lock().get() != this) {
auto src = ref.lock();
if (src) {
if (src.get() == this) {
return;
}
//增加判断, 防止当前流已注册时再次注册
throw std::invalid_argument("media source already existed:" + _schema + "/" + _vhost + "/" + _app + "/" + _stream_id);
}
ref = shared_from_this();
......
......@@ -13,6 +13,7 @@
#include <mutex>
#include <string>
#include <atomic>
#include <memory>
#include <functional>
#include <unordered_map>
......@@ -219,6 +220,9 @@ public:
// 流id
const string& getId() const;
//获取对象所有权
std::shared_ptr<void> getOwnership();
// 获取所有Track
vector<Track::Ptr> getTracks(bool ready = true) const override;
......@@ -301,6 +305,7 @@ protected:
BytesSpeed _speed[TrackMax];
private:
atomic_flag _owned { false };
time_t _create_stamp;
Ticker _ticker;
string _schema;
......
......@@ -78,6 +78,7 @@ const string kEnableAudio = GENERAL_FIELD"enable_audio";
const string kWaitTrackReadyMS = GENERAL_FIELD"wait_track_ready_ms";
const string kWaitAddTrackMS = GENERAL_FIELD"wait_add_track_ms";
const string kUnreadyFrameCache = GENERAL_FIELD"unready_frame_cache";
const string kContinuePushMS = GENERAL_FIELD"continue_push_ms";
onceToken token([](){
mINI::Instance()[kFlowThreshold] = 1024;
......@@ -100,6 +101,7 @@ onceToken token([](){
mINI::Instance()[kWaitTrackReadyMS] = 10000;
mINI::Instance()[kWaitAddTrackMS] = 3000;
mINI::Instance()[kUnreadyFrameCache] = 100;
mINI::Instance()[kContinuePushMS] = 15 * 1000;
},nullptr);
}//namespace General
......
......@@ -193,6 +193,9 @@ extern const string kWaitTrackReadyMS;
extern const string kWaitAddTrackMS;
//如果track未就绪,我们先缓存帧数据,但是有最大个数限制(100帧时大约4秒),防止内存溢出
extern const string kUnreadyFrameCache;
//推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
//置0关闭此特性(推流断开会导致立即断开播放器)
extern const string kContinuePushMS;
}//namespace General
......
......@@ -85,6 +85,7 @@ private:
void setSocketFlags();
string getStreamId(const string &str);
void dumpMetadata(const AMFValue &metadata);
void sendStatus(const std::initializer_list<string> &key_value);
private:
bool _set_meta_data = false;
......@@ -97,9 +98,10 @@ private:
//数据接收超时计时器
Ticker _ticker;
MediaInfo _media_info;
std::weak_ptr<RtmpMediaSource> _player_src;
AMFValue _publisher_metadata;
std::shared_ptr<RtmpMediaSourceImp> _publisher_src;
std::weak_ptr<RtmpMediaSource> _play_src;
AMFValue _push_metadata;
RtmpMediaSourceImp::Ptr _push_src;
std::shared_ptr<void> _push_src_ownership;
RtmpMediaSource::RingType::RingReader::Ptr _ring_reader;
};
......
......@@ -60,9 +60,9 @@ RtspSession::~RtspSession() {
}
void RtspSession::onError(const SockException &err) {
bool isPlayer = !_push_src;
bool is_player = !_push_src;
uint64_t duration = _alive_ticker.createdTime() / 1000;
WarnP(this) << (isPlayer ? "RTSP播放器(" : "RTSP推流器(")
WarnP(this) << (is_player ? "RTSP播放器(" : "RTSP推流器(")
<< _media_info._vhost << "/"
<< _media_info._app << "/"
<< _media_info._streamid
......@@ -81,16 +81,24 @@ void RtspSession::onError(const SockException &err) {
}
//流量统计事件广播
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
if(_bytes_usage >= iFlowThreshold * 1024){
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, isPlayer, static_cast<SockInfo &>(*this));
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_bytes_usage >= iFlowThreshold * 1024) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, is_player, static_cast<SockInfo &>(*this));
}
GET_CONFIG(uint32_t, continue_push_ms, General::kContinuePushMS);
if (_push_src && continue_push_ms) {
//取消所有权
_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);
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) {
......@@ -198,20 +206,6 @@ void RtspSession::handleReq_Options(const Parser &parser) {
}
void RtspSession::handleReq_ANNOUNCE(const Parser &parser) {
auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA,
_media_info._vhost,
_media_info._app,
_media_info._streamid));
if (src) {
sendRtspResponse("406 Not Acceptable", {"Content-Type", "text/plain"}, "Already publishing.");
string err = StrPrinter << "ANNOUNCE:"
<< "Already publishing:"
<< _media_info._vhost << " "
<< _media_info._app << " "
<< _media_info._streamid << endl;
throw SockException(Err_shutdown, err);
}
auto full_url = parser.FullUrl();
_content_base = full_url;
if (end_with(full_url, ".sdp")) {
......@@ -227,21 +221,50 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) {
throw SockException(Err_shutdown, StrPrinter << err << ":" << full_url);
}
auto onRes = [this, parser, full_url](const string &err, bool enableHls, bool enableMP4){
bool authSuccess = err.empty();
if (!authSuccess) {
sendRtspResponse("401 Unauthorized", {"Content-Type", "text/plain"}, err);
auto onRes = [this, parser, full_url](const string &err, bool enableHls, bool enableMP4) {
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._streamid);
auto push_failed = (bool)src;
while (src) {
//尝试断连后继续推流
auto rtsp_src = dynamic_pointer_cast<RtspMediaSourceImp>(src);
if (!rtsp_src) {
//源不是rtmp推流产生的
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._vhost << " " << _media_info._app << " "
<< _media_info._streamid << endl;
throw SockException(Err_shutdown, err);
}
SdpParser sdpParser(parser.Content());
_sessionid = makeRandStr(12);
_sdp_track = sdpParser.getAvailableTrack();
if (_sdp_track.empty()) {
//sdp无效
// sdp无效
static constexpr auto err = "sdp中无有效track";
sendRtspResponse("403 Forbidden", {"Content-Type", "text/plain"}, err);
sendRtspResponse("403 Forbidden", { "Content-Type", "text/plain" }, err);
shutdown(SockException(Err_shutdown, StrPrinter << err << ":" << full_url));
return;
}
......@@ -249,11 +272,16 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) {
for (auto &track : _sdp_track) {
_rtcp_context.emplace_back(std::make_shared<RtcpContextForRecv>());
}
auto push_src = std::make_shared<RtspMediaSourceImp>(_media_info._vhost, _media_info._app, _media_info._streamid);
push_src->setListener(dynamic_pointer_cast<MediaSourceEvent>(shared_from_this()));
push_src->setProtocolTranslation(enableHls, enableMP4);
push_src->setSdp(parser.Content());
_push_src = std::move(push_src);
if (!_push_src) {
_push_src = std::make_shared<RtspMediaSourceImp>(_media_info._vhost, _media_info._app, _media_info._streamid);
//获取所有权
_push_src_ownership = _push_src->getOwnership();
_push_src->setProtocolTranslation(enableHls, enableMP4);
_push_src->setSdp(parser.Content());
}
_push_src->setListener(dynamic_pointer_cast<MediaSourceEvent>(shared_from_this()));
sendRtspResponse("200 OK");
};
......
......@@ -185,7 +185,9 @@ private:
//url解析后保存的相关信息
MediaInfo _media_info;
//rtsp推流相关绑定的源
RtspMediaSource::Ptr _push_src;
RtspMediaSourceImp::Ptr _push_src;
//推流器所有权
std::shared_ptr<void> _push_src_ownership;
//rtsp播放器绑定的直播源
std::weak_ptr<RtspMediaSource> _play_src;
//直播源读取器
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论