mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-26 04:31:37 +08:00
新增支持http-flv播放器 (#2562)
This commit is contained in:
parent
03770ff409
commit
7e117b1c7f
@ -63,7 +63,8 @@
|
|||||||
- RTMP[S] 发布服务器,支持录制发布流
|
- RTMP[S] 发布服务器,支持录制发布流
|
||||||
- RTMP[S] 播放器,支持RTMP代理,支持生成静音音频
|
- RTMP[S] 播放器,支持RTMP代理,支持生成静音音频
|
||||||
- RTMP[S] 推流客户端
|
- RTMP[S] 推流客户端
|
||||||
- 支持http[s]-flv直播
|
- 支持http[s]-flv直播服务器
|
||||||
|
- 支持http[s]-flv直播播放器
|
||||||
- 支持websocket-flv直播
|
- 支持websocket-flv直播
|
||||||
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||||
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
||||||
|
@ -62,7 +62,8 @@
|
|||||||
- RTMP[S] publishing server, supports recording and publishing streams
|
- RTMP[S] publishing server, supports recording and publishing streams
|
||||||
- RTMP[S] player, supports RTMP proxy, supports generating silent audio
|
- RTMP[S] player, supports RTMP proxy, supports generating silent audio
|
||||||
- RTMP[S] push client
|
- 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 websocket-flv live streaming
|
||||||
- Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol
|
- 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)
|
- Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "PlayerBase.h"
|
#include "PlayerBase.h"
|
||||||
#include "Rtsp/RtspPlayerImp.h"
|
#include "Rtsp/RtspPlayerImp.h"
|
||||||
#include "Rtmp/RtmpPlayerImp.h"
|
#include "Rtmp/RtmpPlayerImp.h"
|
||||||
|
#include "Rtmp/FlvPlayer.h"
|
||||||
#include "Http/HlsPlayer.h"
|
#include "Http/HlsPlayer.h"
|
||||||
#include "Http/TsPlayerImp.h"
|
#include "Http/TsPlayerImp.h"
|
||||||
|
|
||||||
@ -20,12 +21,13 @@ using namespace toolkit;
|
|||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) {
|
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) {
|
||||||
static auto releasePlayer = [](PlayerBase *ptr) {
|
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
|
||||||
onceToken token(nullptr, [&]() {
|
static auto releasePlayer = [poller](PlayerBase *ptr) {
|
||||||
delete ptr;
|
poller->async([ptr]() {
|
||||||
|
onceToken token(nullptr, [&]() { delete ptr; });
|
||||||
|
ptr->teardown();
|
||||||
});
|
});
|
||||||
ptr->teardown();
|
|
||||||
};
|
};
|
||||||
string url = url_in;
|
string url = url_in;
|
||||||
string prefix = findSubString(url.data(), NULL, "://");
|
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 ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) {
|
||||||
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
|
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
|
||||||
return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer);
|
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);
|
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);
|
throw std::invalid_argument("not supported play schema:" + url_in);
|
||||||
|
@ -97,18 +97,16 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) {
|
|||||||
header->flv[0] = 'F';
|
header->flv[0] = 'F';
|
||||||
header->flv[1] = 'L';
|
header->flv[1] = 'L';
|
||||||
header->flv[2] = 'V';
|
header->flv[2] = 'V';
|
||||||
header->version = 1;
|
header->version = FLVHeader::kFlvVersion;
|
||||||
header->length = htonl(9);
|
header->length = htonl(FLVHeader::kFlvHeaderLength);
|
||||||
header->have_video = src->haveVideo();
|
header->have_video = src->haveVideo();
|
||||||
header->have_audio = src->haveAudio();
|
header->have_audio = src->haveAudio();
|
||||||
|
//memset时已经赋值为0
|
||||||
|
//header->previous_tag_size0 = 0;
|
||||||
|
|
||||||
//flv header
|
//flv header
|
||||||
onWrite(buffer, false);
|
onWrite(buffer, false);
|
||||||
|
|
||||||
//PreviousTagSize0 Always 0
|
|
||||||
auto size = htonl(0);
|
|
||||||
onWrite(obtainBuffer((char *) &size, 4), false);
|
|
||||||
|
|
||||||
auto &metadata = src->getMetaData();
|
auto &metadata = src->getMetaData();
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
//在有metadata的情况下才发送metadata
|
//在有metadata的情况下才发送metadata
|
||||||
|
72
src/Rtmp/FlvPlayer.cpp
Normal file
72
src/Rtmp/FlvPlayer.cpp
Normal file
@ -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<int>());
|
||||||
|
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||||
|
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<HttpClient::HttpHeader &>(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
|
48
src/Rtmp/FlvPlayer.h
Normal file
48
src/Rtmp/FlvPlayer.h
Normal file
@ -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<FlvPlayer>;
|
||||||
|
|
||||||
|
}//namespace mediakit
|
||||||
|
#endif //ZLMEDIAKIT_FLVPLAYER_H
|
125
src/Rtmp/FlvSplitter.cpp
Normal file
125
src/Rtmp/FlvSplitter.cpp
Normal file
@ -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<const FLVHeader *>(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<const RtmpTagHeader *>(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<std::string>();
|
||||||
|
bool flag = true;
|
||||||
|
if (type == "@setDataFrame") {
|
||||||
|
std::string type = dec.load<std::string>();
|
||||||
|
if (type == "onMetaData") {
|
||||||
|
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||||
|
} else {
|
||||||
|
WarnL << "unknown type:" << type;
|
||||||
|
}
|
||||||
|
} else if (type == "onMetaData") {
|
||||||
|
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||||
|
} 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
|
42
src/Rtmp/FlvSplitter.h
Normal file
42
src/Rtmp/FlvSplitter.h
Normal file
@ -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
|
@ -113,6 +113,8 @@ public:
|
|||||||
|
|
||||||
class FLVHeader {
|
class FLVHeader {
|
||||||
public:
|
public:
|
||||||
|
static constexpr uint8_t kFlvVersion = 1;
|
||||||
|
static constexpr uint8_t kFlvHeaderLength = 9;
|
||||||
//FLV
|
//FLV
|
||||||
char flv[3];
|
char flv[3];
|
||||||
//File version (for example, 0x01 for FLV version 1)
|
//File version (for example, 0x01 for FLV version 1)
|
||||||
@ -138,6 +140,8 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
//The length of this header in bytes,固定为9
|
//The length of this header in bytes,固定为9
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
|
//固定为0
|
||||||
|
uint32_t previous_tag_size0;
|
||||||
} PACKED;
|
} PACKED;
|
||||||
|
|
||||||
class RtmpTagHeader {
|
class RtmpTagHeader {
|
||||||
|
@ -313,8 +313,8 @@ void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
|||||||
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
||||||
//TraceL;
|
//TraceL;
|
||||||
auto val = dec.load<AMFValue>();
|
auto val = dec.load<AMFValue>();
|
||||||
if (!onCheckMeta(val)) {
|
if (!onMetadata(val)) {
|
||||||
throw std::runtime_error("onCheckMeta failed");
|
throw std::runtime_error("onMetadata failed");
|
||||||
}
|
}
|
||||||
_metadata_got = true;
|
_metadata_got = true;
|
||||||
}
|
}
|
||||||
@ -328,18 +328,18 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) {
|
|||||||
_rtmp_recv_ticker.resetTime();
|
_rtmp_recv_ticker.resetTime();
|
||||||
if (!_play_timer) {
|
if (!_play_timer) {
|
||||||
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
||||||
onMediaData(chunk_data);
|
onRtmpPacket(chunk_data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunk_data->isCfgFrame()) {
|
if (chunk_data->isCfgFrame()) {
|
||||||
//输入配置帧以便初始化完成各个track
|
//输入配置帧以便初始化完成各个track
|
||||||
onMediaData(chunk_data);
|
onRtmpPacket(chunk_data);
|
||||||
} else {
|
} else {
|
||||||
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
||||||
onPlayResult_l(SockException(Err_success, "play rtmp success"), false);
|
onPlayResult_l(SockException(Err_success, "play rtmp success"), false);
|
||||||
//触发onPlayResult事件后,再把帧数据输入到解码器
|
//触发onPlayResult事件后,再把帧数据输入到解码器
|
||||||
onMediaData(chunk_data);
|
onRtmpPacket(chunk_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,8 +379,8 @@ void RtmpPlayer::onRtmpChunk(RtmpPacket::Ptr packet) {
|
|||||||
_now_stamp[idx] = chunk_data.time_stamp;
|
_now_stamp[idx] = chunk_data.time_stamp;
|
||||||
}
|
}
|
||||||
if (!_metadata_got) {
|
if (!_metadata_got) {
|
||||||
if (!onCheckMeta(TitleMeta().getMetadata())) {
|
if (!onMetadata(TitleMeta().getMetadata())) {
|
||||||
throw std::runtime_error("onCheckMeta failed");
|
throw std::runtime_error("onMetadata failed");
|
||||||
}
|
}
|
||||||
_metadata_got = true;
|
_metadata_got = true;
|
||||||
}
|
}
|
||||||
@ -420,49 +420,4 @@ void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
float RtmpPlayerImp::getDuration() const
|
|
||||||
{
|
|
||||||
return _demuxer ? _demuxer->getDuration() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<mediakit::Track::Ptr> 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<bool>() || 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<RtmpMediaSource>(_media_src);
|
|
||||||
if (_rtmp_src) {
|
|
||||||
_rtmp_src->setMetaData(val);
|
|
||||||
}
|
|
||||||
if (_demuxer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_demuxer = std::make_shared<RtmpDemuxer>();
|
|
||||||
//TraceL<<" _wait_track_ready "<<_wait_track_ready;
|
|
||||||
_demuxer->setTrackListener(this, _wait_track_ready);
|
|
||||||
_demuxer->loadMetaData(val);
|
|
||||||
}
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -37,8 +37,8 @@ public:
|
|||||||
void teardown() override;
|
void teardown() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool onCheckMeta(const AMFValue &val) = 0;
|
virtual bool onMetadata(const AMFValue &val) = 0;
|
||||||
virtual void onMediaData(RtmpPacket::Ptr chunk_data) = 0;
|
virtual void onRtmpPacket(RtmpPacket::Ptr chunk_data) = 0;
|
||||||
uint32_t getProgressMilliSecond() const;
|
uint32_t getProgressMilliSecond() const;
|
||||||
void seekToMilliSecond(uint32_t ms);
|
void seekToMilliSecond(uint32_t ms);
|
||||||
|
|
||||||
|
@ -13,16 +13,95 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include "Common/config.h"
|
||||||
#include "RtmpPlayer.h"
|
#include "RtmpPlayer.h"
|
||||||
#include "RtmpDemuxer.h"
|
|
||||||
#include "RtmpMediaSource.h"
|
#include "RtmpMediaSource.h"
|
||||||
|
#include "RtmpDemuxer.h"
|
||||||
|
#include "Poller/Timer.h"
|
||||||
|
#include "Util/TimeTicker.h"
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class RtmpPlayerImp: public PlayerImp<RtmpPlayer,PlayerBase>, private TrackListener {
|
template<typename Parent>
|
||||||
|
class FlvPlayerBase: public PlayerImp<Parent,PlayerBase>, private TrackListener {
|
||||||
|
public:
|
||||||
|
using Ptr = std::shared_ptr<FlvPlayerBase>;
|
||||||
|
using Super = PlayerImp<Parent, PlayerBase>;
|
||||||
|
|
||||||
|
FlvPlayerBase(const toolkit::EventPoller::Ptr &poller) : Super(poller) {};
|
||||||
|
|
||||||
|
~FlvPlayerBase() override {
|
||||||
|
DebugL << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getDuration() const override {
|
||||||
|
return _demuxer ? _demuxer->getDuration() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Track::Ptr> 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<bool>() || 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<RtmpMediaSource>(this->Super::_media_src);
|
||||||
|
if (_rtmp_src) {
|
||||||
|
_rtmp_src->setMetaData(val);
|
||||||
|
}
|
||||||
|
if(_demuxer){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_demuxer = std::make_shared<RtmpDemuxer>();
|
||||||
|
//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<RtmpPlayer> {
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<RtmpPlayerImp>;
|
using Ptr = std::shared_ptr<RtmpPlayerImp>;
|
||||||
using Super = PlayerImp<RtmpPlayer,PlayerBase>;
|
using Super = FlvPlayerBase<RtmpPlayer>;
|
||||||
|
|
||||||
RtmpPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {};
|
RtmpPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {};
|
||||||
|
|
||||||
@ -46,39 +125,6 @@ public:
|
|||||||
uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000;
|
uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000;
|
||||||
seekToMilliSecond(pos);
|
seekToMilliSecond(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
float getDuration() const override;
|
|
||||||
|
|
||||||
std::vector<Track::Ptr> 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
112
tests/test_flv.cpp
Normal file
112
tests/test_flv.cpp
Normal file
@ -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 <map>
|
||||||
|
#include <iostream>
|
||||||
|
#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<RtmpMediaSourceImp>(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>("ConsoleChannel"));
|
||||||
|
//启动异步日志线程
|
||||||
|
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||||
|
loadIniConfig((exeDir() + "config.ini").data());
|
||||||
|
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr httpSrv(new TcpServer());
|
||||||
|
rtspSrv->start<RtspSession>(554);//默认554
|
||||||
|
rtmpSrv->start<RtmpSession>(1935);//默认1935
|
||||||
|
httpSrv->start<HttpSession>(80);//默认80
|
||||||
|
|
||||||
|
if (argc == 2)
|
||||||
|
loadFile(argv[1]);
|
||||||
|
else
|
||||||
|
ErrorL << "parameter error.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user