diff --git a/README.md b/README.md index fa9f50d5..ddee13cd 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ - RTMP[S] 发布服务器,支持录制发布流 - RTMP[S] 播放器,支持RTMP代理,支持生成静音音频 - RTMP[S] 推流客户端 - - 支持http[s]-flv直播 + - 支持http[s]-flv直播服务器 + - 支持http[s]-flv直播播放器 - 支持websocket-flv直播 - 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议 - 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) diff --git a/README_en.md b/README_en.md index b7410f8b..154f51c7 100644 --- a/README_en.md +++ b/README_en.md @@ -62,7 +62,8 @@ - RTMP[S] publishing server, supports recording and publishing streams - RTMP[S] player, supports RTMP proxy, supports generating silent audio - RTMP[S] push client - - Supports http[s]-flv live streaming + - Supports http[s]-flv live streaming server + - Supports http[s]-flv live streaming player - Supports websocket-flv live streaming - Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol - Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index cb00a0da..3f465bd7 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -12,6 +12,7 @@ #include "PlayerBase.h" #include "Rtsp/RtspPlayerImp.h" #include "Rtmp/RtmpPlayerImp.h" +#include "Rtmp/FlvPlayer.h" #include "Http/HlsPlayer.h" #include "Http/TsPlayerImp.h" @@ -20,12 +21,13 @@ using namespace toolkit; namespace mediakit { -PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) { - static auto releasePlayer = [](PlayerBase *ptr) { - onceToken token(nullptr, [&]() { - delete ptr; +PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) { + auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller(); + static auto releasePlayer = [poller](PlayerBase *ptr) { + poller->async([ptr]() { + onceToken token(nullptr, [&]() { delete ptr; }); + ptr->teardown(); }); - ptr->teardown(); }; string url = url_in; string prefix = findSubString(url.data(), NULL, "://"); @@ -53,9 +55,13 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const s if ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) { if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) { return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer); - } else if (end_with(url, ".ts") || end_with(url_in, ".ts")) { + } + if (end_with(url, ".ts") || end_with(url_in, ".ts")) { return PlayerBase::Ptr(new TsPlayerImp(poller), releasePlayer); } + if (end_with(url, ".flv") || end_with(url_in, ".flv")) { + return PlayerBase::Ptr(new FlvPlayerImp(poller), releasePlayer); + } } throw std::invalid_argument("not supported play schema:" + url_in); diff --git a/src/Rtmp/FlvMuxer.cpp b/src/Rtmp/FlvMuxer.cpp index bd64acf9..a68df5bc 100644 --- a/src/Rtmp/FlvMuxer.cpp +++ b/src/Rtmp/FlvMuxer.cpp @@ -97,18 +97,16 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) { header->flv[0] = 'F'; header->flv[1] = 'L'; header->flv[2] = 'V'; - header->version = 1; - header->length = htonl(9); + header->version = FLVHeader::kFlvVersion; + header->length = htonl(FLVHeader::kFlvHeaderLength); header->have_video = src->haveVideo(); header->have_audio = src->haveAudio(); + //memset时已经赋值为0 + //header->previous_tag_size0 = 0; //flv header onWrite(buffer, false); - //PreviousTagSize0 Always 0 - auto size = htonl(0); - onWrite(obtainBuffer((char *) &size, 4), false); - auto &metadata = src->getMetaData(); if (metadata) { //在有metadata的情况下才发送metadata diff --git a/src/Rtmp/FlvPlayer.cpp b/src/Rtmp/FlvPlayer.cpp new file mode 100644 index 00000000..c0dc1dfa --- /dev/null +++ b/src/Rtmp/FlvPlayer.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "FlvPlayer.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) { + setPoller(poller); +} + +void FlvPlayer::play(const string &url) { + TraceL << "play http-flv: " << url; + _play_result = false; + setHeaderTimeout((*this)[Client::kTimeoutMS].as()); + setBodyTimeout((*this)[Client::kMediaTimeoutMS].as()); + setMethod("GET"); + sendRequest(url); +} + +void FlvPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) { + if (status != "200" && status != "206") { + // http状态码不符合预期 + throw invalid_argument("bad http status code:" + status); + } + + auto content_type = const_cast(header)["Content-Type"]; + if (content_type.find("video/x-flv") != 0) { + throw invalid_argument("content type not http-flv: " + content_type); + } +} + +void FlvPlayer::teardown() { + HttpClientImp::shutdown(); +} + +void FlvPlayer::onResponseCompleted(const SockException &ex) { + if (!_play_result) { + _play_result = true; + onPlayResult(ex); + } else { + onShutdown(ex); + } +} + +void FlvPlayer::onResponseBody(const char *buf, size_t size) { + FlvSplitter::input(buf, size); +} + +bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) { + return onMetadata(metadata); +} + +void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) { + if (!_play_result && !packet->isCfgFrame()) { + _play_result = true; + onPlayResult(SockException(Err_success, "play http-flv success")); + } + onRtmpPacket(std::move(packet)); +} + +}//mediakit \ No newline at end of file diff --git a/src/Rtmp/FlvPlayer.h b/src/Rtmp/FlvPlayer.h new file mode 100644 index 00000000..c49b2b3f --- /dev/null +++ b/src/Rtmp/FlvPlayer.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_FLVPLAYER_H +#define ZLMEDIAKIT_FLVPLAYER_H + +#include "FlvSplitter.h" +#include "Http/HttpClientImp.h" +#include "Player/PlayerBase.h" + +namespace mediakit { + +class FlvPlayer : public PlayerBase, public HttpClientImp, private FlvSplitter { +public: + FlvPlayer(const toolkit::EventPoller::Ptr &poller); + ~FlvPlayer() override = default; + + void play(const std::string &url) override; + void teardown() override; + +protected: + void onResponseHeader(const std::string &status, const HttpHeader &header) override; + void onResponseCompleted(const toolkit::SockException &ex) override; + void onResponseBody(const char *buf, size_t size) override; + +protected: + virtual void onRtmpPacket(RtmpPacket::Ptr packet) = 0; + virtual bool onMetadata(const AMFValue &metadata) = 0; + +private: + bool onRecvMetadata(const AMFValue &metadata) override; + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override; + +private: + bool _play_result = false; +}; + +using FlvPlayerImp = FlvPlayerBase; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVPLAYER_H diff --git a/src/Rtmp/FlvSplitter.cpp b/src/Rtmp/FlvSplitter.cpp new file mode 100644 index 00000000..c2e21e6a --- /dev/null +++ b/src/Rtmp/FlvSplitter.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ +#include "FlvSplitter.h" +#include "utils.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +const char *FlvSplitter::onSearchPacketTail(const char *data, size_t len) { + if (!_flv_started) { + //还没获取到flv头 + if (len < sizeof(FLVHeader)) { + //数据不够 + return nullptr; + } + return data + sizeof(FLVHeader); + } + + //获取到flv头,处理tag数据 + if (len < sizeof(RtmpTagHeader)) { + //数据不够 + return nullptr; + } + return data + sizeof(RtmpTagHeader); +} + +ssize_t FlvSplitter::onRecvHeader(const char *data, size_t len) { + if (!_flv_started) { + //获取到flv头了 + auto header = reinterpret_cast(data); + if (memcmp(header->flv, "FLV", 3)) { + throw std::invalid_argument("不是flv容器格式!"); + } + if (header->version != FLVHeader::kFlvVersion) { + throw std::invalid_argument("flv头中version字段不正确"); + } + if (!header->have_video && !header->have_audio) { + throw std::invalid_argument("flv头中声明音频和视频都不存在"); + } + if (FLVHeader::kFlvHeaderLength != ntohl(header->length)) { + throw std::invalid_argument("flv头中length字段非法"); + } + if (0 != ntohl(header->previous_tag_size0)) { + throw std::invalid_argument("flv头中previous tag size字段非法"); + } + onRecvFlvHeader(*header); + _flv_started = true; + return 0; + } + + //获取到flv头,处理tag数据 + auto tag = reinterpret_cast(data); + auto data_size = load_be24(tag->data_size); + _type = tag->type; + _time_stamp = load_be24(tag->timestamp); + _time_stamp |= (tag->timestamp_ex << 24); + return data_size + 4/*PreviousTagSize*/; +} + +void FlvSplitter::onRecvContent(const char *data, size_t len) { + len -= 4; + auto previous_tag_size = load_be32(data + len); + if (len != previous_tag_size - sizeof(RtmpTagHeader)) { + WarnL << "flv previous tag size 字段非法:" << len << " != " << previous_tag_size - sizeof(RtmpTagHeader); + } + RtmpPacket::Ptr packet; + switch (_type) { + case MSG_AUDIO : { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_AUDIO; + packet->stream_index = STREAM_MEDIA; + break; + } + case MSG_VIDEO: { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_VIDEO; + packet->stream_index = STREAM_MEDIA; + break; + } + + case MSG_DATA: + case MSG_DATA3: { + BufferLikeString buffer(string(data, len)); + AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0); + std::string type = dec.load(); + bool flag = true; + if (type == "@setDataFrame") { + std::string type = dec.load(); + if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown type:" << type; + } + } else if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown notify:" << type; + } + if(!flag){ + throw std::invalid_argument("check rtmp metadata failed"); + } + return; + } + + default: WarnL << "不识别的flv msg type:" << (int) _type; return; + } + + packet->time_stamp = _time_stamp; + packet->type_id = _type; + packet->body_size = len; + packet->buffer.assign(data, len); + onRecvRtmpPacket(std::move(packet)); +} + + +}//namespace mediakit \ No newline at end of file diff --git a/src/Rtmp/FlvSplitter.h b/src/Rtmp/FlvSplitter.h new file mode 100644 index 00000000..78f2d1d5 --- /dev/null +++ b/src/Rtmp/FlvSplitter.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_FLVSPLITTER_H +#define ZLMEDIAKIT_FLVSPLITTER_H + +#include "Rtmp.h" +#include "Http/HttpRequestSplitter.h" +#include "RtmpPlayerImp.h" + +namespace mediakit { + +class FlvSplitter : public HttpRequestSplitter { +public: + FlvSplitter() = default; + ~FlvSplitter() = default; + +protected: + void onRecvContent(const char *data,size_t len) override; + ssize_t onRecvHeader(const char *data,size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + +protected: + virtual void onRecvFlvHeader(const FLVHeader &header) {}; + virtual bool onRecvMetadata(const AMFValue &metadata) = 0; + virtual void onRecvRtmpPacket(RtmpPacket::Ptr packet) = 0; + +private: + bool _flv_started = false; + uint8_t _type; + uint32_t _time_stamp; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVSPLITTER_H diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 55802549..739d4cf5 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -113,6 +113,8 @@ public: class FLVHeader { public: + static constexpr uint8_t kFlvVersion = 1; + static constexpr uint8_t kFlvHeaderLength = 9; //FLV char flv[3]; //File version (for example, 0x01 for FLV version 1) @@ -138,6 +140,8 @@ public: #endif //The length of this header in bytes,固定为9 uint32_t length; + //固定为0 + uint32_t previous_tag_size0; } PACKED; class RtmpTagHeader { diff --git a/src/Rtmp/RtmpPlayer.cpp b/src/Rtmp/RtmpPlayer.cpp index 1c2b335c..9546d889 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -313,8 +313,8 @@ void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) { void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) { //TraceL; auto val = dec.load(); - if (!onCheckMeta(val)) { - throw std::runtime_error("onCheckMeta failed"); + if (!onMetadata(val)) { + throw std::runtime_error("onMetadata failed"); } _metadata_got = true; } @@ -328,18 +328,18 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) { _rtmp_recv_ticker.resetTime(); if (!_play_timer) { //已经触发了onPlayResult事件,直接触发onMediaData事件 - onMediaData(chunk_data); + onRtmpPacket(chunk_data); return; } if (chunk_data->isCfgFrame()) { //输入配置帧以便初始化完成各个track - onMediaData(chunk_data); + onRtmpPacket(chunk_data); } else { //先触发onPlayResult事件,这个时候解码器才能初始化完毕 onPlayResult_l(SockException(Err_success, "play rtmp success"), false); //触发onPlayResult事件后,再把帧数据输入到解码器 - onMediaData(chunk_data); + onRtmpPacket(chunk_data); } } @@ -379,8 +379,8 @@ void RtmpPlayer::onRtmpChunk(RtmpPacket::Ptr packet) { _now_stamp[idx] = chunk_data.time_stamp; } if (!_metadata_got) { - if (!onCheckMeta(TitleMeta().getMetadata())) { - throw std::runtime_error("onCheckMeta failed"); + if (!onMetadata(TitleMeta().getMetadata())) { + throw std::runtime_error("onMetadata failed"); } _metadata_got = true; } @@ -420,49 +420,4 @@ void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){ }); } -//////////////////////////////////////////// -float RtmpPlayerImp::getDuration() const -{ - return _demuxer ? _demuxer->getDuration() : 0; -} - -std::vector RtmpPlayerImp::getTracks(bool ready /*= true*/) const -{ - return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); -} - -bool RtmpPlayerImp::onCheckMeta(const AMFValue &val) -{ - //无metadata或metadata中无track信息时,需要从数据包中获取track - _wait_track_ready = (*this)[Client::kWaitTrackReady].as() || RtmpDemuxer::trackCount(val) == 0; - onCheckMeta_l(val); - return true; -} - -void RtmpPlayerImp::onMediaData(RtmpPacket::Ptr chunkData) -{ - if (!_demuxer) { - //有些rtmp流没metadata - onCheckMeta_l(TitleMeta().getMetadata()); - } - _demuxer->inputRtmp(chunkData); - if (_rtmp_src) { - _rtmp_src->onWrite(std::move(chunkData)); - } -} - -void RtmpPlayerImp::onCheckMeta_l(const AMFValue &val) -{ - _rtmp_src = std::dynamic_pointer_cast(_media_src); - if (_rtmp_src) { - _rtmp_src->setMetaData(val); - } - if (_demuxer) { - return; - } - _demuxer = std::make_shared(); - //TraceL<<" _wait_track_ready "<<_wait_track_ready; - _demuxer->setTrackListener(this, _wait_track_ready); - _demuxer->loadMetaData(val); -} } /* namespace mediakit */ diff --git a/src/Rtmp/RtmpPlayer.h b/src/Rtmp/RtmpPlayer.h index 0a45f1b5..75abf7f9 100644 --- a/src/Rtmp/RtmpPlayer.h +++ b/src/Rtmp/RtmpPlayer.h @@ -37,8 +37,8 @@ public: void teardown() override; protected: - virtual bool onCheckMeta(const AMFValue &val) = 0; - virtual void onMediaData(RtmpPacket::Ptr chunk_data) = 0; + virtual bool onMetadata(const AMFValue &val) = 0; + virtual void onRtmpPacket(RtmpPacket::Ptr chunk_data) = 0; uint32_t getProgressMilliSecond() const; void seekToMilliSecond(uint32_t ms); diff --git a/src/Rtmp/RtmpPlayerImp.h b/src/Rtmp/RtmpPlayerImp.h index 25b33741..ecf9a34f 100644 --- a/src/Rtmp/RtmpPlayerImp.h +++ b/src/Rtmp/RtmpPlayerImp.h @@ -13,16 +13,95 @@ #include #include +#include "Common/config.h" #include "RtmpPlayer.h" -#include "RtmpDemuxer.h" #include "RtmpMediaSource.h" +#include "RtmpDemuxer.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" namespace mediakit { -class RtmpPlayerImp: public PlayerImp, private TrackListener { +template +class FlvPlayerBase: public PlayerImp, private TrackListener { +public: + using Ptr = std::shared_ptr; + using Super = PlayerImp; + + FlvPlayerBase(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; + + ~FlvPlayerBase() override { + DebugL << std::endl; + } + + float getDuration() const override { + return _demuxer ? _demuxer->getDuration() : 0; + } + + std::vector getTracks(bool ready = true) const override { + return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); + } + +private: + //派生类回调函数 + bool onMetadata(const AMFValue &val) override { + //无metadata或metadata中无track信息时,需要从数据包中获取track + _wait_track_ready = this->Super::operator[](Client::kWaitTrackReady).template as() || RtmpDemuxer::trackCount(val) == 0; + onCheckMeta_l(val); + return true; + } + + void onRtmpPacket(RtmpPacket::Ptr chunkData) override { + if (!_demuxer) { + //有些rtmp流没metadata + onCheckMeta_l(TitleMeta().getMetadata()); + } + _demuxer->inputRtmp(chunkData); + if (_rtmp_src) { + _rtmp_src->onWrite(std::move(chunkData)); + } + } + + void onPlayResult(const toolkit::SockException &ex) override { + if (!_wait_track_ready || ex) { + Super::onPlayResult(ex); + return; + } + } + + bool addTrack(const Track::Ptr &track) override { return true; } + + void addTrackCompleted() override { + if (_wait_track_ready) { + Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); + } + } + +private: + void onCheckMeta_l(const AMFValue &val) { + _rtmp_src = std::dynamic_pointer_cast(this->Super::_media_src); + if (_rtmp_src) { + _rtmp_src->setMetaData(val); + } + if(_demuxer){ + return; + } + _demuxer = std::make_shared(); + //TraceL<<" _wait_track_ready "<<_wait_track_ready; + _demuxer->setTrackListener(this, _wait_track_ready); + _demuxer->loadMetaData(val); + } + +private: + bool _wait_track_ready = true; + RtmpDemuxer::Ptr _demuxer; + RtmpMediaSource::Ptr _rtmp_src; +}; + +class RtmpPlayerImp: public FlvPlayerBase { public: using Ptr = std::shared_ptr; - using Super = PlayerImp; + using Super = FlvPlayerBase; RtmpPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; @@ -46,39 +125,6 @@ public: uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000; seekToMilliSecond(pos); } - - float getDuration() const override; - - std::vector getTracks(bool ready = true) const override; - -private: - //派生类回调函数 - bool onCheckMeta(const AMFValue &val) override; - - void onMediaData(RtmpPacket::Ptr chunkData) override; - - void onPlayResult(const toolkit::SockException &ex) override { - if (!_wait_track_ready || ex) { - Super::onPlayResult(ex); - return; - } - } - - bool addTrack(const Track::Ptr &track) override { return true; } - - void addTrackCompleted() override { - if (_wait_track_ready) { - Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); - } - } - -private: - void onCheckMeta_l(const AMFValue &val); - -private: - bool _wait_track_ready = true; - RtmpDemuxer::Ptr _demuxer; - RtmpMediaSource::Ptr _rtmp_src; }; diff --git a/tests/test_flv.cpp b/tests/test_flv.cpp new file mode 100644 index 00000000..ffed816f --- /dev/null +++ b/tests/test_flv.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "Util/logger.h" +#include "Util/util.h" +#include "Network/TcpServer.h" +#include "Common/config.h" +#include "Rtsp/RtspSession.h" +#include "Rtmp/RtmpSession.h" +#include "Http/HttpSession.h" +#include "Rtmp/FlvSplitter.h" +#include "Rtmp/RtmpMediaSourceImp.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class FlvSplitterImp : public FlvSplitter { +public: + FlvSplitterImp() { + _src = std::make_shared(MediaTuple{DEFAULT_VHOST, "live", "test"}); + } + ~FlvSplitterImp() override = default; + + void inputData(const char *data, size_t len, uint32_t &stamp) { + FlvSplitter::input(data, len); + stamp = _stamp; + } + +protected: + void onRecvFlvHeader(const FLVHeader &header) override { + } + + bool onRecvMetadata(const AMFValue &metadata) override { + _src->setMetaData(metadata); + _src->setProtocolOption(ProtocolOption()); + return true; + } + + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override { + _stamp = packet->time_stamp; + _src->onWrite(std::move(packet)); + } + +private: + uint32_t _stamp; + RtmpMediaSourceImp::Ptr _src; +}; + +static bool loadFile(const char *path){ + FILE *fp = fopen(path, "rb"); + if (!fp) { + WarnL << "open file failed:" << path; + return false; + } + + char buffer[64 * 1024]; + uint32_t timeStamp_last = 0; + size_t len; + size_t total_size = 0; + FlvSplitterImp flv; + while (true) { + len = fread(buffer, 1, sizeof(buffer), fp); + if (len <= 0) { + break; + } + total_size += len; + uint32_t timeStamp; + flv.inputData(buffer, len, timeStamp); + auto diff = timeStamp - timeStamp_last; + if (diff > 0) { + usleep(diff * 1000); + } else { + usleep(1 * 1000); + } + timeStamp_last = timeStamp; + } + WarnL << total_size / 1024 << "KB"; + fclose(fp); + return true; +} + +int main(int argc,char *argv[]) { + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel")); + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + loadIniConfig((exeDir() + "config.ini").data()); + TcpServer::Ptr rtspSrv(new TcpServer()); + TcpServer::Ptr rtmpSrv(new TcpServer()); + TcpServer::Ptr httpSrv(new TcpServer()); + rtspSrv->start(554);//默认554 + rtmpSrv->start(1935);//默认1935 + httpSrv->start(80);//默认80 + + if (argc == 2) + loadFile(argv[1]); + else + ErrorL << "parameter error."; + return 0; +} + +