ZLMediaKit源码分析(三)拉流创建

这篇具有很好参考价值的文章主要介绍了ZLMediaKit源码分析(三)拉流创建。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ZLMediaKit源码分析(一)服务启动
ZLMediaKit源码分析(二)推流创建
ZLMediaKit源码分析(三)拉流创建
ZLMediaKit源码分析(四)重封装
ZLMediaKit源码分析(五)代理服务
ZLMediaKit源码分析(六)http 点播

ZLMediaKit拉流结构体创建是在RtspSession::onRecv()之后。
如何触发onRecv()请参考ZLMediaKit源码分析(一)服务启动这里不在赘述。
RtspSession::onRecv()数据处理参考ZLMediaKit源码分析(二)推流创建这里也不再赘述。

继承关系

ZLMediaKit源码分析(三)拉流创建

RtspSession::onRecv()

RtspSession:: onWholeRtspPacket()

RtspSession::handleReq_Options()

RtspSession::handleReq_ANNOUNCE()上行请求

RtspSession::handleReq_SETUP() 建立链接

RtspSession::handleReq_RECORD() 上行推流

RtspSession::handleReq_Describe() 下行请求

src/Rtsp/RtspSession.cpp
void RtspSession::handleReq_Describe(const Parser &parser) {
    //该请求中的认证信息
    auto authorization = parser["Authorization"];
    weak_ptr<RtspSession> weak_self = dynamic_pointer_cast<RtspSession>(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 (!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnGetRtspRealm, _media_info, invoker, static_cast<SockInfo &>(*this))) {
            //无人监听此事件,说明无需认证
            invoker("");
        }
    }else{
        invoker(_rtsp_realm);
    }
}
src/Rtsp/RtspSession.cpp
void RtspSession::emitOnPlay(){
    weak_ptr<RtspSession> weak_self = dynamic_pointer_cast<RtspSession>(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 : NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast<SockInfo &>(*this));
    if (!flag) {
        //该事件无人监听,默认不鉴权
        onRes("");
    }
    //已经鉴权过了
    _emit_on_play = true;
}

创建推流结构体RtspMediaSource

src/Rtsp/RtspSession.cpp
void RtspSession::onAuthSuccess() {
    TraceP(this);
	weak_ptr<RtspSession> weak_self = dynamic_pointer_cast<RtspSession>(shared_from_this());
	// 查找流是否存在
	// 返回MediaSource::Ptr src
	MediaSource::findAsync(_media_info, weak_self.lock(), [weak_self](const MediaSource::Ptr &src){
    	// RtspSession strong_self
        auto strong_self = weak_self.lock();
        if(!strong_self){
            return;
        }

        // 转换为派生类的指针
        // 派生出RtspMediaSource??
        // 打印显示 rtsp_src依然是src的指针内容
        auto rtsp_src = dynamic_pointer_cast<RtspMediaSource>(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流
        // rtsp_src->getSdp() 返回字符串
        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<RtcpContextForSend>());
        }
        // 更新sessionid
        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);
        }

        // 返回数据包含sessionid???
        // sendRtspResponse 返回时会主动加上 sessionid 具体参考函数实现
        // if(!_sessionid.empty()){
        //    header.emplace("Session", _sessionid);
        // }
        strong_self->sendRtspResponse("200 OK",
                  {"Content-Base", strong_self->_content_base + "/",
                   "x-Accept-Retransmit","our-retransmit",
                   "x-Accept-Dynamic-Rate","1"
                                     },rtsp_src->getSdp());
    });
}

RtspSession::handleReq_Play() 下行播放

src/Rtsp/RtspSession.cpp
void RtspSession::handleReq_Play(const Parser &parser) {
    // parser中包含sessionid, 是的。
    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");
	}

	//直播源读取器
	// RtspMediaSource::RingType::RingReader::Ptr _play_reader;
	//rtsp播放器绑定的直播源
	// std::weak_ptr<RtspMediaSource> _play_src;
	// _play_src 创建应该在handleReq_Describe()
    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 = FindField(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<TrackType> 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=" << (int) (track->_time_stamp * (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();

	//直播源读取器
	// RtspMediaSource::RingType::RingReader::Ptr _play_reader;
    if (!_play_reader && _rtp_type != Rtsp::RTP_MULTICAST) {
        weak_ptr<RtspSession> weak_self = dynamic_pointer_cast<RtspSession>(shared_from_this());

		//直播源读取器
		// RtspMediaSource::RingType::RingReader::Ptr _play_reader;
		//rtsp播放器绑定的直播源
		// std::weak_ptr<RtspMediaSource> _play_src;
		// RtspMediaSource::getRing()返回 (RingType::Ptr)RtspMediaSource::_ring;
        _play_reader = play_src->getRing()->attach(getPoller(), use_gop);
        _play_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
        _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"));
        });
		// weak_ptr<RtspSession> weak_self = dynamic_pointer_cast<RtspSession>(shared_from_this());
        _play_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pack) {
            auto strong_self = weak_self.lock();
            if (!strong_self) {
                return;
            }
            strong_self->sendRtpPacket(pack);
        });
    }
}

RtspSession::_ring添加RingReaderDispatcher

看RingBuffer::_dispatcher_map的类型吧,一对多的关系。
std::unordered_map<EventPoller::Ptr, typename RingReaderDispatcher::Ptr, HashOfPtr> RingBuffer::_dispatcher_map;

src/Rtsp/RtspMediaSource.h
class RtspMediaSource : public MediaSource, public toolkit::RingDelegate<RtpPacket::Ptr>, private PacketCache<RtpPacket> {
public:
    using Ptr = std::shared_ptr<RtspMediaSource>;
    using RingDataType = std::shared_ptr<toolkit::List<RtpPacket::Ptr> >;
    using RingType = toolkit::RingBuffer<RingDataType>;
    ......
	const RingType::Ptr &getRing() const {
        return _ring;
}
private:
    ......
    RingType::Ptr _ring;
};

如果poller对应的RingReaderDispatcher不存在,则构造RingReaderDispatcher::Ptr dispatcher,new RingReaderDispatcher();
如果存在则在RingReaderDispatcher添加一个RingReader,dispatcher->attach(poller, use_cache);

3rdpart/ZLToolKit/src/Util/RingBuffer.h
template <typename T>
class RingBuffer : public std::enable_shared_from_this<RingBuffer<T>> {
public:
	......
    using RingReaderDispatcher = _RingReaderDispatcher<T>;
	......
	std::shared_ptr<RingReader> attach(const EventPoller::Ptr &poller, bool use_cache = true) {
        typename RingReaderDispatcher::Ptr dispatcher;
        {
            LOCK_GUARD(_mtx_map);
     		// 数组变量中增加一项。针对poller只会初始化一次
            auto &ref = _dispatcher_map[poller];
     		// 如果不存在则创建RingReaderDispatcher
            if (!ref) {
                std::weak_ptr<RingBuffer> weak_self = this->shared_from_this();
         		// 定义回调函数,最终调用RingReader的onSizeChanged()
                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;
        }
		// 返回ringReader
        return dispatcher->attach(poller, use_cache);
	}
private:
	......
    std::unordered_map<EventPoller::Ptr, typename RingReaderDispatcher::Ptr, HashOfPtr> _dispatcher_map;
};

RingReaderDispatcher添加一个RingReader

看RingReaderDispatcher::_reader_map的类型吧,一个RingReaderDispatcher,对应多个RingReader。
std::unordered_map<void *, std::weak_ptr> _reader_map;

template <typename T>
class _RingReaderDispatcher : public std::enable_shared_from_this<_RingReaderDispatcher<T>> {
public:
    using Ptr = std::shared_ptr<_RingReaderDispatcher>;
    using RingReader = _RingReader<T>;
    using RingStorage = _RingStorage<T>;
    using onChangeInfoCB = std::function<ReaderInfo(ReaderInfo &&info)>;
    ......
private:
    _RingReaderDispatcher(
        const typename RingStorage::Ptr &storage, std::function<void(int, bool)> onSizeChanged) {
        _reader_size = 0;
        _storage = storage;
        _on_size_changed = std::move(onSizeChanged);
        assert(_on_size_changed);
	}
	......
	std::shared_ptr<RingReader> 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<RingReader> reader(new RingReader(use_cache ? _storage : nullptr), on_dealloc);
		// _reader_map中添加reader
        _reader_map[reader.get()] = reader;
        ++_reader_size;
        onSizeChanged(true);
        return reader;
	}
	......
private:
    std::atomic_int _reader_size;
    std::function<void(int, bool)> _on_size_changed;
    typename RingStorage::Ptr _storage;
    std::unordered_map<void *, std::weak_ptr<RingReader>> _reader_map;
};

RingReader初始化,new RingReader()。

3rdpart/ZLToolKit/src/Util/RingBuffer.h
template <typename T>
class _RingReader {
public:
    using Ptr = std::shared_ptr<_RingReader>;
    friend class _RingReaderDispatcher<T>;

    _RingReader(std::shared_ptr<_RingStorage<T>> storage) { _storage = std::move(storage); }

    ~_RingReader() = default;

    void setReadCB(std::function<void(const T &)> cb) {
        if (!cb) {
            _read_cb = [](const T &) {};
        } else {
            _read_cb = std::move(cb);
            flushGop();
        }
    }

    void setDetachCB(std::function<void()> cb) {
        _detach_cb = cb ? std::move(cb) : []() {};
    }

    void setGetInfoCB(std::function<ReaderInfo()> cb) {
        _get_info = cb ? std::move(cb) : []() { return ReaderInfo(); };
    }

private:
    void onRead(const T &data, bool /*is_key*/) { _read_cb(data); }

    void onDetach() const { _detach_cb(); }

    void flushGop() {
        if (!_storage) {
            return;
        }
        _storage->getCache().for_each([this](const List<std::pair<bool, T>> &lst) {
            lst.for_each([this](const std::pair<bool, T> &pr) { onRead(pr.second, pr.first); });
        });
    }

    ReaderInfo getInfo() { return _get_info(); }

private:
    std::shared_ptr<_RingStorage<T>> _storage;
    std::function<void(void)> _detach_cb = []() {};
    std::function<void(const T &)> _read_cb = [](const T &) {};
    std::function<ReaderInfo()> _get_info = []() { return ReaderInfo(); };
};

注册RingReader::setGetInfoCB()

参考:RingReader::setReadCB()。

注册RingReader::setDetachCB()

参考:RingReader::setReadCB()。

注册RingReader::setReadCB()

实现参考:RingReaderDispatcher添加一个RingReader。
调用:

src/Rtsp/RtspSession.cpp
void RtspSession::handleReq_Play(const Parser &parser) {
    ......
    auto play_src = _play_src.lock();
    ......
	//直播源读取器
	// RtspMediaSource::RingType::RingReader::Ptr _play_reader;
    if (!_play_reader && _rtp_type != Rtsp::RTP_MULTICAST) {
        ......
        _play_reader = play_src->getRing()->attach(getPoller(), use_gop);
        _play_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
        _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"));
        });
		// weak_ptr<RtspSession> weak_self = dynamic_pointer_cast<RtspSession>(shared_from_this());
        _play_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pack) {
            auto strong_self = weak_self.lock();
            if (!strong_self) {
                return;
            }
            strong_self->sendRtpPacket(pack);
        });
    }
}

调用RingReader::setReadCB(),最终注册函数为RtspSession::sendRtpPacket()。又抛到最上层了。

src/Rtsp/RtspSession.cpp
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<BufferRtp>(rtp, RtpPacket::kRtpTcpHeaderSize), nullptr, 0, false);
                }
            });
            for (auto &sock : rtp_socks) {
                if (sock) {
                    sock->flushAll();
                }
            }
        }
            break;
        default:
            break;
    }
}

RtspSession::handleReq_TEARDOWN() 断开链接

RtspSession:: onRtpPacket()数据输入

主要由推流调用

RtspSession::onRtpSorted() 数据排序后

数据分发
参考ZLMediaKit源码分析(二)推流创建这里也不再赘述。文章来源地址https://www.toymoban.com/news/detail-477022.html

到了这里,关于ZLMediaKit源码分析(三)拉流创建的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 实战讲解及分析Spring新建Bean的几种方式以及创建过程(图+文+源码)

    作为一个应用开发人员而言,会使用某一个工具分为两个层次(个人观点): 第一个层次,知道工具,会使用这个工具解决问题; 第二个层次,理解工具的实现原理。 关于Spring的学习,还在第一个层次转悠,缺少原理的研究, 随着学习的深入,开始研究些Spring源码,配合

    2023年04月08日
    浏览(39)
  • 【SA8295P 源码分析 (一)】52 - 答疑之 QNX 创建镜像、Android修改CMDLINE

    【源码分析】 因为一些原因,本文需要移除, 对于已经购买的兄弟,不用担心,不是跑路, 我会继续持续提供技术支持, 有什么模块想学习的,或者有什么问题有疑问的, 请私聊我,我们 +VX 沟通技术问题,一起学习,一起进步 接下来,我一一私聊已经购买的兄弟添加V

    2024年02月07日
    浏览(53)
  • 设计模式 代理模式(静态代理 动态代理) 与 Spring Aop源码分析 具体是如何创建Aop代理的

    代理模式是一种结构型设计模式,它通过创建一个代理对象来控制对真实对象的访问。这种模式可以用于提供额外的功能操作,或者扩展目标对象的功能。 在代理模式中,代理对象与真实对象实现相同的接口,以便在任何地方都可以使用相同的接口来调用真实对象的方法。这

    2024年01月20日
    浏览(44)
  • 最新技术整理3款开源免费直播推流工具,实现实时视频推流、视频拉流,目标端可以是服务器、云平台、移动设备等(附源码)

    最新技术整理3款开源免费直播推流工具,实现实时视频推流、视频拉流,目标端可以是服务器、云平台、移动设备等(附源码)。 什么是推流? 视频推流是指将实时的视频数据从一个源端发送到一个或多个目标端的过程。推流的源端可以是摄像头、采集卡等设备,而目标端

    2024年02月04日
    浏览(67)
  • 关于webRTC拉流及拉流步骤

    WebRTC是一种实时通讯协议,它允许浏览器进行音视频通话和数据传输。下面是WebRTC拉流的步骤: 1.获取媒体流 拉流的第一步是获取媒体流。媒体流可以是摄像头、麦克风或屏幕共享。在WebRTC中,使用getUserMedia API获取媒体流。获取到媒体流后,就可以进行处理和发送了。 2.创

    2024年02月16日
    浏览(36)
  • ZLMediakit编译(Win32)

    ZLMediakit编译流程,本文是编译32位的ZLMediakit 直接下载binary就好了,地址:https://slproweb.com/download/Win32OpenSSL-1_1_1u.msi 也可以根据自己的需求下载其他版本,地址https://slproweb.com/products/Win32OpenSSL.html 下载libsrtp,地址:https://github.com/cisco/libsrtp/archive/refs/tags/v2.5.0.zip 解压文件并进

    2024年02月12日
    浏览(43)
  • ZLMediaKit简介

    本文属于《ZLMediaKit源码分析》连载系列博客的第一篇,简要介绍了ZLMediaKit的功能定位、技术优势和源码目录结构。 ZLMediaKit是一个采用现代C++标准编写的开源项目,它既可以直接作为流媒体服务器使用,也可以作为音视频、流媒体相关应用(如:播放器、推流器、流媒体服务

    2024年02月10日
    浏览(37)
  • ZLMediaKit 重建docker包

    修改配置文件,配置项参考:ZLMediaKit 配置文件说明 上传步骤2中导出来的media文件夹到/opt/nms/目录下 加载镜像  各种端口直接用配置文件中配置的,不需要映射

    2024年02月11日
    浏览(34)
  • zlmediakit功能

    1、zlmediakit 编译成功后,进入release/linux/Debug/目录,执行Sudo ./MediaServer -c config.ini -d 2、可以通过ffmpeg 进行拉流 和推流      ffmpeg -re -stream_loop -1 -i MV.mp4 -vcodec copy -acodec copy -f flv rtmp://localhost:1935/test/live -stream_loop : -1 表示循环推流 -0 表示单次推流,也是默认值; -i :表示输

    2024年02月01日
    浏览(20)
  • ZLMediaKit 各种推拉流

    在浏览器上访问: 用push表示推流,推流成功后,其他人可以用WebRTC播放,或者用vlc、ffplay播放

    2024年02月10日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包