mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-07-07 04:08:13 +08:00
初始提交
This commit is contained in:
223
src/Rtmp/Rtmp.h
Normal file
223
src/Rtmp/Rtmp.h
Normal file
@@ -0,0 +1,223 @@
|
||||
#ifndef __rtmp_h
|
||||
#define __rtmp_h
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include "Util/util.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "Util/logger.h"
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
|
||||
|
||||
#define PORT 1935
|
||||
|
||||
#define DEFAULT_CHUNK_LEN 128
|
||||
|
||||
#define PACKED __attribute__((packed))
|
||||
|
||||
#define HANDSHAKE_PLAINTEXT 0x03
|
||||
|
||||
#define RANDOM_LEN (1536 - 8)
|
||||
|
||||
#define MSG_SET_CHUNK 1 /*Set Chunk Size (1)*/
|
||||
#define MSG_ABORT 2 /*Abort Message (2)*/
|
||||
#define MSG_ACK 3 /*Acknowledgement (3)*/
|
||||
#define MSG_USER_CONTROL 4 /*User Control Messages (4)*/
|
||||
#define MSG_WIN_SIZE 5 /*Window Acknowledgement Size (5)*/
|
||||
#define MSG_SET_PEER_BW 6 /*Set Peer Bandwidth (6)*/
|
||||
#define MSG_AUDIO 8 /*Audio Message (8)*/
|
||||
#define MSG_VIDEO 9 /*Video Message (9)*/
|
||||
#define MSG_DATA 18 /*Data Message (18, 15) AMF0*/
|
||||
#define MSG_DATA3 15 /*Data Message (18, 15) AMF3*/
|
||||
#define MSG_CMD 20 /*Command Message AMF0 */
|
||||
#define MSG_CMD3 17 /*Command Message AMF3 */
|
||||
#define MSG_OBJECT3 16 /*Shared Object Message (19, 16) AMF3*/
|
||||
#define MSG_OBJECT 19 /*Shared Object Message (19, 16) AMF0*/
|
||||
#define MSG_AGGREGATE 22 /*Aggregate Message (22)*/
|
||||
|
||||
|
||||
#define CONTROL_STREAM_BEGIN 0
|
||||
#define CONTROL_STREAM_EOF 1
|
||||
#define CONTROL_STREAM_DRY 2
|
||||
#define CONTROL_SETBUFFER 3
|
||||
#define CONTROL_STREAM_ISRECORDED 4
|
||||
#define CONTROL_PING_REQUEST 6
|
||||
#define CONTROL_PING_RESPONSE 7
|
||||
|
||||
|
||||
#define STREAM_CONTROL 0
|
||||
#define STREAM_MEDIA 1
|
||||
|
||||
#define CHUNK_SERVER_REQUEST 2 /*服务器像客户端发出请求时的chunkID*/
|
||||
#define CHUNK_CLIENT_REQUEST_BEFORE 3 /*客户端在createStream前,向服务器发出请求的chunkID*/
|
||||
#define CHUNK_CLIENT_REQUEST_AFTER 4 /*客户端在createStream后,向服务器发出请求的chunkID*/
|
||||
|
||||
#define FLV_KEY_FRAME 1
|
||||
#define FLV_INTER_FRAME 2
|
||||
|
||||
class RtmpHandshake {
|
||||
public:
|
||||
RtmpHandshake(uint32_t _time, uint8_t *_random = nullptr) {
|
||||
_time = htonl(_time);
|
||||
memcpy(timeStamp, &_time, 4);
|
||||
if (!_random) {
|
||||
random_generate((char *) random, sizeof(random));
|
||||
} else {
|
||||
memcpy(random, _random, sizeof(random));
|
||||
}
|
||||
}
|
||||
uint8_t timeStamp[4];
|
||||
uint8_t zero[4] = { 0 };
|
||||
uint8_t random[RANDOM_LEN];
|
||||
private:
|
||||
void random_generate(char* bytes, int size) {
|
||||
static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
|
||||
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d };
|
||||
for (int i = 0; i < size; i++) {
|
||||
bytes[i] = cdata[rand() % (sizeof(cdata) - 1)];
|
||||
}
|
||||
}
|
||||
}PACKED;
|
||||
|
||||
class RtmpHeader {
|
||||
public:
|
||||
uint8_t flags;
|
||||
uint8_t timeStamp[3];
|
||||
uint8_t bodySize[3];
|
||||
uint8_t typeId;
|
||||
uint8_t streamId[4]; /* Note, this is little-endian while others are BE */
|
||||
}PACKED;
|
||||
|
||||
|
||||
class RtmpPacket {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPacket> Ptr;
|
||||
uint8_t typeId;
|
||||
uint32_t bodySize = 0;
|
||||
uint32_t timeStamp = 0;
|
||||
bool extStamp = false;
|
||||
uint32_t streamId;
|
||||
uint32_t chunkId;
|
||||
std::string strBuf;
|
||||
bool isVideoKeyFrame() const {
|
||||
return typeId == MSG_VIDEO && (uint8_t) strBuf[0] >> 4 == FLV_KEY_FRAME
|
||||
&& (uint8_t) strBuf[1] == 1;
|
||||
}
|
||||
bool isCfgFrame() const {
|
||||
return (typeId == MSG_VIDEO || typeId == MSG_AUDIO)
|
||||
&& (uint8_t) strBuf[1] == 0;
|
||||
}
|
||||
int getMediaType() const {
|
||||
switch (typeId) {
|
||||
case MSG_VIDEO: {
|
||||
return (uint8_t) strBuf[0] & 0x0F;
|
||||
}
|
||||
break;
|
||||
case MSG_AUDIO: {
|
||||
return (uint8_t) strBuf[0] >> 4;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int getAudioSampleRate() const {
|
||||
if (typeId != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
int flvSampleRate = ((uint8_t) strBuf[0] & 0x0C) >> 2;
|
||||
const static int sampleRate[] = { 5512, 11025, 22050, 44100 };
|
||||
return sampleRate[flvSampleRate];
|
||||
}
|
||||
int getAudioSampleBit() const {
|
||||
if (typeId != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
int flvSampleBit = ((uint8_t) strBuf[0] & 0x02) >> 1;
|
||||
const static int sampleBit[] = { 8, 16 };
|
||||
return sampleBit[flvSampleBit];
|
||||
}
|
||||
int getAudioChannel() const {
|
||||
if (typeId != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
int flvStereoOrMono = (uint8_t) strBuf[0] & 0x01;
|
||||
const static int channel[] = { 1, 2 };
|
||||
return channel[flvStereoOrMono];
|
||||
}
|
||||
string getH264SPS() const {
|
||||
string ret;
|
||||
if (getMediaType() != 7) {
|
||||
return ret;
|
||||
}
|
||||
if (!isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (strBuf.size() < 13) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t sps_size ;
|
||||
memcpy(&sps_size,strBuf.data() + 11,2);
|
||||
sps_size = ntohs(sps_size);
|
||||
if ((int) strBuf.size() < 13 + sps_size) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret.assign(strBuf.data() + 13, sps_size);
|
||||
return ret;
|
||||
}
|
||||
string getH264PPS() const {
|
||||
string ret;
|
||||
if (getMediaType() != 7) {
|
||||
return ret;
|
||||
}
|
||||
if (!isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (strBuf.size() < 13) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t sps_size ;
|
||||
memcpy(&sps_size,strBuf.data() + 11,2);
|
||||
sps_size = ntohs(sps_size);
|
||||
|
||||
if ((int) strBuf.size() < 13 + sps_size + 1 + 2) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t pps_size ;
|
||||
memcpy(&pps_size,strBuf.data() + 13 + sps_size + 1,2);
|
||||
pps_size = ntohs(pps_size);
|
||||
|
||||
if ((int) strBuf.size() < 13 + sps_size + 1 + 2 + pps_size) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret.assign(strBuf.data() + 13 + sps_size + 1 + 2, pps_size);
|
||||
return ret;
|
||||
}
|
||||
string getAacCfg() const {
|
||||
string ret;
|
||||
if (getMediaType() != 10) {
|
||||
return ret;
|
||||
}
|
||||
if (!isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (strBuf.size() < 4) {
|
||||
WarnL << "bad aac cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret = strBuf.substr(2, 2);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
42
src/Rtmp/RtmpMediaSource.cpp
Normal file
42
src/Rtmp/RtmpMediaSource.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* MediaSource.cpp
|
||||
*
|
||||
* Created on: 2016年9月1日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "RtmpMediaSource.h"
|
||||
#include "MedaiFile/MediaReader.h"
|
||||
using namespace ZL::MediaFile;
|
||||
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
recursive_mutex RtmpMediaSource::g_mtxMediaSrc;
|
||||
unordered_map<string, unordered_map<string,weak_ptr<RtmpMediaSource> > > RtmpMediaSource::g_mapMediaSrc;
|
||||
|
||||
RtmpMediaSource::Ptr RtmpMediaSource::find(const string &strApp, const string &strId, bool bMake) {
|
||||
//查找某一媒体源,找到后返回
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
auto itApp = g_mapMediaSrc.find(strApp);
|
||||
if (itApp == g_mapMediaSrc.end()) {
|
||||
return bMake ? MediaReader::onMakeRtmp(strApp, strId) : nullptr;
|
||||
}
|
||||
auto itId = itApp->second.find(strId);
|
||||
if (itId == itApp->second.end()) {
|
||||
return bMake ? MediaReader::onMakeRtmp(strApp, strId) : nullptr;
|
||||
}
|
||||
auto ret = itId->second.lock();
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
itApp->second.erase(itId);
|
||||
if (itApp->second.size() == 0) {
|
||||
g_mapMediaSrc.erase(itApp);
|
||||
}
|
||||
return bMake ? MediaReader::onMakeRtmp(strApp, strId) : nullptr;
|
||||
}
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
163
src/Rtmp/RtmpMediaSource.h
Normal file
163
src/Rtmp/RtmpMediaSource.h
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* RtmpMediaSource.h
|
||||
*
|
||||
* Created on: 2016年9月1日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPMEDIASOURCE_H_
|
||||
#define SRC_RTMP_RTMPMEDIASOURCE_H_
|
||||
|
||||
#include "config.h"
|
||||
#include <netinet/in.h>
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/ResourcePool.h"
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "Util/RingBuffer.hpp"
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include "Util/logger.h"
|
||||
#include "Thread/ThreadPool.hpp"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "amf.h"
|
||||
#include "Rtmp.h"
|
||||
#include "Util/util.h"
|
||||
#include "MediaSender.h"
|
||||
#include <mutex>
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Thread;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
|
||||
class RtmpMediaSource: public enable_shared_from_this<RtmpMediaSource> {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpMediaSource> Ptr;
|
||||
typedef RingBuffer<RtmpPacket> RingType;
|
||||
RtmpMediaSource(const string &strApp, const string &strId) :
|
||||
m_strApp(strApp),
|
||||
m_strId(strId),
|
||||
m_pRing( new RingBuffer<RtmpPacket>(1)),
|
||||
m_thPool( MediaSender::sendThread()) {
|
||||
}
|
||||
virtual ~RtmpMediaSource() {
|
||||
unregist();
|
||||
}
|
||||
const RingType::Ptr &getRing() const {
|
||||
//获取媒体源的rtp环形缓冲
|
||||
return m_pRing;
|
||||
}
|
||||
virtual void regist() {
|
||||
//注册该源,注册后rtmp服务器才能找到该源
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
if (!g_mapMediaSrc[m_strApp].erase(m_strId)) {
|
||||
InfoL << "Rtmp src:" << m_strApp << " " << m_strId;
|
||||
}
|
||||
g_mapMediaSrc[m_strApp].emplace(m_strId, shared_from_this());
|
||||
NoticeCenter::Instance().emitEvent(Config::Broadcast::kBroadcastRtmpSrcRegisted,m_strApp.data(),m_strId.data());
|
||||
}
|
||||
virtual void unregist() {
|
||||
//反注册该源
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
auto it = g_mapMediaSrc.find(m_strApp);
|
||||
if (it == g_mapMediaSrc.end()) {
|
||||
return;
|
||||
}
|
||||
if (it->second.erase(m_strId)) {
|
||||
if (it->second.size() == 0) {
|
||||
g_mapMediaSrc.erase(it);
|
||||
}
|
||||
InfoL << "Rtmp src:" << m_strApp << " " << m_strId;
|
||||
}
|
||||
}
|
||||
static set<string> getMediaSet() {
|
||||
set<string> ret;
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
for (auto &pr0 : g_mapMediaSrc) {
|
||||
for (auto &pr1 : pr0.second) {
|
||||
if (pr1.second.lock()) {
|
||||
ret.emplace(pr0.first + "/" + pr1.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static Ptr find(const string &strApp, const string &strId, bool bMake = true) ;
|
||||
const string& getApp() const {
|
||||
//获取该源的id
|
||||
return m_strApp;
|
||||
}
|
||||
const string& getId() const {
|
||||
//获取该源的id
|
||||
return m_strId;
|
||||
}
|
||||
const AMFValue &getMetaData() const {
|
||||
return m_metadata;
|
||||
}
|
||||
template<typename FUN>
|
||||
void getConfigFrame(const FUN &f) {
|
||||
lock_guard<recursive_mutex> lock(m_mtxMap);
|
||||
for (auto &pr : m_mapCfgFrame) {
|
||||
f(pr.second);
|
||||
}
|
||||
}
|
||||
bool ready() const {
|
||||
lock_guard<recursive_mutex> lock(m_mtxMap);
|
||||
return (m_mapCfgFrame.size() != 0);
|
||||
}
|
||||
virtual void onGetMetaData(const AMFValue &_metadata) {
|
||||
m_metadata = _metadata;
|
||||
}
|
||||
virtual void onGetMedia(const RtmpPacket &_pkt) {
|
||||
RtmpPacket & pkt = const_cast<RtmpPacket &>(_pkt);
|
||||
if (pkt.isCfgFrame()) {
|
||||
lock_guard<recursive_mutex> lock(m_mtxMap);
|
||||
m_mapCfgFrame.emplace(pkt.typeId, pkt);
|
||||
}
|
||||
auto _ring = m_pRing;
|
||||
m_thPool.async([_ring,pkt]() {
|
||||
_ring->write(pkt);
|
||||
});
|
||||
}
|
||||
bool seekTo(uint32_t ui32Stamp) {
|
||||
if (!m_onSeek) {
|
||||
return false;
|
||||
}
|
||||
return m_onSeek(ui32Stamp);
|
||||
}
|
||||
virtual void setOnSeek(const function<bool(uint32_t)> &cb) {
|
||||
m_onSeek = cb;
|
||||
}
|
||||
uint32_t getStamp() {
|
||||
if (!m_onStamp) {
|
||||
return 0;
|
||||
}
|
||||
return m_onStamp();
|
||||
}
|
||||
virtual void setOnStamp(const function<uint32_t()> &cb) {
|
||||
m_onStamp = cb;
|
||||
}
|
||||
protected:
|
||||
function<bool(uint32_t)> m_onSeek;
|
||||
function<uint32_t()> m_onStamp;
|
||||
private:
|
||||
AMFValue m_metadata;
|
||||
unordered_map<int, RtmpPacket> m_mapCfgFrame;
|
||||
mutable recursive_mutex m_mtxMap;
|
||||
string m_strApp; //媒体app
|
||||
string m_strId; //媒体id
|
||||
RingBuffer<RtmpPacket>::Ptr m_pRing; //rtp环形缓冲
|
||||
ThreadPool &m_thPool;
|
||||
static unordered_map<string, unordered_map<string,weak_ptr<RtmpMediaSource> > > g_mapMediaSrc; //静态的媒体源表
|
||||
static recursive_mutex g_mtxMediaSrc; ///访问静态的媒体源表的互斥锁
|
||||
};
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPMEDIASOURCE_H_ */
|
||||
231
src/Rtmp/RtmpParser.cpp
Normal file
231
src/Rtmp/RtmpParser.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* RtmpParser.cpp
|
||||
*
|
||||
* Created on: 2016年12月2日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "RtmpParser.h"
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
RtmpParser::RtmpParser(const AMFValue &val) {
|
||||
auto videoCodec = val["videocodecid"];
|
||||
auto audioCodec = val["audiocodecid"];
|
||||
|
||||
if (videoCodec.type() == AMF_STRING) {
|
||||
if (videoCodec.as_string() == "avc1") {
|
||||
//264
|
||||
m_bHaveVideo = true;
|
||||
} else {
|
||||
InfoL << "不支持RTMP视频格式:" << videoCodec.as_string();
|
||||
}
|
||||
}else if (videoCodec.type() != AMF_NULL){
|
||||
if (videoCodec.as_integer() == 7) {
|
||||
//264
|
||||
m_bHaveVideo = true;
|
||||
} else {
|
||||
InfoL << "不支持RTMP视频格式:" << videoCodec.as_integer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (audioCodec.type() == AMF_STRING) {
|
||||
if (audioCodec.as_string() == "mp4a") {
|
||||
//aac
|
||||
m_bHaveAudio = true;
|
||||
} else {
|
||||
InfoL << "不支持RTMP音频格式:" << audioCodec.as_string();
|
||||
}
|
||||
}else if (audioCodec.type() != AMF_NULL) {
|
||||
if (audioCodec.as_integer() == 10) {
|
||||
//aac
|
||||
m_bHaveAudio = true;
|
||||
} else {
|
||||
InfoL << "不支持RTMP音频格式:" << audioCodec.as_integer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!m_bHaveVideo && !m_bHaveAudio) {
|
||||
throw std::runtime_error("不支持该RTMP媒体格式");
|
||||
}
|
||||
|
||||
onCheckMedia(val);
|
||||
}
|
||||
|
||||
RtmpParser::~RtmpParser() {
|
||||
// TODO Auto-generated destructor stub
|
||||
}
|
||||
|
||||
bool RtmpParser::inputRtmp(const RtmpPacket &pkt) {
|
||||
switch (pkt.typeId) {
|
||||
case MSG_VIDEO:
|
||||
if (m_bHaveVideo) {
|
||||
return inputVideo(pkt);
|
||||
}
|
||||
return false;
|
||||
case MSG_AUDIO:
|
||||
if (m_bHaveAudio) {
|
||||
return inputAudio(pkt);
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool RtmpParser::inputVideo(const RtmpPacket& pkt) {
|
||||
if (pkt.isCfgFrame()) {
|
||||
//WarnL << " got h264 cfg";
|
||||
if (m_strSPS.size()) {
|
||||
return false;
|
||||
}
|
||||
m_strSPS.assign("\x00\x00\x00\x01", 4);
|
||||
m_strSPS.append(pkt.getH264SPS());
|
||||
|
||||
m_strPPS.assign("\x00\x00\x00\x01", 4);
|
||||
m_strPPS.append(pkt.getH264PPS());
|
||||
|
||||
getAVCInfo(pkt.getH264SPS(), m_iVideoWidth, m_iVideoHeight, m_fVideoFps);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_strSPS.size()) {
|
||||
uint32_t iTotalLen = pkt.strBuf.size();
|
||||
uint32_t iOffset = 5;
|
||||
while(iOffset + 4 < iTotalLen){
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pkt.strBuf.data() + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
iOffset += 4;
|
||||
if(iFrameLen + iOffset > iTotalLen){
|
||||
break;
|
||||
}
|
||||
_onGetH264(pkt.strBuf.data() + iOffset, iFrameLen, pkt.timeStamp);
|
||||
iOffset += iFrameLen;
|
||||
}
|
||||
}
|
||||
return pkt.isVideoKeyFrame();
|
||||
}
|
||||
inline void RtmpParser::_onGetH264(const char* pcData, int iLen, uint32_t ui32TimeStamp) {
|
||||
switch (pcData[0] & 0x1F) {
|
||||
case 5: {
|
||||
onGetH264(m_strSPS.data() + 4, m_strSPS.length() - 4, ui32TimeStamp);
|
||||
onGetH264(m_strPPS.data() + 4, m_strPPS.length() - 4, ui32TimeStamp);
|
||||
}
|
||||
case 1: {
|
||||
onGetH264(pcData, iLen, ui32TimeStamp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//WarnL <<(int)(pcData[0] & 0x1F);
|
||||
break;
|
||||
}
|
||||
}
|
||||
inline void RtmpParser::onGetH264(const char* pcData, int iLen, uint32_t ui32TimeStamp) {
|
||||
m_h264frame.type = pcData[0] & 0x1F;
|
||||
m_h264frame.timeStamp = ui32TimeStamp;
|
||||
m_h264frame.data.assign("\x0\x0\x0\x1", 4); //添加264头
|
||||
m_h264frame.data.append(pcData, iLen);
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(m_mtxCB);
|
||||
if (onVideo) {
|
||||
onVideo(m_h264frame);
|
||||
}
|
||||
}
|
||||
m_h264frame.data.clear();
|
||||
}
|
||||
|
||||
inline bool RtmpParser::inputAudio(const RtmpPacket& pkt) {
|
||||
if (pkt.isCfgFrame()) {
|
||||
if (m_strAudioCfg.size()) {
|
||||
return false;
|
||||
}
|
||||
m_strAudioCfg = pkt.getAacCfg();
|
||||
m_iSampleBit = pkt.getAudioSampleBit();
|
||||
makeAdtsHeader(m_strAudioCfg,m_adts);
|
||||
getAACInfo(m_adts, m_iSampleRate, m_iChannel);
|
||||
return false;
|
||||
}
|
||||
if (m_strAudioCfg.size()) {
|
||||
onGetAAC(pkt.strBuf.data() + 2, pkt.strBuf.size() - 2, pkt.timeStamp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
inline void RtmpParser::onGetAAC(const char* pcData, int iLen, uint32_t ui32TimeStamp) {
|
||||
//添加adts头
|
||||
memcpy(m_adts.data + 7, pcData, iLen);
|
||||
m_adts.aac_frame_length = 7 + iLen;
|
||||
m_adts.timeStamp = ui32TimeStamp;
|
||||
writeAdtsHeader(m_adts, m_adts.data);
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(m_mtxCB);
|
||||
if (onAudio) {
|
||||
onAudio(m_adts);
|
||||
}
|
||||
}
|
||||
m_adts.aac_frame_length = 7;
|
||||
|
||||
}
|
||||
inline void RtmpParser::onCheckMedia(const AMFValue& obj) {
|
||||
obj.object_for_each([&](const string &key ,const AMFValue& val) {
|
||||
if(key == "duration") {
|
||||
m_fDuration = val.as_number();
|
||||
return;
|
||||
}
|
||||
if(key == "width") {
|
||||
m_iVideoWidth = val.as_number();
|
||||
return;
|
||||
}
|
||||
if(key == "height") {
|
||||
m_iVideoHeight = val.as_number();
|
||||
return;
|
||||
}
|
||||
if(key == "framerate") {
|
||||
m_fVideoFps = val.as_number();
|
||||
return;
|
||||
}
|
||||
if(key == "audiosamplerate") {
|
||||
m_iSampleRate = val.as_number();
|
||||
return;
|
||||
}
|
||||
if(key == "audiosamplesize") {
|
||||
m_iSampleBit = val.as_number();
|
||||
return;
|
||||
}
|
||||
if(key == "stereo") {
|
||||
m_iChannel = val.as_boolean() ? 2 :1;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
130
src/Rtmp/RtmpParser.h
Normal file
130
src/Rtmp/RtmpParser.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* RtmpParser.h
|
||||
*
|
||||
* Created on: 2016年12月2日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPPARSER_H_
|
||||
#define SRC_RTMP_RTMPPARSER_H_
|
||||
|
||||
#include "Rtmp.h"
|
||||
#include "amf.h"
|
||||
#include "Player/Player.h"
|
||||
#include <unordered_map>
|
||||
#include "Util/TimeTicker.h"
|
||||
#include <functional>
|
||||
#include "Player/PlayerBase.h"
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Player;
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
class RtmpParser : public PlayerBase{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpParser> Ptr;
|
||||
RtmpParser(const AMFValue &val);
|
||||
virtual ~RtmpParser();
|
||||
|
||||
bool inputRtmp(const RtmpPacket &pkt);
|
||||
|
||||
void setOnVideoCB(const function<void(const H264Frame &frame)> &cb) override{
|
||||
lock_guard<recursive_mutex> lck(m_mtxCB);
|
||||
onVideo = cb;
|
||||
}
|
||||
void setOnAudioCB(const function<void(const AdtsFrame &frame)> &cb) override{
|
||||
lock_guard<recursive_mutex> lck(m_mtxCB);
|
||||
onAudio = cb;
|
||||
}
|
||||
|
||||
int getVideoHeight() const override{
|
||||
return m_iVideoHeight;
|
||||
}
|
||||
|
||||
int getVideoWidth() const override{
|
||||
return m_iVideoWidth;
|
||||
}
|
||||
|
||||
float getVideoFps() const override{
|
||||
return m_fVideoFps;
|
||||
}
|
||||
|
||||
int getAudioSampleRate() const override{
|
||||
return m_iSampleRate;
|
||||
}
|
||||
|
||||
int getAudioSampleBit() const override{
|
||||
return m_iSampleBit;
|
||||
}
|
||||
|
||||
int getAudioChannel() const override{
|
||||
return m_iChannel;
|
||||
}
|
||||
|
||||
const string& getPps() const override{
|
||||
return m_strPPS;
|
||||
}
|
||||
|
||||
const string& getSps() const override{
|
||||
return m_strSPS;
|
||||
}
|
||||
|
||||
const string& getAudioCfg() const override{
|
||||
return m_strAudioCfg;
|
||||
}
|
||||
bool containAudio() const override{
|
||||
return m_bHaveAudio;
|
||||
}
|
||||
bool containVideo () const override{
|
||||
return m_bHaveVideo;
|
||||
}
|
||||
bool isInited() const override{
|
||||
if (m_bHaveAudio && !m_strAudioCfg.size()) {
|
||||
return false;
|
||||
}
|
||||
if (m_bHaveVideo && !m_strSPS.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
float getDuration() const override{
|
||||
return m_fDuration;
|
||||
}
|
||||
private:
|
||||
inline void onCheckMedia(const AMFValue &obj);
|
||||
|
||||
//返回值:true 代表是i帧第一个rtp包
|
||||
inline bool inputVideo(const RtmpPacket &pkt);
|
||||
inline bool inputAudio(const RtmpPacket &pkt);
|
||||
inline void _onGetH264(const char *pcData, int iLen, uint32_t ui32TimeStamp);
|
||||
inline void onGetH264(const char *pcData, int iLen, uint32_t ui32TimeStamp);
|
||||
inline void onGetAAC(const char *pcData, int iLen, uint32_t ui32TimeStamp);
|
||||
//video
|
||||
H264Frame m_h264frame;
|
||||
//aduio
|
||||
AdtsFrame m_adts;
|
||||
|
||||
int m_iSampleRate = 44100;
|
||||
int m_iSampleBit = 16;
|
||||
int m_iChannel = 1;
|
||||
|
||||
string m_strSPS;
|
||||
string m_strPPS;
|
||||
string m_strAudioCfg;
|
||||
int m_iVideoWidth = 0;
|
||||
int m_iVideoHeight = 0;
|
||||
float m_fVideoFps = 0;
|
||||
bool m_bHaveAudio = false;
|
||||
bool m_bHaveVideo = false;
|
||||
float m_fDuration = 0;
|
||||
function<void(const H264Frame &frame)> onVideo;
|
||||
function<void(const AdtsFrame &frame)> onAudio;
|
||||
recursive_mutex m_mtxCB;
|
||||
|
||||
};
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPPARSER_H_ */
|
||||
326
src/Rtmp/RtmpPlayer.cpp
Normal file
326
src/Rtmp/RtmpPlayer.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* RtmpPlayer2.cpp
|
||||
*
|
||||
* Created on: 2016Äê11ÔÂ29ÈÕ
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "RtmpPlayer.h"
|
||||
#include "Thread/ThreadPool.hpp"
|
||||
#include "Util/util.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Rtmp/utils.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
using namespace ZL::Util;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
unordered_map<string, RtmpPlayer::rtmpCMDHandle> RtmpPlayer::g_mapCmd;
|
||||
RtmpPlayer::RtmpPlayer() {
|
||||
static onceToken token([]() {
|
||||
g_mapCmd.emplace("_error",&RtmpPlayer::onCmd_result);
|
||||
g_mapCmd.emplace("_result",&RtmpPlayer::onCmd_result);
|
||||
g_mapCmd.emplace("onStatus",&RtmpPlayer::onCmd_onStatus);
|
||||
g_mapCmd.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData);
|
||||
}, []() {});
|
||||
|
||||
}
|
||||
|
||||
RtmpPlayer::~RtmpPlayer() {
|
||||
teardown();
|
||||
DebugL << endl;
|
||||
}
|
||||
void RtmpPlayer::teardown() {
|
||||
if (alive()) {
|
||||
m_strApp.clear();
|
||||
m_strStream.clear();
|
||||
m_strTcUrl.clear();
|
||||
m_mapOnResultCB.clear();
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(m_mtxDeque);
|
||||
m_dqOnStatusCB.clear();
|
||||
}
|
||||
m_pBeatTimer.reset();
|
||||
m_pPlayTimer.reset();
|
||||
m_pMediaTimer.reset();
|
||||
m_fSeekTo = 0;
|
||||
CLEAR_ARR(m_adFistStamp);
|
||||
CLEAR_ARR(m_adNowStamp);
|
||||
clear();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::play(const char* strUrl, const char * , const char *, eRtpType) {
|
||||
teardown();
|
||||
string strHost = FindField(strUrl, "://", "/");
|
||||
m_strApp = FindField(strUrl, (strHost + "/").data(), "/");
|
||||
m_strStream = FindField(strUrl, (strHost + "/" + m_strApp + "/").data(), NULL);
|
||||
m_strTcUrl = string("rtmp://") + strHost + "/" + m_strApp;
|
||||
|
||||
if (!m_strApp.size() || !m_strStream.size()) {
|
||||
_onPlayResult(SockException(Err_other,"rtmp url非法"));
|
||||
return;
|
||||
}
|
||||
DebugL << strHost << " " << m_strApp << " " << m_strStream;
|
||||
|
||||
auto iPort = atoi(FindField(strHost.c_str(), ":", NULL).c_str());
|
||||
if (iPort <= 0) {
|
||||
//rtmp 默认端口1935
|
||||
iPort = 1935;
|
||||
} else {
|
||||
//服务器域名
|
||||
strHost = FindField(strHost.c_str(), NULL, ":");
|
||||
}
|
||||
startConnect(strHost, iPort);
|
||||
}
|
||||
void RtmpPlayer::onErr(const SockException &ex){
|
||||
_onShutdown(ex);
|
||||
}
|
||||
void RtmpPlayer::onConnect(const SockException &err){
|
||||
if(err.getErrCode()!=Err_success) {
|
||||
_onPlayResult(err);
|
||||
return;
|
||||
}
|
||||
|
||||
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
m_pPlayTimer.reset( new Timer(10, [weakSelf]() {
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->_onPlayResult(SockException(Err_timeout,"play rtmp timeout"));
|
||||
strongSelf->teardown();
|
||||
return false;
|
||||
}));
|
||||
startClientSession([weakSelf](){
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->send_connect();
|
||||
});
|
||||
}
|
||||
void RtmpPlayer::onRecv(const Socket::Buffer::Ptr &pBuf){
|
||||
try {
|
||||
onParseRtmp(pBuf->data(), pBuf->size());
|
||||
} catch (exception &e) {
|
||||
SockException ex(Err_other, e.what());
|
||||
_onPlayResult(ex);
|
||||
_onShutdown(ex);
|
||||
teardown();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::pause(bool bPause) {
|
||||
send_pause(bPause);
|
||||
}
|
||||
|
||||
inline void RtmpPlayer::send_connect() {
|
||||
AMFValue obj(AMF_OBJECT);
|
||||
obj.set("app", m_strApp);
|
||||
obj.set("tcUrl", m_strTcUrl);
|
||||
obj.set("fpad", false);
|
||||
obj.set("capabilities", 15);
|
||||
obj.set("videoFunction", 1);
|
||||
//只支持aac
|
||||
obj.set("audioCodecs", 3191);
|
||||
//只支持H264
|
||||
obj.set("videoCodecs", 252);
|
||||
sendInvoke("connect", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
//TraceL << "connect result";
|
||||
dec.load<AMFValue>();
|
||||
auto val = dec.load<AMFValue>();
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
||||
}
|
||||
send_createStream();
|
||||
});
|
||||
}
|
||||
|
||||
inline void RtmpPlayer::send_createStream() {
|
||||
AMFValue obj(AMF_NULL);
|
||||
sendInvoke("createStream", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
//TraceL << "createStream result";
|
||||
dec.load<AMFValue>();
|
||||
m_ui32StreamId = dec.load<int>();
|
||||
send_play();
|
||||
});
|
||||
}
|
||||
|
||||
inline void RtmpPlayer::send_play() {
|
||||
AMFEncoder enc;
|
||||
enc << "play" << ++m_iReqID << nullptr << m_strStream << (double)m_ui32StreamId;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
auto fun = [this](AMFValue &val){
|
||||
//TraceL << "play onStatus";
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"play 失败:" << level << " " << code << endl);
|
||||
}
|
||||
};
|
||||
addOnStatusCB(fun);
|
||||
addOnStatusCB(fun);
|
||||
}
|
||||
|
||||
inline void RtmpPlayer::send_pause(bool bPause) {
|
||||
AMFEncoder enc;
|
||||
enc << "pause" << ++m_iReqID << nullptr << bPause;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
auto fun = [this,bPause](AMFValue &val){
|
||||
//TraceL << "pause onStatus";
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status") {
|
||||
if(!bPause){
|
||||
throw std::runtime_error(StrPrinter <<"pause 恢复播放失败:" << level << " " << code << endl);
|
||||
}
|
||||
}else{
|
||||
m_bPaused = bPause;
|
||||
if(!bPause){
|
||||
_onPlayResult(SockException(Err_success, "rtmp resum success"));
|
||||
}else{
|
||||
//暂停播放
|
||||
m_pMediaTimer.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
addOnStatusCB(fun);
|
||||
|
||||
m_pBeatTimer.reset();
|
||||
if(bPause){
|
||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
m_pBeatTimer.reset(new Timer(3,[weakSelf](){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf){
|
||||
return false;
|
||||
}
|
||||
uint32_t timeStamp = ::time(NULL);
|
||||
strongSelf->sendUserControl(CONTROL_PING_REQUEST, timeStamp);
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::onCmd_result(AMFDecoder &dec){
|
||||
auto iReqId = dec.load<int>();
|
||||
auto it = m_mapOnResultCB.find(iReqId);
|
||||
if(it != m_mapOnResultCB.end()){
|
||||
it->second(dec);
|
||||
m_mapOnResultCB.erase(it);
|
||||
}else{
|
||||
WarnL << "unhandled _result";
|
||||
}
|
||||
}
|
||||
void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
||||
AMFValue val;
|
||||
while(true){
|
||||
val = dec.load<AMFValue>();
|
||||
if(val.type() == AMF_OBJECT){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(val.type() != AMF_OBJECT){
|
||||
throw std::runtime_error("onStatus: 未找到结果对象");
|
||||
}
|
||||
|
||||
lock_guard<recursive_mutex> lck(m_mtxDeque);
|
||||
if(m_dqOnStatusCB.size()){
|
||||
m_dqOnStatusCB.front()(val);
|
||||
m_dqOnStatusCB.pop_front();
|
||||
}else{
|
||||
auto level = val["level"];
|
||||
auto code = val["code"].as_string();
|
||||
if(level.type() == AMF_STRING){
|
||||
if(level.as_string() != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||
}
|
||||
}
|
||||
//WarnL << "unhandled onStatus:" << code;
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
||||
//TraceL;
|
||||
auto val = dec.load<AMFValue>();
|
||||
if(!onCheckMeta(val)){
|
||||
throw std::runtime_error("onCheckMeta faied");
|
||||
}
|
||||
_onPlayResult(SockException(Err_success,"play rtmp success"));
|
||||
}
|
||||
|
||||
void RtmpPlayer::onStreamDry(uint32_t ui32StreamId) {
|
||||
//TraceL << ui32StreamId;
|
||||
_onShutdown(SockException(Err_other,"rtmp stream dry"));
|
||||
}
|
||||
|
||||
|
||||
void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) {
|
||||
switch (chunkData.typeId) {
|
||||
case MSG_CMD:
|
||||
case MSG_CMD3:
|
||||
case MSG_DATA:
|
||||
case MSG_DATA3: {
|
||||
AMFDecoder dec(chunkData.strBuf, 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
auto it = g_mapCmd.find(type);
|
||||
if(it != g_mapCmd.end()){
|
||||
auto fun = it->second;
|
||||
(this->*fun)(dec);
|
||||
}else{
|
||||
WarnL << "can not support cmd:" << type;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MSG_AUDIO:
|
||||
case MSG_VIDEO: {
|
||||
auto idx = chunkData.typeId%2;
|
||||
if (m_aNowStampTicker[idx].elapsedTime() > 500) {
|
||||
m_adNowStamp[idx] = chunkData.timeStamp;
|
||||
}
|
||||
_onMediaData(chunkData);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float RtmpPlayer::getProgressTime() const{
|
||||
double iTime[2] = {0,0};
|
||||
for(auto i = 0 ;i < 2 ;i++){
|
||||
iTime[i] = (m_adNowStamp[i] - m_adFistStamp[i]) / 1000.0;
|
||||
}
|
||||
return m_fSeekTo + MAX(iTime[0],iTime[1]);
|
||||
}
|
||||
void RtmpPlayer::seekToTime(float fTime){
|
||||
if (m_bPaused) {
|
||||
pause(false);
|
||||
}
|
||||
AMFEncoder enc;
|
||||
enc << "seek" << ++m_iReqID << nullptr << fTime * 1000.0;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
addOnStatusCB([this,fTime](AMFValue &val) {
|
||||
//TraceL << "seek result";
|
||||
m_aNowStampTicker[0].resetTime();
|
||||
m_aNowStampTicker[1].resetTime();
|
||||
float iTimeInc = fTime - getProgressTime();
|
||||
for(auto i = 0 ;i < 2 ;i++){
|
||||
m_adFistStamp[i] = m_adNowStamp[i] + iTimeInc * 1000.0;
|
||||
m_adNowStamp[i] = m_adFistStamp[i];
|
||||
}
|
||||
m_fSeekTo = fTime;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
143
src/Rtmp/RtmpPlayer.h
Normal file
143
src/Rtmp/RtmpPlayer.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* RtmpPlayer2.h
|
||||
*
|
||||
* Created on: 2016年11月29日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RtmpPlayer2_H_
|
||||
#define SRC_RTMP_RtmpPlayer2_H_
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include "Player/PlayerBase.h"
|
||||
#include <string>
|
||||
#include "Util/logger.h"
|
||||
#include "Util/util.h"
|
||||
#include "Network/Socket.hpp"
|
||||
#include "Network/TcpClient.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include <functional>
|
||||
#include "Rtmp.h"
|
||||
#include <memory>
|
||||
#include "amf.h"
|
||||
#include "RtmpProtocol.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Player;
|
||||
using namespace ZL::Network;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
class RtmpPlayer:public PlayerBase, public TcpClient, public RtmpProtocol{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPlayer> Ptr;
|
||||
RtmpPlayer();
|
||||
virtual ~RtmpPlayer();
|
||||
|
||||
void play(const char* strUrl, const char *strUser, const char *strPwd,
|
||||
eRtpType eType) override;
|
||||
void pause(bool bPause) override;
|
||||
void teardown() override;
|
||||
protected:
|
||||
virtual bool onCheckMeta(AMFValue &val) =0;
|
||||
virtual void onMediaData(RtmpPacket &chunkData) =0;
|
||||
float getProgressTime() const;
|
||||
void seekToTime(float fTime);
|
||||
private:
|
||||
void _onShutdown(const SockException &ex) {
|
||||
WarnL << ex.getErrCode() << " " << ex.what();
|
||||
m_pPlayTimer.reset();
|
||||
m_pMediaTimer.reset();
|
||||
m_pBeatTimer.reset();
|
||||
onShutdown(ex);
|
||||
}
|
||||
void _onMediaData(RtmpPacket &chunkData) {
|
||||
m_mediaTicker.resetTime();
|
||||
onMediaData(chunkData);
|
||||
}
|
||||
void _onPlayResult(const SockException &ex) {
|
||||
WarnL << ex.getErrCode() << " " << ex.what();
|
||||
m_pPlayTimer.reset();
|
||||
m_pMediaTimer.reset();
|
||||
if (!ex) {
|
||||
m_mediaTicker.resetTime();
|
||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
m_pMediaTimer.reset( new Timer(5, [weakSelf]() {
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
if(strongSelf->m_mediaTicker.elapsedTime()>10000) {
|
||||
//recv media timeout!
|
||||
strongSelf->_onShutdown(SockException(Err_timeout,"recv rtmp timeout"));
|
||||
strongSelf->teardown();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
onPlayResult(ex);
|
||||
}
|
||||
|
||||
//for Tcpclient
|
||||
void onRecv(const Socket::Buffer::Ptr &pBuf) override;
|
||||
void onConnect(const SockException &err) override;
|
||||
void onErr(const SockException &ex) override;
|
||||
//fro RtmpProtocol
|
||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||
void onStreamDry(uint32_t ui32StreamId) override;
|
||||
void onSendRawData(const char *pcRawData, int iSize) override {
|
||||
send(pcRawData, iSize);
|
||||
}
|
||||
|
||||
template<typename FUN>
|
||||
inline void addOnResultCB(const FUN &fun) {
|
||||
m_mapOnResultCB.emplace(m_iReqID, fun);
|
||||
}
|
||||
template<typename FUN>
|
||||
inline void addOnStatusCB(const FUN &fun) {
|
||||
lock_guard<recursive_mutex> lck(m_mtxDeque);
|
||||
m_dqOnStatusCB.emplace_back(fun);
|
||||
}
|
||||
|
||||
void onCmd_result(AMFDecoder &dec);
|
||||
void onCmd_onStatus(AMFDecoder &dec);
|
||||
void onCmd_onMetaData(AMFDecoder &dec);
|
||||
|
||||
inline void send_connect();
|
||||
inline void send_createStream();
|
||||
inline void send_play();
|
||||
inline void send_pause(bool bPause);
|
||||
|
||||
string m_strApp;
|
||||
string m_strStream;
|
||||
string m_strTcUrl;
|
||||
bool m_bPaused = false;
|
||||
|
||||
unordered_map<int, function<void(AMFDecoder &dec)> > m_mapOnResultCB;
|
||||
deque<function<void(AMFValue &dec)> > m_dqOnStatusCB;
|
||||
recursive_mutex m_mtxDeque;
|
||||
|
||||
typedef void (RtmpPlayer::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||
|
||||
//超时功能实现
|
||||
Ticker m_mediaTicker;
|
||||
std::shared_ptr<Timer> m_pMediaTimer;
|
||||
std::shared_ptr<Timer> m_pPlayTimer;
|
||||
//心跳定时器
|
||||
std::shared_ptr<Timer> m_pBeatTimer;
|
||||
|
||||
//播放进度控制
|
||||
float m_fSeekTo = 0;
|
||||
double m_adFistStamp[2] = { 0, 0 };
|
||||
double m_adNowStamp[2] = { 0, 0 };
|
||||
Ticker m_aNowStampTicker[2];
|
||||
};
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RtmpPlayer2_H_ */
|
||||
23
src/Rtmp/RtmpPlayerImp.cpp
Normal file
23
src/Rtmp/RtmpPlayerImp.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* RtmpPlayerImp.cpp
|
||||
*
|
||||
* Created on: 2016年12月1日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "RtmpPlayerImp.h"
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
RtmpPlayerImp::RtmpPlayerImp() {
|
||||
|
||||
}
|
||||
|
||||
RtmpPlayerImp::~RtmpPlayerImp() {
|
||||
DebugL<<endl;
|
||||
teardown();
|
||||
}
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
62
src/Rtmp/RtmpPlayerImp.h
Normal file
62
src/Rtmp/RtmpPlayerImp.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* RtmpPlayerImp.h
|
||||
*
|
||||
* Created on: 2016年12月1日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPPLAYERIMP_H_
|
||||
#define SRC_RTMP_RTMPPLAYERIMP_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "RtmpPlayer.h"
|
||||
#include "RtmpParser.h"
|
||||
#include "config.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Poller/Timer.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Player;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
class RtmpPlayerImp: public PlayerImp<RtmpPlayer,RtmpParser> {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPlayerImp> Ptr;
|
||||
RtmpPlayerImp();
|
||||
virtual ~RtmpPlayerImp();
|
||||
float getProgresss() const override{
|
||||
if(getDuration() > 0){
|
||||
return getProgressTime() / getDuration();
|
||||
}
|
||||
return PlayerBase::getProgresss();
|
||||
};
|
||||
void seekTo(float fProgress) override{
|
||||
fProgress = MAX(float(0),MIN(fProgress,float(1.0)));
|
||||
seekToTime(fProgress * getDuration());
|
||||
};
|
||||
private:
|
||||
//派生类回调函数
|
||||
bool onCheckMeta(AMFValue &val) override {
|
||||
try {
|
||||
m_parser.reset(new RtmpParser(val));
|
||||
m_parser->setOnVideoCB(m_onGetVideoCB);
|
||||
m_parser->setOnAudioCB(m_onGetAudioCB);
|
||||
return true;
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void onMediaData(RtmpPacket &chunkData) override {
|
||||
m_parser->inputRtmp(chunkData);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPPLAYERIMP_H_ */
|
||||
435
src/Rtmp/RtmpProtocol.cpp
Normal file
435
src/Rtmp/RtmpProtocol.cpp
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* RtmpProtocol.cpp
|
||||
*
|
||||
* Created on: 2017年2月7日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "RtmpProtocol.h"
|
||||
#include "Thread/ThreadPool.hpp"
|
||||
#include "Util/util.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Rtmp/utils.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
using namespace ZL::Util;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
RtmpProtocol::RtmpProtocol() {
|
||||
m_nextHandle = [this](){
|
||||
handle_C0C1();
|
||||
};
|
||||
}
|
||||
RtmpProtocol::~RtmpProtocol() {
|
||||
clear();
|
||||
}
|
||||
void RtmpProtocol::clear() {
|
||||
////////////ChunkSize////////////
|
||||
m_iChunkLenIn = DEFAULT_CHUNK_LEN;
|
||||
m_iChunkLenOut = DEFAULT_CHUNK_LEN;
|
||||
////////////Acknowledgement////////////
|
||||
m_ui32ByteSent = 0;
|
||||
m_ui32LastSent = 0;
|
||||
m_ui32WinSize = 0;
|
||||
///////////PeerBandwidth///////////
|
||||
m_ui32Bandwidth = 2500000;
|
||||
m_ui8LimitType = 2;
|
||||
////////////Chunk////////////
|
||||
m_mapChunkData.clear();
|
||||
m_iNowStreamID = 0;
|
||||
m_iNowChunkID = 0;
|
||||
//////////Invoke Request//////////
|
||||
m_iReqID = 0;
|
||||
//////////Rtmp parser//////////
|
||||
m_strRcvBuf.clear();
|
||||
m_ui32StreamId = STREAM_CONTROL;
|
||||
m_nextHandle = [this]() {
|
||||
handle_C0C1();
|
||||
};
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendAcknowledgement(uint32_t ui32Size) {
|
||||
std::string control;
|
||||
uint32_t stream = htonl(ui32Size);
|
||||
control.append((char *) &stream, 4);
|
||||
sendRequest(MSG_ACK, control);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendAcknowledgementSize(uint32_t ui32Size) {
|
||||
uint32_t windowSize = htonl(ui32Size);
|
||||
std::string set_windowSize((char *) &windowSize, 4);
|
||||
sendRequest(MSG_WIN_SIZE, set_windowSize);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendPeerBandwidth(uint32_t ui32Size) {
|
||||
uint32_t peerBandwidth = htonl(ui32Size);
|
||||
std::string set_peerBandwidth((char *) &peerBandwidth, 4);
|
||||
set_peerBandwidth.push_back((char) 0x02);
|
||||
sendRequest(MSG_SET_PEER_BW, set_peerBandwidth);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendChunkSize(uint32_t ui32Size) {
|
||||
uint32_t len = htonl(ui32Size);
|
||||
std::string set_chunk((char *) &len, 4);
|
||||
sendRequest(MSG_SET_CHUNK, set_chunk);
|
||||
m_iChunkLenOut = ui32Size;
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendPingRequest(uint32_t ui32TimeStamp) {
|
||||
sendUserControl(CONTROL_PING_REQUEST, ui32TimeStamp);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendPingResponse(uint32_t ui32TimeStamp) {
|
||||
sendUserControl(CONTROL_PING_RESPONSE, ui32TimeStamp);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendSetBufferLength(uint32_t ui32StreamId,
|
||||
uint32_t ui32Length) {
|
||||
std::string control;
|
||||
ui32StreamId = htonl(ui32StreamId);
|
||||
control.append((char *) &ui32StreamId, 4);
|
||||
ui32Length = htonl(ui32Length);
|
||||
control.append((char *) &ui32Length, 4);
|
||||
sendUserControl(CONTROL_SETBUFFER, control);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendUserControl(uint16_t ui16EventType,
|
||||
uint32_t ui32EventData) {
|
||||
std::string control;
|
||||
uint16_t type = htons(ui16EventType);
|
||||
control.append((char *) &type, 2);
|
||||
uint32_t stream = htonl(ui32EventData);
|
||||
control.append((char *) &stream, 4);
|
||||
sendRequest(MSG_USER_CONTROL, control);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendUserControl(uint16_t ui16EventType,
|
||||
const string& strEventData) {
|
||||
std::string control;
|
||||
uint16_t type = htons(ui16EventType);
|
||||
control.append((char *) &type, 2);
|
||||
control.append(strEventData);
|
||||
sendRequest(MSG_USER_CONTROL, control);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendResponse(int iType, const string& str) {
|
||||
sendRtmp(iType, m_iNowStreamID, str, 0, m_iNowChunkID);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendInvoke(const string& strCmd, const AMFValue& val) {
|
||||
AMFEncoder enc;
|
||||
enc << strCmd << ++m_iReqID << val;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendRequest(int iCmd, const string& str) {
|
||||
sendRtmp(iCmd, m_ui32StreamId, str, 0, CHUNK_SERVER_REQUEST);
|
||||
}
|
||||
|
||||
void RtmpProtocol::sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId,
|
||||
const std::string& strBuf, uint32_t ui32TimeStamp, int iChunkId) {
|
||||
if (iChunkId < 2 || iChunkId > 63) {
|
||||
auto strErr = StrPrinter << "不支持发送该类型的块流 ID:" << iChunkId << endl;
|
||||
throw std::runtime_error(strErr);
|
||||
}
|
||||
|
||||
bool bExtStamp = ui32TimeStamp >= 0xFFFFFF;
|
||||
RtmpHeader header;
|
||||
header.flags = (iChunkId & 0x3f) | (0 << 6);
|
||||
header.typeId = ui8Type;
|
||||
set_be24(header.timeStamp, bExtStamp ? 0xFFFFFF : ui32TimeStamp);
|
||||
set_be24(header.bodySize, strBuf.size());
|
||||
set_le32(header.streamId, ui32StreamId);
|
||||
std::string strSend;
|
||||
strSend.append((char *) &header, sizeof header);
|
||||
char acExtStamp[4];
|
||||
if (bExtStamp) {
|
||||
//扩展时间戳
|
||||
set_be32(acExtStamp, ui32TimeStamp);
|
||||
}
|
||||
size_t pos = 0;
|
||||
while (pos < strBuf.size()) {
|
||||
if (pos) {
|
||||
uint8_t flags = (iChunkId & 0x3f) | (3 << 6);
|
||||
strSend += char(flags);
|
||||
}
|
||||
if (bExtStamp) {
|
||||
//扩展时间戳
|
||||
strSend.append(acExtStamp, 4);
|
||||
}
|
||||
size_t chunk = min(m_iChunkLenOut, strBuf.size() - pos);
|
||||
strSend.append(strBuf, pos, chunk);
|
||||
pos += chunk;
|
||||
}
|
||||
onSendRawData(strSend.data(),strSend.size());
|
||||
m_ui32ByteSent += strSend.size();
|
||||
if (m_ui32WinSize > 0 && m_ui32ByteSent - m_ui32LastSent >= m_ui32WinSize) {
|
||||
m_ui32LastSent = m_ui32ByteSent;
|
||||
sendAcknowledgement(m_ui32ByteSent);
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpProtocol::onParseRtmp(const char *pcRawData, int iSize) {
|
||||
m_strRcvBuf.append(pcRawData, iSize);
|
||||
auto cb = m_nextHandle;
|
||||
cb();
|
||||
}
|
||||
|
||||
////for client////
|
||||
void RtmpProtocol::startClientSession(const function<void()> &callBack) {
|
||||
//发送 C0C1
|
||||
char handshake_head = HANDSHAKE_PLAINTEXT;
|
||||
onSendRawData(&handshake_head, 1);
|
||||
RtmpHandshake c0c1(::time(NULL));
|
||||
onSendRawData((char *) (&c0c1), sizeof(RtmpHandshake));
|
||||
m_nextHandle = [this,callBack]() {
|
||||
//等待 S0+S1+S2
|
||||
handle_S0S1S2(callBack);
|
||||
};
|
||||
}
|
||||
void RtmpProtocol::handle_S0S1S2(const function<void()> &callBack) {
|
||||
if (m_strRcvBuf.size() < 1 + 2 * sizeof(RtmpHandshake)) {
|
||||
//数据不够
|
||||
return;
|
||||
}
|
||||
if (m_strRcvBuf[0] != HANDSHAKE_PLAINTEXT) {
|
||||
throw std::runtime_error("only plaintext[0x03] handshake supported");
|
||||
}
|
||||
//发送 C2
|
||||
const char *pcC2 = m_strRcvBuf.data() + 1;
|
||||
onSendRawData(pcC2, sizeof(RtmpHandshake));
|
||||
m_strRcvBuf.erase(0, 1 + 2 * sizeof(RtmpHandshake));
|
||||
//握手结束
|
||||
m_nextHandle = [this]() {
|
||||
//握手结束并且开始进入解析命令模式
|
||||
handle_rtmp();
|
||||
};
|
||||
callBack();
|
||||
}
|
||||
////for server ////
|
||||
void RtmpProtocol::handle_C0C1() {
|
||||
if (m_strRcvBuf.size() < 1 + sizeof(RtmpHandshake)) {
|
||||
//need more data!
|
||||
return;
|
||||
}
|
||||
if (m_strRcvBuf[0] != HANDSHAKE_PLAINTEXT) {
|
||||
throw std::runtime_error("only plaintext[0x03] handshake supported");
|
||||
}
|
||||
char handshake_head = HANDSHAKE_PLAINTEXT;
|
||||
onSendRawData(&handshake_head, 1);
|
||||
//发送S2
|
||||
RtmpHandshake s2(0);
|
||||
onSendRawData((char *) &s2, sizeof(RtmpHandshake));
|
||||
//发送S0S1
|
||||
onSendRawData(m_strRcvBuf.c_str() + 1, sizeof(RtmpHandshake));
|
||||
m_strRcvBuf.erase(0, 1 + sizeof(RtmpHandshake));
|
||||
//等待C2
|
||||
m_nextHandle = [this]() {
|
||||
handle_C2();
|
||||
};
|
||||
}
|
||||
|
||||
void RtmpProtocol::handle_C2() {
|
||||
if (m_strRcvBuf.size() < sizeof(RtmpHandshake)) {
|
||||
//need more data!
|
||||
return;
|
||||
}
|
||||
m_strRcvBuf.erase(0, sizeof(RtmpHandshake));
|
||||
//握手结束,进入命令模式
|
||||
if (!m_strRcvBuf.empty()) {
|
||||
handle_rtmp();
|
||||
}
|
||||
m_nextHandle = [this]() {
|
||||
handle_rtmp();
|
||||
};
|
||||
}
|
||||
|
||||
void RtmpProtocol::handle_rtmp() {
|
||||
while (!m_strRcvBuf.empty()) {
|
||||
uint8_t flags = m_strRcvBuf[0];
|
||||
int iOffset = 0;
|
||||
static const size_t HEADER_LENGTH[] = { 12, 8, 4, 1 };
|
||||
size_t iHeaderLen = HEADER_LENGTH[flags >> 6];
|
||||
m_iNowChunkID = flags & 0x3f;
|
||||
switch (m_iNowChunkID) {
|
||||
case 0: {
|
||||
//0 值表示二字节形式,并且 ID 范围 64 - 319
|
||||
//(第二个字节 + 64)。
|
||||
if (m_strRcvBuf.size() < 2) {
|
||||
//need more data
|
||||
return;
|
||||
}
|
||||
m_iNowChunkID = 64 + (uint8_t) (m_strRcvBuf[1]);
|
||||
iOffset = 1;
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
//1 值表示三字节形式,并且 ID 范围为 64 - 65599
|
||||
//((第三个字节) * 256 + 第二个字节 + 64)。
|
||||
if (m_strRcvBuf.size() < 3) {
|
||||
//need more data
|
||||
return;
|
||||
}
|
||||
m_iNowChunkID = 64 + ((uint8_t) (m_strRcvBuf[2]) << 8) + (uint8_t) (m_strRcvBuf[1]);
|
||||
iOffset = 2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//带有 2 值的块流 ID 被保留,用于下层协议控制消息和命令。
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_strRcvBuf.size() < iHeaderLen + iOffset) {
|
||||
//need more data
|
||||
return;
|
||||
}
|
||||
RtmpHeader &header = *((RtmpHeader *) (m_strRcvBuf.data() + iOffset));
|
||||
auto &chunkData = m_mapChunkData[m_iNowChunkID];
|
||||
chunkData.chunkId = m_iNowChunkID;
|
||||
switch (iHeaderLen) {
|
||||
case 12:
|
||||
chunkData.streamId = load_le32(header.streamId);
|
||||
case 8:
|
||||
chunkData.bodySize = load_be24(header.bodySize);
|
||||
chunkData.typeId = header.typeId;
|
||||
case 4:
|
||||
uint32_t ts = load_be24(header.timeStamp);
|
||||
if (ts == 0xFFFFFF) {
|
||||
chunkData.extStamp = true;
|
||||
}else{
|
||||
chunkData.extStamp = false;
|
||||
chunkData.timeStamp = ts + ((iHeaderLen == 12) ? 0 : chunkData.timeStamp);
|
||||
}
|
||||
}
|
||||
if (chunkData.extStamp) {
|
||||
if (m_strRcvBuf.size() < iHeaderLen + iOffset + 4) {
|
||||
//need more data
|
||||
return;
|
||||
}
|
||||
chunkData.timeStamp = load_be32( m_strRcvBuf.data() + iOffset + iHeaderLen);
|
||||
iOffset += 4;
|
||||
}
|
||||
|
||||
if (chunkData.bodySize == 0 || chunkData.bodySize < chunkData.strBuf.size()) {
|
||||
throw std::runtime_error("非法的bodySize");
|
||||
}
|
||||
|
||||
auto iMore = min(m_iChunkLenIn, chunkData.bodySize - chunkData.strBuf.size());
|
||||
if (m_strRcvBuf.size() < iHeaderLen + iOffset + iMore) {
|
||||
//need more data
|
||||
return;
|
||||
}
|
||||
chunkData.strBuf.append(m_strRcvBuf, iHeaderLen + iOffset, iMore);
|
||||
m_strRcvBuf.erase(0, iHeaderLen + iOffset + iMore);
|
||||
if (chunkData.strBuf.size() == chunkData.bodySize) {
|
||||
m_iNowStreamID = chunkData.streamId;
|
||||
handle_rtmpChunk(chunkData);
|
||||
chunkData.strBuf.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpProtocol::handle_rtmpChunk(RtmpPacket& chunkData) {
|
||||
switch (chunkData.typeId) {
|
||||
case MSG_ACK: {
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("MSG_ACK: Not enough data");
|
||||
}
|
||||
//auto bytePeerRecv = load_be32(&chunkData.strBuf[0]);
|
||||
//TraceL << "MSG_ACK:" << bytePeerRecv;
|
||||
}
|
||||
break;
|
||||
case MSG_SET_CHUNK: {
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("MSG_SET_CHUNK :Not enough data");
|
||||
}
|
||||
m_iChunkLenIn = load_be32(&chunkData.strBuf[0]);
|
||||
TraceL << "MSG_SET_CHUNK:" << m_iChunkLenIn;
|
||||
}
|
||||
break;
|
||||
case MSG_USER_CONTROL: {
|
||||
//user control message
|
||||
if (chunkData.strBuf.size() < 2) {
|
||||
throw std::runtime_error("MSG_USER_CONTROL: Not enough data.");
|
||||
}
|
||||
uint16_t event_type = load_be16(&chunkData.strBuf[0]);
|
||||
chunkData.strBuf.erase(0, 2);
|
||||
switch (event_type) {
|
||||
case CONTROL_PING_REQUEST: {
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("CONTROL_PING_REQUEST: Not enough data.");
|
||||
}
|
||||
uint32_t timeStamp = load_be32(&chunkData.strBuf[0]);
|
||||
//TraceL << "CONTROL_PING_REQUEST:" << timeStamp;
|
||||
sendUserControl(CONTROL_PING_RESPONSE, timeStamp);
|
||||
}
|
||||
break;
|
||||
case CONTROL_PING_RESPONSE: {
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("CONTROL_PING_RESPONSE: Not enough data.");
|
||||
}
|
||||
//uint32_t timeStamp = load_be32(&chunkData.strBuf[0]);
|
||||
//TraceL << "CONTROL_PING_RESPONSE:" << timeStamp;
|
||||
}
|
||||
break;
|
||||
case CONTROL_STREAM_BEGIN: {
|
||||
//开始播放
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("CONTROL_STREAM_BEGIN: Not enough data.");
|
||||
}
|
||||
uint32_t stramId = load_be32(&chunkData.strBuf[0]);
|
||||
onStreamBegin(stramId);
|
||||
TraceL << "CONTROL_STREAM_BEGIN:" << stramId;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTROL_STREAM_EOF: {
|
||||
//暂停
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("CONTROL_STREAM_EOF: Not enough data.");
|
||||
}
|
||||
uint32_t stramId = load_be32(&chunkData.strBuf[0]);
|
||||
onStreamEof(stramId);
|
||||
TraceL << "CONTROL_STREAM_EOF:" << stramId;
|
||||
}
|
||||
break;
|
||||
case CONTROL_STREAM_DRY: {
|
||||
//停止播放
|
||||
if (chunkData.strBuf.size() < 4) {
|
||||
throw std::runtime_error("CONTROL_STREAM_DRY: Not enough data.");
|
||||
}
|
||||
uint32_t stramId = load_be32(&chunkData.strBuf[0]);
|
||||
onStreamDry(stramId);
|
||||
TraceL << "CONTROL_STREAM_DRY:" << stramId;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//WarnL << "unhandled user control:" << event_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MSG_WIN_SIZE: {
|
||||
m_ui32WinSize = load_be32(&chunkData.strBuf[0]);
|
||||
TraceL << "MSG_WIN_SIZE:" << m_ui32WinSize;
|
||||
}
|
||||
break;
|
||||
case MSG_SET_PEER_BW: {
|
||||
m_ui32Bandwidth = load_be32(&chunkData.strBuf[0]);
|
||||
m_ui8LimitType = chunkData.strBuf[4];
|
||||
TraceL << "MSG_SET_PEER_BW:" << m_ui32WinSize;
|
||||
}
|
||||
break;
|
||||
case MSG_AGGREGATE:
|
||||
throw std::runtime_error("streaming FLV not supported");
|
||||
break;
|
||||
default:
|
||||
onRtmpChunk(chunkData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
93
src/Rtmp/RtmpProtocol.h
Normal file
93
src/Rtmp/RtmpProtocol.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* RtmpProtocol.h
|
||||
*
|
||||
* Created on: 2017年2月7日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPPROTOCOL_H_
|
||||
#define SRC_RTMP_RTMPPROTOCOL_H_
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <string>
|
||||
#include "Util/logger.h"
|
||||
#include "Util/util.h"
|
||||
#include "Network/Socket.hpp"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include <functional>
|
||||
#include "Rtmp.h"
|
||||
#include <memory>
|
||||
#include "amf.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Network;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
class RtmpProtocol {
|
||||
public:
|
||||
RtmpProtocol();
|
||||
virtual ~RtmpProtocol();
|
||||
//作为客户端发送c0c1,等待s0s1s2并且回调
|
||||
void startClientSession(const function<void()> &cb);
|
||||
void onParseRtmp(const char *pcRawData,int iSize);
|
||||
void clear();
|
||||
protected:
|
||||
virtual void onSendRawData(const char *pcRawData,int iSize) = 0;
|
||||
virtual void onRtmpChunk(RtmpPacket &chunkData) = 0;
|
||||
|
||||
virtual void onStreamBegin(uint32_t ui32StreamId){
|
||||
m_ui32StreamId = ui32StreamId;
|
||||
}
|
||||
virtual void onStreamEof(uint32_t ui32StreamId){};
|
||||
virtual void onStreamDry(uint32_t ui32StreamId){};
|
||||
protected:
|
||||
void sendAcknowledgement(uint32_t ui32Size);
|
||||
void sendAcknowledgementSize(uint32_t ui32Size);
|
||||
void sendPeerBandwidth(uint32_t ui32Size);
|
||||
void sendChunkSize(uint32_t ui32Size);
|
||||
void sendPingRequest(uint32_t ui32TimeStamp = ::time(NULL));
|
||||
void sendPingResponse(uint32_t ui32TimeStamp = ::time(NULL));
|
||||
void sendSetBufferLength(uint32_t ui32StreamId, uint32_t ui32Length);
|
||||
void sendUserControl(uint16_t ui16EventType, uint32_t ui32EventData);
|
||||
void sendUserControl(uint16_t ui16EventType, const string &strEventData);
|
||||
|
||||
void sendInvoke(const string &strCmd, const AMFValue &val);
|
||||
void sendRequest(int iCmd, const string &str);
|
||||
void sendResponse(int iType, const string &str);
|
||||
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const std::string &strBuf, uint32_t ui32TimeStamp, int iChunkID);
|
||||
protected:
|
||||
int m_iReqID = 0;
|
||||
uint32_t m_ui32StreamId = STREAM_CONTROL;
|
||||
private:
|
||||
void handle_S0S1S2(const function<void()> &cb);
|
||||
void handle_C0C1();
|
||||
void handle_C2();
|
||||
void handle_rtmp();
|
||||
void handle_rtmpChunk(RtmpPacket &chunkData);
|
||||
|
||||
////////////ChunkSize////////////
|
||||
size_t m_iChunkLenIn = DEFAULT_CHUNK_LEN;
|
||||
size_t m_iChunkLenOut = DEFAULT_CHUNK_LEN;
|
||||
////////////Acknowledgement////////////
|
||||
uint32_t m_ui32ByteSent = 0;
|
||||
uint32_t m_ui32LastSent = 0;
|
||||
uint32_t m_ui32WinSize = 0;
|
||||
///////////PeerBandwidth///////////
|
||||
uint32_t m_ui32Bandwidth = 2500000;
|
||||
uint8_t m_ui8LimitType = 2;
|
||||
////////////Chunk////////////
|
||||
unordered_map<int, RtmpPacket> m_mapChunkData;
|
||||
int m_iNowStreamID = 0;
|
||||
int m_iNowChunkID = 0;
|
||||
//////////Rtmp parser//////////
|
||||
string m_strRcvBuf;
|
||||
function<void()> m_nextHandle;
|
||||
};
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPPROTOCOL_H_ */
|
||||
260
src/Rtmp/RtmpPusher.cpp
Normal file
260
src/Rtmp/RtmpPusher.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* RtmpPusher.cpp
|
||||
*
|
||||
* Created on: 2017年2月13日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "RtmpPusher.h"
|
||||
#include "Thread/ThreadPool.hpp"
|
||||
#include "Util/util.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Rtmp/utils.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
|
||||
using namespace ZL::Util;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
unordered_map<string, RtmpPusher::rtmpCMDHandle> RtmpPusher::g_mapCmd;
|
||||
RtmpPusher::RtmpPusher(const char *strApp,const char *strStream) {
|
||||
static onceToken token([]() {
|
||||
g_mapCmd.emplace("_error",&RtmpPusher::onCmd_result);
|
||||
g_mapCmd.emplace("_result",&RtmpPusher::onCmd_result);
|
||||
g_mapCmd.emplace("onStatus",&RtmpPusher::onCmd_onStatus);
|
||||
}, []() {});
|
||||
auto src = RtmpMediaSource::find(strApp,strStream);
|
||||
if (!src) {
|
||||
auto strErr = StrPrinter << "媒体源:" << strApp << "/" << strStream << "不存在" << endl;
|
||||
throw std::runtime_error(strErr);
|
||||
}
|
||||
m_pMediaSrc = src;
|
||||
}
|
||||
|
||||
RtmpPusher::~RtmpPusher() {
|
||||
teardown();
|
||||
DebugL << endl;
|
||||
}
|
||||
void RtmpPusher::teardown() {
|
||||
if (alive()) {
|
||||
m_strApp.clear();
|
||||
m_strStream.clear();
|
||||
m_strTcUrl.clear();
|
||||
m_mapOnResultCB.clear();
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(m_mtxDeque);
|
||||
m_dqOnStatusCB.clear();
|
||||
}
|
||||
m_pPlayTimer.reset();
|
||||
clear();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::publish(const char* strUrl) {
|
||||
teardown();
|
||||
string strHost = FindField(strUrl, "://", "/");
|
||||
m_strApp = FindField(strUrl, (strHost + "/").data(), "/");
|
||||
m_strStream = FindField(strUrl, (strHost + "/" + m_strApp + "/").data(), NULL);
|
||||
m_strTcUrl = string("rtmp://") + strHost + "/" + m_strApp;
|
||||
|
||||
if (!m_strApp.size() || !m_strStream.size()) {
|
||||
_onPlayResult(SockException(Err_other,"rtmp url非法"));
|
||||
return;
|
||||
}
|
||||
DebugL << strHost << " " << m_strApp << " " << m_strStream;
|
||||
|
||||
auto iPort = atoi(FindField(strHost.c_str(), ":", NULL).c_str());
|
||||
if (iPort <= 0) {
|
||||
//rtmp 默认端口1935
|
||||
iPort = 1935;
|
||||
} else {
|
||||
//服务器域名
|
||||
strHost = FindField(strHost.c_str(), NULL, ":");
|
||||
}
|
||||
startConnect(strHost, iPort);
|
||||
}
|
||||
|
||||
void RtmpPusher::onErr(const SockException &ex){
|
||||
_onShutdown(ex);
|
||||
}
|
||||
void RtmpPusher::onConnect(const SockException &err){
|
||||
if(err.getErrCode()!=Err_success) {
|
||||
_onPlayResult(err);
|
||||
return;
|
||||
}
|
||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
m_pPlayTimer.reset( new Timer(10, [weakSelf]() {
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->_onPlayResult(SockException(Err_timeout,"publish rtmp timeout"));
|
||||
strongSelf->teardown();
|
||||
return false;
|
||||
}));
|
||||
startClientSession([weakSelf](){
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
//strongSelf->sendChunkSize(60000);
|
||||
strongSelf->send_connect();
|
||||
});
|
||||
}
|
||||
void RtmpPusher::onRecv(const Socket::Buffer::Ptr &pBuf){
|
||||
try {
|
||||
onParseRtmp(pBuf->data(), pBuf->size());
|
||||
} catch (exception &e) {
|
||||
SockException ex(Err_other, e.what());
|
||||
_onPlayResult(ex);
|
||||
_onShutdown(ex);
|
||||
teardown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline void RtmpPusher::send_connect() {
|
||||
AMFValue obj(AMF_OBJECT);
|
||||
obj.set("app", m_strApp);
|
||||
obj.set("type", "nonprivate");
|
||||
obj.set("tcUrl", m_strTcUrl);
|
||||
obj.set("swfUrl", m_strTcUrl);
|
||||
sendInvoke("connect", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
//TraceL << "connect result";
|
||||
dec.load<AMFValue>();
|
||||
auto val = dec.load<AMFValue>();
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
||||
}
|
||||
send_createStream();
|
||||
});
|
||||
}
|
||||
|
||||
inline void RtmpPusher::send_createStream() {
|
||||
AMFValue obj(AMF_NULL);
|
||||
sendInvoke("createStream", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
//TraceL << "createStream result";
|
||||
dec.load<AMFValue>();
|
||||
m_ui32StreamId = dec.load<int>();
|
||||
send_publish();
|
||||
});
|
||||
}
|
||||
inline void RtmpPusher::send_publish() {
|
||||
AMFEncoder enc;
|
||||
enc << "publish" << ++m_iReqID << nullptr << m_strStream << m_strApp ;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
|
||||
addOnStatusCB([this](AMFValue &val) {
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status") {
|
||||
throw std::runtime_error(StrPrinter <<"publish 失败:" << level << " " << code << endl);
|
||||
}
|
||||
//start send media
|
||||
send_metaData();
|
||||
});
|
||||
}
|
||||
|
||||
inline void RtmpPusher::send_metaData(){
|
||||
auto src = m_pMediaSrc.lock();
|
||||
if (!src) {
|
||||
throw std::runtime_error("媒体源已被释放");
|
||||
}
|
||||
if (!src->ready()) {
|
||||
throw std::runtime_error("媒体源尚未准备就绪");
|
||||
}
|
||||
|
||||
AMFEncoder enc;
|
||||
enc << "@setDataFrame" << "onMetaData" << src->getMetaData();
|
||||
sendRequest(MSG_DATA, enc.data());
|
||||
|
||||
src->getConfigFrame([&](const RtmpPacket &pkt){
|
||||
sendRtmp(pkt.typeId, m_ui32StreamId, pkt.strBuf, pkt.timeStamp, pkt.chunkId);
|
||||
});
|
||||
|
||||
m_pRtmpReader = src->getRing()->attach();
|
||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
m_pRtmpReader->setReadCB([weakSelf](const RtmpPacket &pkt){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->sendRtmp(pkt.typeId, strongSelf->m_ui32StreamId, pkt.strBuf, pkt.timeStamp, pkt.chunkId);
|
||||
});
|
||||
m_pRtmpReader->setDetachCB([weakSelf](){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(strongSelf){
|
||||
strongSelf->_onShutdown(SockException(Err_other,"媒体源被释放"));
|
||||
strongSelf->teardown();
|
||||
}
|
||||
});
|
||||
_onPlayResult(SockException(Err_success,"success"));
|
||||
}
|
||||
void RtmpPusher::onCmd_result(AMFDecoder &dec){
|
||||
auto iReqId = dec.load<int>();
|
||||
auto it = m_mapOnResultCB.find(iReqId);
|
||||
if(it != m_mapOnResultCB.end()){
|
||||
it->second(dec);
|
||||
m_mapOnResultCB.erase(it);
|
||||
}else{
|
||||
WarnL << "unhandled _result";
|
||||
}
|
||||
}
|
||||
void RtmpPusher::onCmd_onStatus(AMFDecoder &dec) {
|
||||
AMFValue val;
|
||||
while(true){
|
||||
val = dec.load<AMFValue>();
|
||||
if(val.type() == AMF_OBJECT){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(val.type() != AMF_OBJECT){
|
||||
throw std::runtime_error("onStatus: 未找到结果对象");
|
||||
}
|
||||
|
||||
lock_guard<recursive_mutex> lck(m_mtxDeque);
|
||||
if(m_dqOnStatusCB.size()){
|
||||
m_dqOnStatusCB.front()(val);
|
||||
m_dqOnStatusCB.pop_front();
|
||||
}else{
|
||||
auto level = val["level"];
|
||||
auto code = val["code"].as_string();
|
||||
if(level.type() == AMF_STRING){
|
||||
if(level.as_string() != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::onRtmpChunk(RtmpPacket &chunkData) {
|
||||
switch (chunkData.typeId) {
|
||||
case MSG_CMD:
|
||||
case MSG_CMD3: {
|
||||
AMFDecoder dec(chunkData.strBuf, 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
auto it = g_mapCmd.find(type);
|
||||
if(it != g_mapCmd.end()){
|
||||
auto fun = it->second;
|
||||
(this->*fun)(dec);
|
||||
}else{
|
||||
WarnL << "can not support cmd:" << type;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
95
src/Rtmp/RtmpPusher.h
Normal file
95
src/Rtmp/RtmpPusher.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* RtmpPusher.h
|
||||
*
|
||||
* Created on: 2017年2月13日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPPUSHER_H_
|
||||
#define SRC_RTMP_RTMPPUSHER_H_
|
||||
|
||||
#include "RtmpProtocol.h"
|
||||
#include "Network/TcpClient.h"
|
||||
#include "RtmpMediaSource.h"
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
class RtmpPusher: public RtmpProtocol , public TcpClient{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPusher> Ptr;
|
||||
RtmpPusher(const char *strApp,const char *strStream);
|
||||
virtual ~RtmpPusher();
|
||||
|
||||
void publish(const char* strUrl);
|
||||
void teardown();
|
||||
|
||||
protected:
|
||||
|
||||
//for Tcpclient
|
||||
void onRecv(const Socket::Buffer::Ptr &pBuf) override;
|
||||
void onConnect(const SockException &err) override;
|
||||
void onErr(const SockException &ex) override;
|
||||
|
||||
//fro RtmpProtocol
|
||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||
void onSendRawData(const char *pcRawData, int iSize) override {
|
||||
send(pcRawData, iSize);
|
||||
}
|
||||
|
||||
virtual void onShutdown(const SockException &ex){}
|
||||
virtual void onPlayResult(const SockException &ex) {}
|
||||
private:
|
||||
void _onShutdown(const SockException &ex) {
|
||||
WarnL << ex.getErrCode() << " " << ex.what();
|
||||
m_pPlayTimer.reset();
|
||||
onShutdown(ex);
|
||||
}
|
||||
void _onPlayResult(const SockException &ex) {
|
||||
WarnL << ex.getErrCode() << " " << ex.what();
|
||||
m_pPlayTimer.reset();
|
||||
onPlayResult(ex);
|
||||
}
|
||||
|
||||
template<typename FUN>
|
||||
inline void addOnResultCB(const FUN &fun) {
|
||||
m_mapOnResultCB.emplace(m_iReqID, fun);
|
||||
}
|
||||
template<typename FUN>
|
||||
inline void addOnStatusCB(const FUN &fun) {
|
||||
lock_guard<recursive_mutex> lck(m_mtxDeque);
|
||||
m_dqOnStatusCB.emplace_back(fun);
|
||||
}
|
||||
|
||||
void onCmd_result(AMFDecoder &dec);
|
||||
void onCmd_onStatus(AMFDecoder &dec);
|
||||
void onCmd_onMetaData(AMFDecoder &dec);
|
||||
|
||||
inline void send_connect();
|
||||
inline void send_createStream();
|
||||
inline void send_publish();
|
||||
inline void send_metaData();
|
||||
|
||||
string m_strApp;
|
||||
string m_strStream;
|
||||
string m_strTcUrl;
|
||||
|
||||
unordered_map<int, function<void(AMFDecoder &dec)> > m_mapOnResultCB;
|
||||
deque<function<void(AMFValue &dec)> > m_dqOnStatusCB;
|
||||
recursive_mutex m_mtxDeque;
|
||||
|
||||
typedef void (RtmpPusher::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||
|
||||
//超时功能实现
|
||||
std::shared_ptr<Timer> m_pPlayTimer;
|
||||
|
||||
//源
|
||||
std::weak_ptr<RtmpMediaSource> m_pMediaSrc;
|
||||
RtmpMediaSource::RingType::RingReader::Ptr m_pRtmpReader;
|
||||
};
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPPUSHER_H_ */
|
||||
316
src/Rtmp/RtmpSession.cpp
Normal file
316
src/Rtmp/RtmpSession.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* RtmpSession.cpp
|
||||
*
|
||||
* Created on: 2017年2月10日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
|
||||
#include "RtmpSession.h"
|
||||
#include "Util/onceToken.h"
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
unordered_map<string, RtmpSession::rtmpCMDHandle> RtmpSession::g_mapCmd;
|
||||
RtmpSession::RtmpSession(const std::shared_ptr<ThreadPool> &pTh, const Socket::Ptr &pSock) :
|
||||
TcpLimitedSession(pTh, pSock) {
|
||||
static onceToken token([]() {
|
||||
g_mapCmd.emplace("connect",&RtmpSession::onCmd_connect);
|
||||
g_mapCmd.emplace("createStream",&RtmpSession::onCmd_createStream);
|
||||
g_mapCmd.emplace("publish",&RtmpSession::onCmd_publish);
|
||||
g_mapCmd.emplace("deleteStream",&RtmpSession::onCmd_deleteStream);
|
||||
g_mapCmd.emplace("play",&RtmpSession::onCmd_play);
|
||||
g_mapCmd.emplace("seek",&RtmpSession::onCmd_seek);
|
||||
g_mapCmd.emplace("pause",&RtmpSession::onCmd_pause);}, []() {});
|
||||
DebugL << getPeerIp();
|
||||
}
|
||||
|
||||
RtmpSession::~RtmpSession() {
|
||||
DebugL << getPeerIp();
|
||||
}
|
||||
|
||||
void RtmpSession::onError(const SockException& err) {
|
||||
if (m_pPublisherSrc) {
|
||||
m_pPublisherSrc.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpSession::onManager() {
|
||||
if (m_ticker.createdTime() > 10 * 1000) {
|
||||
if (!m_pRingReader && !m_pPublisherSrc) {
|
||||
WarnL << "非法链接:" << getPeerIp();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
if (m_pPublisherSrc) {
|
||||
//publisher
|
||||
if (m_ticker.elapsedTime() > 10 * 1000) {
|
||||
WarnL << "数据接收超时:" << getPeerIp();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpSession::onRecv(const Socket::Buffer::Ptr &pBuf) {
|
||||
m_ticker.resetTime();
|
||||
try {
|
||||
onParseRtmp(pBuf->data(), pBuf->size());
|
||||
} catch (exception &e) {
|
||||
WarnL << e.what();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_connect(AMFDecoder &dec) {
|
||||
auto params = dec.load<AMFValue>();
|
||||
m_strApp = params["app"].as_string();
|
||||
bool ok = true; //(app == APP_NAME);
|
||||
AMFValue version(AMF_OBJECT);
|
||||
version.set("fmsVer", "FMS/3,5,3,888");
|
||||
version.set("capabilities", 127.0);
|
||||
version.set("mode", 1.0);
|
||||
AMFValue status(AMF_OBJECT);
|
||||
status.set("level", ok ? "status" : "error");
|
||||
status.set("code", ok ? "NetConnection.Connect.Success" : "NetConnection.Connect.InvalidApp");
|
||||
status.set("description", ok ? "Connection succeeded." : "InvalidApp.");
|
||||
status.set("objectEncoding", (double) (dec.getVersion()));
|
||||
sendReply(ok ? "_result" : "_error", version, status);
|
||||
if (!ok) {
|
||||
throw std::runtime_error("Unsupported application: " + m_strApp);
|
||||
}
|
||||
|
||||
////////////window Acknowledgement size/////
|
||||
sendAcknowledgementSize(2500000);
|
||||
///////////set peerBandwidth////////////////
|
||||
sendPeerBandwidth(2500000);
|
||||
///////////set chunk size////////////////
|
||||
#ifndef _DEBUG
|
||||
sendChunkSize(60000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_createStream(AMFDecoder &dec) {
|
||||
sendReply("_result", nullptr, double(STREAM_MEDIA));
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_publish(AMFDecoder &dec) {
|
||||
dec.load<AMFValue>();/* NULL */
|
||||
m_strId = dec.load<std::string>();
|
||||
auto iPos = m_strId.find('?');
|
||||
if (iPos != string::npos) {
|
||||
m_strId.erase(iPos);
|
||||
}
|
||||
auto src = RtmpMediaSource::find(m_strApp,m_strId,false);
|
||||
bool ok = (!src && !m_pPublisherSrc);
|
||||
AMFValue status(AMF_OBJECT);
|
||||
status.set("level", ok ? "status" : "error");
|
||||
status.set("code", ok ? "NetStream.Publish.Start" : "NetStream.Publish.BadName");
|
||||
status.set("description", ok ? "Started publishing stream." : "Already publishing.");
|
||||
status.set("clientid", "ASAICiss");
|
||||
sendReply("onStatus", nullptr, status);
|
||||
if (!ok) {
|
||||
throw std::runtime_error( StrPrinter << "Already publishing:" << m_strApp << "/" << m_strId << endl);
|
||||
}
|
||||
m_pPublisherSrc.reset(new RtmpToRtspMediaSource(m_strApp,m_strId));
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_deleteStream(AMFDecoder &dec) {
|
||||
AMFValue status(AMF_OBJECT);
|
||||
status.set("level", "status");
|
||||
status.set("code", "NetStream.Unpublish.Success");
|
||||
status.set("description", "Stop publishing.");
|
||||
sendReply("onStatus", nullptr, status);
|
||||
throw std::runtime_error(StrPrinter << "Stop publishing." << endl);
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_play(AMFDecoder &dec) {
|
||||
dec.load<AMFValue>();/* NULL */
|
||||
m_strId = dec.load<std::string>();
|
||||
auto iPos = m_strId.find('?');
|
||||
if (iPos != string::npos) {
|
||||
m_strId.erase(iPos);
|
||||
}
|
||||
auto src = RtmpMediaSource::find(m_strApp,m_strId,true);
|
||||
bool ok = (src.operator bool());
|
||||
ok = ok && src->ready();
|
||||
// onStatus(NetStream.Play.Reset)
|
||||
AMFValue status(AMF_OBJECT);
|
||||
|
||||
status.set("level", ok ? "status" : "error");
|
||||
status.set("code", ok ? "NetStream.Play.Reset" : "NetStream.Play.StreamNotFound");
|
||||
status.set("description", ok ? "Resetting and playing stream." : "No such stream.");
|
||||
status.set("details", "stream");
|
||||
status.set("clientid", "ASAICiss");
|
||||
sendReply("onStatus", nullptr, status);
|
||||
if (!ok) {
|
||||
throw std::runtime_error( StrPrinter << "No such stream:" << m_strApp << " " << m_strId << endl);
|
||||
}
|
||||
//stream begin
|
||||
sendUserControl(CONTROL_STREAM_BEGIN, STREAM_MEDIA);
|
||||
// onStatus(NetStream.Play.Start)
|
||||
status.clear();
|
||||
status.set("level", "status");
|
||||
status.set("code", "NetStream.Play.Start");
|
||||
status.set("description", "Started playing stream.");
|
||||
status.set("details", "stream");
|
||||
status.set("clientid", "ASAICiss");
|
||||
sendReply("onStatus", AMFValue(), status);
|
||||
|
||||
// |RtmpSampleAccess(true, true)
|
||||
AMFEncoder invoke;
|
||||
invoke << "|RtmpSampleAccess" << true << true;
|
||||
sendResponse(MSG_DATA, invoke.data());
|
||||
|
||||
// onMetaData
|
||||
invoke.clear();
|
||||
invoke << "onMetaData" << src->getMetaData();
|
||||
sendResponse(MSG_DATA, invoke.data());
|
||||
|
||||
src->getConfigFrame([&](const RtmpPacket &pkt) {
|
||||
//DebugL<<"send initial frame";
|
||||
onSendMedia(pkt);
|
||||
});
|
||||
|
||||
m_pRingReader = src->getRing()->attach();
|
||||
weak_ptr<RtmpSession> weakSelf = dynamic_pointer_cast<RtmpSession>(shared_from_this());
|
||||
m_pRingReader->setReadCB([weakSelf](const RtmpPacket& pkt){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->async([weakSelf,pkt]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->onSendMedia(pkt);
|
||||
});
|
||||
});
|
||||
m_pRingReader->setDetachCB([weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->safeShutdown();
|
||||
});
|
||||
m_pPlayerSrc = src;
|
||||
if(src->getRing()->readerCount() == 1){
|
||||
src->seekTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_pause(AMFDecoder &dec) {
|
||||
dec.load<AMFValue>();/* NULL */
|
||||
bool paused = dec.load<bool>();
|
||||
TraceL << paused;
|
||||
AMFValue status(AMF_OBJECT);
|
||||
status.set("level", "status");
|
||||
status.set("code", paused ? "NetStream.Pause.Notify" : "NetStream.Unpause.Notify");
|
||||
status.set("description", paused ? "Paused stream." : "Unpaused stream.");
|
||||
sendReply("onStatus", nullptr, status);
|
||||
//streamBegin
|
||||
sendUserControl(paused ? CONTROL_STREAM_EOF : CONTROL_STREAM_BEGIN,
|
||||
STREAM_MEDIA);
|
||||
if (!m_pRingReader) {
|
||||
throw std::runtime_error("Rtmp not started yet!");
|
||||
}
|
||||
if (paused) {
|
||||
m_pRingReader->setReadCB(nullptr);
|
||||
} else {
|
||||
weak_ptr<RtmpSession> weakSelf = dynamic_pointer_cast<RtmpSession>(shared_from_this());
|
||||
m_pRingReader->setReadCB([weakSelf](const RtmpPacket& pkt) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->async([weakSelf,pkt]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->onSendMedia(pkt);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpSession::setMetaData(AMFDecoder &dec) {
|
||||
if (!m_pPublisherSrc) {
|
||||
throw std::runtime_error("not a publisher");
|
||||
}
|
||||
std::string type = dec.load<std::string>();
|
||||
if (type != "onMetaData") {
|
||||
throw std::runtime_error("can only set metadata");
|
||||
}
|
||||
m_pPublisherSrc->onGetMetaData(dec.load<AMFValue>());
|
||||
m_pPublisherSrc->regist();
|
||||
}
|
||||
|
||||
void RtmpSession::onProcessCmd(AMFDecoder &dec) {
|
||||
std::string method = dec.load<std::string>();
|
||||
auto it = g_mapCmd.find(method);
|
||||
if (it == g_mapCmd.end()) {
|
||||
TraceL << "can not support cmd:" << method;
|
||||
return;
|
||||
}
|
||||
m_dNowReqID = dec.load<double>();
|
||||
auto fun = it->second;
|
||||
(this->*fun)(dec);
|
||||
}
|
||||
|
||||
void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) {
|
||||
switch (chunkData.typeId) {
|
||||
case MSG_CMD:
|
||||
case MSG_CMD3: {
|
||||
AMFDecoder dec(chunkData.strBuf, 0);
|
||||
onProcessCmd(dec);
|
||||
}
|
||||
break;
|
||||
|
||||
case MSG_DATA:
|
||||
case MSG_DATA3: {
|
||||
AMFDecoder dec(chunkData.strBuf, 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
TraceL << "notify:" << type;
|
||||
if (type == "@setDataFrame") {
|
||||
setMetaData(dec);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MSG_AUDIO:
|
||||
case MSG_VIDEO: {
|
||||
if (!m_pPublisherSrc) {
|
||||
throw std::runtime_error("Not a rtmp publisher!");
|
||||
}
|
||||
m_pPublisherSrc->onGetMedia(chunkData);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpSession::onCmd_seek(AMFDecoder &dec) {
|
||||
dec.load<AMFValue>();/* NULL */
|
||||
auto milliSeconds = dec.load<AMFValue>().as_number();
|
||||
InfoL << "rtmp seekTo:" << milliSeconds/1000.0;
|
||||
auto stongSrc = m_pPlayerSrc.lock();
|
||||
if (stongSrc) {
|
||||
stongSrc->seekTo(milliSeconds);
|
||||
}
|
||||
AMFValue status(AMF_OBJECT);
|
||||
AMFEncoder invoke;
|
||||
status.set("level", "status");
|
||||
status.set("code", "NetStream.Seek.Notify");
|
||||
status.set("description", "Seeking.");
|
||||
sendReply("onStatus", nullptr, status);
|
||||
}
|
||||
|
||||
void RtmpSession::onSendMedia(const RtmpPacket& pkt) {
|
||||
sendRtmp(pkt.typeId, pkt.streamId, pkt.strBuf, pkt.timeStamp, pkt.chunkId);
|
||||
}
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
79
src/Rtmp/RtmpSession.h
Normal file
79
src/Rtmp/RtmpSession.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* RtmpSession.h
|
||||
*
|
||||
* Created on: 2017年2月10日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPSESSION_H_
|
||||
#define SRC_RTMP_RTMPSESSION_H_
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "amf.h"
|
||||
#include "Rtmp.h"
|
||||
#include "utils.h"
|
||||
#include "config.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "RtmpProtocol.h"
|
||||
#include "RtmpToRtspMediaSource.h"
|
||||
#include "Network/TcpLimitedSession.h"
|
||||
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Network;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
class RtmpSession: public TcpLimitedSession<MAX_TCP_SESSION> ,public RtmpProtocol{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpSession> Ptr;
|
||||
RtmpSession(const std::shared_ptr<ThreadPool> &_th, const Socket::Ptr &_sock);
|
||||
virtual ~RtmpSession();
|
||||
void onRecv(const Socket::Buffer::Ptr &pBuf) override;
|
||||
void onError(const SockException &err) override;
|
||||
void onManager() override;
|
||||
private:
|
||||
std::string m_strApp;
|
||||
std::string m_strId;
|
||||
double m_dNowReqID = 0;
|
||||
Ticker m_ticker;//数据接收时间
|
||||
typedef void (RtmpSession::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||
|
||||
RingBuffer<RtmpPacket>::RingReader::Ptr m_pRingReader;
|
||||
std::shared_ptr<RtmpMediaSource> m_pPublisherSrc;
|
||||
std::weak_ptr<RtmpMediaSource> m_pPlayerSrc;
|
||||
|
||||
void onProcessCmd(AMFDecoder &dec);
|
||||
void onCmd_connect(AMFDecoder &dec);
|
||||
void onCmd_createStream(AMFDecoder &dec);
|
||||
|
||||
void onCmd_publish(AMFDecoder &dec);
|
||||
void onCmd_deleteStream(AMFDecoder &dec);
|
||||
|
||||
void onCmd_play(AMFDecoder &dec);
|
||||
void onCmd_seek(AMFDecoder &dec);
|
||||
void onCmd_pause(AMFDecoder &dec);
|
||||
void setMetaData(AMFDecoder &dec);
|
||||
|
||||
void onSendMedia(const RtmpPacket &pkt);
|
||||
void onSendRawData(const char *pcRawData,int iSize) override{
|
||||
send(pcRawData, iSize);
|
||||
}
|
||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||
|
||||
template<typename first, typename second>
|
||||
inline void sendReply(const char *str, const first &reply, const second &status) {
|
||||
AMFEncoder invoke;
|
||||
invoke << str << m_dNowReqID << reply << status;
|
||||
sendResponse(MSG_CMD, invoke.data());
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPSESSION_H_ */
|
||||
150
src/Rtmp/RtmpToRtspMediaSource.cpp
Normal file
150
src/Rtmp/RtmpToRtspMediaSource.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* RtmpToRtspMediaSource.cpp
|
||||
*
|
||||
* Created on: 2016年10月20日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#include "Network/sockutil.h"
|
||||
#include "RtmpToRtspMediaSource.h"
|
||||
#include "Util/util.h"
|
||||
#include "Device/base64.h"
|
||||
#include "Network/sockutil.h"
|
||||
#include "config.h"
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Network;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
#ifdef ENABLE_RTMP2RTSP
|
||||
RtmpToRtspMediaSource::RtmpToRtspMediaSource(const string &_app, const string &_id) :
|
||||
RtmpMediaSource(_app,_id) {
|
||||
}
|
||||
RtmpToRtspMediaSource::~RtmpToRtspMediaSource() {
|
||||
}
|
||||
|
||||
void RtmpToRtspMediaSource::regist() {
|
||||
RtmpMediaSource::regist();
|
||||
if (m_pRtspSrc) {
|
||||
m_pRtspSrc->regist();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpToRtspMediaSource::unregist() {
|
||||
RtmpMediaSource::unregist();
|
||||
if(m_pRtspSrc){
|
||||
m_pRtspSrc->unregist();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpToRtspMediaSource::onGetH264(const H264Frame &frame) {
|
||||
m_pRecorder->inputH264((char *) frame.data.data(), frame.data.size(), frame.timeStamp, frame.type);
|
||||
|
||||
if(m_pRtpMaker_h264){
|
||||
m_pRtpMaker_h264->makeRtp(frame.data.data() + 4, frame.data.size() - 4, frame.timeStamp);
|
||||
}
|
||||
}
|
||||
inline void RtmpToRtspMediaSource::onGetAdts(const AdtsFrame &frame) {
|
||||
m_pRecorder->inputAAC((char *) frame.data, frame.aac_frame_length, frame.timeStamp);
|
||||
|
||||
if (m_pRtpMaker_aac) {
|
||||
m_pRtpMaker_aac->makeRtp((char *) frame.data + 7, frame.aac_frame_length - 7, frame.timeStamp);
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpToRtspMediaSource::makeSDP() {
|
||||
string strSDP;
|
||||
strSDP = "v=0\r\n";
|
||||
strSDP += "o=- 1383190487994921 1 IN IP4 0.0.0.0\r\n";
|
||||
strSDP += "s=RTSP Session, streamed by the ZL\r\n";
|
||||
strSDP += "i=ZL Live Stream\r\n";
|
||||
strSDP += "c=IN IP4 0.0.0.0\r\n";
|
||||
strSDP += "t=0 0\r\n";
|
||||
if(m_pParser->getDuration() <= 0){
|
||||
strSDP += "a=range:npt=0-\r\n";
|
||||
}else{
|
||||
strSDP += StrPrinter << "0-"<< m_pParser->getDuration()<< "\r\n" << endl;
|
||||
}
|
||||
strSDP += "a=control:*\r\n";
|
||||
if (m_pParser->containVideo()) {
|
||||
uint32_t ssrc0;
|
||||
memcpy(&ssrc0, makeRandStr(4, false).data(), 4);
|
||||
auto lam = [this](const RtpPacket::Ptr &pkt, bool bKeyPos) {
|
||||
m_pRtspSrc->onGetRTP(pkt,bKeyPos);
|
||||
};
|
||||
static uint32_t videoMtu = mINI::Instance()[Config::Rtp::kVideoMtuSize].as<uint32_t>();
|
||||
m_pRtpMaker_h264.reset(new RtpMaker_H264(lam, ssrc0,videoMtu));
|
||||
|
||||
char strTemp[100];
|
||||
int profile_level_id = 0;
|
||||
string strSPS =m_pParser->getSps().substr(4);
|
||||
string strPPS =m_pParser->getPps().substr(4);
|
||||
if (strSPS.length() >= 4) { // sanity check
|
||||
profile_level_id = (strSPS[1] << 16) | (strSPS[2] << 8) | strSPS[3]; // profile_idc|constraint_setN_flag|level_idc
|
||||
}
|
||||
|
||||
//视频通道
|
||||
strSDP += StrPrinter << "m=video 0 RTP/AVP " << m_pRtpMaker_h264->getPlayloadType()
|
||||
<< "\r\n" << endl;
|
||||
strSDP += "b=AS:5100\r\n";
|
||||
strSDP += StrPrinter << "a=rtpmap:" << m_pRtpMaker_h264->getPlayloadType()
|
||||
<< " H264/" << m_pRtpMaker_h264->getSampleRate() << "\r\n" << endl;
|
||||
strSDP += StrPrinter << "a=fmtp:" << m_pRtpMaker_h264->getPlayloadType()
|
||||
<< " packetization-mode=1;profile-level-id=" << endl;
|
||||
|
||||
memset(strTemp, 0, 100);
|
||||
sprintf(strTemp, "%06X", profile_level_id);
|
||||
strSDP += strTemp;
|
||||
strSDP += ";sprop-parameter-sets=";
|
||||
memset(strTemp, 0, 100);
|
||||
av_base64_encode(strTemp, 100, (uint8_t *) strSPS.data(), strSPS.size());
|
||||
strSDP += strTemp;
|
||||
strSDP += ",";
|
||||
memset(strTemp, 0, 100);
|
||||
av_base64_encode(strTemp, 100, (uint8_t *) strPPS.data(), strPPS.size());
|
||||
strSDP += strTemp;
|
||||
strSDP += "\r\n";
|
||||
strSDP += StrPrinter << "a=control:trackID=" << m_pRtpMaker_h264->getInterleaved() / 2
|
||||
<< "\r\n" << endl;
|
||||
}
|
||||
|
||||
if (m_pParser->containAudio()) {
|
||||
uint32_t ssrc1;
|
||||
memcpy(&ssrc1, makeRandStr(8, false).data() + 4, 4);
|
||||
auto lam = [this](const RtpPacket::Ptr &pkt, bool bKeyPos) {
|
||||
m_pRtspSrc->onGetRTP(pkt,bKeyPos);
|
||||
};
|
||||
static uint32_t audioMtu = mINI::Instance()[Config::Rtp::kAudioMtuSize].as<uint32_t>();
|
||||
m_pRtpMaker_aac.reset(new RtpMaker_AAC(lam, ssrc1, audioMtu,m_pParser->getAudioSampleRate()));
|
||||
|
||||
char configStr[32];
|
||||
const string & strAacCfg = m_pParser->getAudioCfg();
|
||||
snprintf(configStr, sizeof(configStr), "%02X%02x", strAacCfg[0], strAacCfg[1]);
|
||||
strSDP += StrPrinter << "m=audio 0 RTP/AVP " << m_pRtpMaker_aac->getPlayloadType()
|
||||
<< "\r\n" << endl;
|
||||
strSDP += "b=AS:96\r\n";
|
||||
strSDP += StrPrinter << "a=rtpmap:" << m_pRtpMaker_aac->getPlayloadType()
|
||||
<< " MPEG4-GENERIC/" << m_pRtpMaker_aac->getSampleRate() << "\r\n"
|
||||
<< endl;
|
||||
strSDP += StrPrinter << "a=fmtp:" << m_pRtpMaker_aac->getPlayloadType()
|
||||
<< " streamtype=5;profile-level-id=1;mode=AAC-hbr;"
|
||||
<< "sizelength=13;indexlength=3;indexdeltalength=3;config="
|
||||
<< endl;
|
||||
strSDP.append(configStr, 4);
|
||||
strSDP += "\r\n";
|
||||
strSDP += StrPrinter << "a=control:trackID=" << m_pRtpMaker_aac->getInterleaved() / 2
|
||||
<< "\r\n" << endl;
|
||||
}
|
||||
|
||||
m_pRtspSrc.reset(new RtspMediaSource(getApp(),getId()));
|
||||
m_pRtspSrc->setOnSeek(m_onSeek);
|
||||
m_pRtspSrc->setOnStamp(m_onStamp);
|
||||
m_pRtspSrc->onGetSDP(strSDP);
|
||||
m_pRtspSrc->regist();
|
||||
}
|
||||
|
||||
#endif // ENABLE_RTMP2RTSP
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
96
src/Rtmp/RtmpToRtspMediaSource.h
Normal file
96
src/Rtmp/RtmpToRtspMediaSource.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* RtmpToRtspMediaSource.h
|
||||
*
|
||||
* Created on: 2016年10月20日
|
||||
* Author: xzl
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RTMPTORTSPMEDIASOURCE_H_
|
||||
#define SRC_RTMP_RTMPTORTSPMEDIASOURCE_H_
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include "Util/logger.h"
|
||||
#include "amf.h"
|
||||
#include "Rtmp.h"
|
||||
#include "Util/util.h"
|
||||
#include <mutex>
|
||||
#include "RtmpMediaSource.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
#include "RTP/RtpMakerH264.h"
|
||||
#include "RTP/RtpMakerAAC.h"
|
||||
#include "MedaiFile/MediaRecorder.h"
|
||||
#include "Rtsp/RtpParser.h"
|
||||
#include "RtmpParser.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ZL::Util;
|
||||
using namespace ZL::Rtsp;
|
||||
using namespace ZL::MediaFile;
|
||||
|
||||
namespace ZL {
|
||||
namespace Rtmp {
|
||||
|
||||
#ifdef ENABLE_RTMP2RTSP
|
||||
class RtmpToRtspMediaSource: public RtmpMediaSource {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpToRtspMediaSource> Ptr;
|
||||
RtmpToRtspMediaSource(const string &_app, const string &_id);
|
||||
virtual ~RtmpToRtspMediaSource();
|
||||
virtual void regist() override;
|
||||
virtual void unregist() override;
|
||||
|
||||
virtual void onGetMetaData(const AMFValue &_metadata) override {
|
||||
try {
|
||||
m_pParser.reset(new RtmpParser(_metadata));
|
||||
m_pRecorder.reset(new MediaRecorder(getApp(),getId(),m_pParser));
|
||||
m_pParser->setOnAudioCB(std::bind(&RtmpToRtspMediaSource::onGetAdts, this, placeholders::_1));
|
||||
m_pParser->setOnVideoCB(std::bind(&RtmpToRtspMediaSource::onGetH264, this, placeholders::_1));
|
||||
} catch (exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
RtmpMediaSource::onGetMetaData(_metadata);
|
||||
}
|
||||
|
||||
virtual void onGetMedia(const RtmpPacket &pkt) override {
|
||||
if (m_pParser) {
|
||||
if (!m_pRtspSrc && m_pParser->isInited()) {
|
||||
makeSDP();
|
||||
}
|
||||
m_pParser->inputRtmp(pkt);
|
||||
}
|
||||
RtmpMediaSource::onGetMedia(pkt);
|
||||
}
|
||||
void setOnSeek(const function<bool(uint32_t)> &cb) override {
|
||||
RtmpMediaSource::setOnSeek(cb);
|
||||
if (m_pRtspSrc) {
|
||||
m_pRtspSrc->setOnSeek(cb);
|
||||
}
|
||||
}
|
||||
void setOnStamp(const function<uint32_t()> &cb) override{
|
||||
RtmpMediaSource::setOnStamp(cb);
|
||||
if (m_pRtspSrc) {
|
||||
m_pRtspSrc->setOnStamp(cb);
|
||||
}
|
||||
}
|
||||
private:
|
||||
RtmpParser::Ptr m_pParser;
|
||||
RtspMediaSource::Ptr m_pRtspSrc;
|
||||
RtpMaker_AAC::Ptr m_pRtpMaker_aac;
|
||||
RtpMaker_H264::Ptr m_pRtpMaker_h264;
|
||||
MediaRecorder::Ptr m_pRecorder;
|
||||
|
||||
void onGetH264(const H264Frame &frame);
|
||||
void onGetAdts(const AdtsFrame &frame);
|
||||
void makeSDP();
|
||||
};
|
||||
#else
|
||||
typedef RtmpMediaSource RtmpToRtspMediaSource;
|
||||
#endif //ENABLE_RTMP2RTSP
|
||||
|
||||
} /* namespace Rtmp */
|
||||
} /* namespace ZL */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPTORTSPMEDIASOURCE_H_ */
|
||||
528
src/Rtmp/amf.cpp
Normal file
528
src/Rtmp/amf.cpp
Normal file
@@ -0,0 +1,528 @@
|
||||
#include "amf.h"
|
||||
#include "utils.h"
|
||||
#include <stdexcept>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "Util/logger.h"
|
||||
#include "Util/util.h"
|
||||
using namespace ZL::Util;
|
||||
|
||||
/////////////////////AMFValue/////////////////////////////
|
||||
inline void AMFValue::destroy() {
|
||||
switch (m_type) {
|
||||
case AMF_STRING:
|
||||
if (m_value.string) {
|
||||
delete m_value.string;
|
||||
m_value.string = nullptr;
|
||||
}
|
||||
break;
|
||||
case AMF_OBJECT:
|
||||
case AMF_ECMA_ARRAY:
|
||||
if (m_value.object) {
|
||||
delete m_value.object;
|
||||
m_value.object = nullptr;
|
||||
}
|
||||
break;
|
||||
case AMF_STRICT_ARRAY:
|
||||
if (m_value.array) {
|
||||
delete m_value.array;
|
||||
m_value.array = nullptr;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
inline void AMFValue::init() {
|
||||
switch (m_type) {
|
||||
case AMF_OBJECT:
|
||||
case AMF_ECMA_ARRAY:
|
||||
m_value.object = new mapType;
|
||||
break;
|
||||
case AMF_STRING:
|
||||
m_value.string = new std::string;
|
||||
break;
|
||||
case AMF_STRICT_ARRAY:
|
||||
m_value.array = new arrayType;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
AMFValue::AMFValue(AMFType type) :
|
||||
m_type(type) {
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
AMFValue::~AMFValue() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
AMFValue::AMFValue(const char *s) :
|
||||
m_type(AMF_STRING) {
|
||||
init();
|
||||
*m_value.string = s;
|
||||
}
|
||||
|
||||
|
||||
AMFValue::AMFValue(const std::string &s) :
|
||||
m_type(AMF_STRING) {
|
||||
init();
|
||||
*m_value.string = s;
|
||||
}
|
||||
|
||||
AMFValue::AMFValue(double n) :
|
||||
m_type(AMF_NUMBER) {
|
||||
init();
|
||||
m_value.number = n;
|
||||
}
|
||||
|
||||
AMFValue::AMFValue(int i) :
|
||||
m_type(AMF_INTEGER) {
|
||||
init();
|
||||
m_value.integer = i;
|
||||
}
|
||||
|
||||
AMFValue::AMFValue(bool b) :
|
||||
m_type(AMF_BOOLEAN) {
|
||||
init();
|
||||
m_value.boolean = b;
|
||||
}
|
||||
|
||||
AMFValue::AMFValue(const AMFValue &from) :
|
||||
m_type(AMF_NULL) {
|
||||
*this = from;
|
||||
}
|
||||
|
||||
AMFValue::AMFValue(AMFValue &&from) {
|
||||
*this = from;
|
||||
}
|
||||
|
||||
AMFValue& AMFValue::operator =(const AMFValue &from) {
|
||||
return *this = const_cast<AMFValue &&>(from);
|
||||
|
||||
}
|
||||
AMFValue& AMFValue::operator =(AMFValue &&from) {
|
||||
destroy();
|
||||
m_type = from.m_type;
|
||||
init();
|
||||
switch (m_type) {
|
||||
case AMF_STRING:
|
||||
*m_value.string = (*from.m_value.string);
|
||||
break;
|
||||
case AMF_OBJECT:
|
||||
case AMF_ECMA_ARRAY:
|
||||
*m_value.object = (*from.m_value.object);
|
||||
break;
|
||||
case AMF_STRICT_ARRAY:
|
||||
*m_value.array = (*from.m_value.array);
|
||||
break;
|
||||
case AMF_NUMBER:
|
||||
m_value.number = from.m_value.number;
|
||||
break;
|
||||
case AMF_INTEGER:
|
||||
m_value.integer = from.m_value.integer;
|
||||
break;
|
||||
case AMF_BOOLEAN:
|
||||
m_value.boolean = from.m_value.boolean;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum {
|
||||
AMF0_NUMBER,
|
||||
AMF0_BOOLEAN,
|
||||
AMF0_STRING,
|
||||
AMF0_OBJECT,
|
||||
AMF0_MOVIECLIP,
|
||||
AMF0_NULL,
|
||||
AMF0_UNDEFINED,
|
||||
AMF0_REFERENCE,
|
||||
AMF0_ECMA_ARRAY,
|
||||
AMF0_OBJECT_END,
|
||||
AMF0_STRICT_ARRAY,
|
||||
AMF0_DATE,
|
||||
AMF0_LONG_STRING,
|
||||
AMF0_UNSUPPORTED,
|
||||
AMF0_RECORD_SET,
|
||||
AMF0_XML_OBJECT,
|
||||
AMF0_TYPED_OBJECT,
|
||||
AMF0_SWITCH_AMF3,
|
||||
};
|
||||
|
||||
enum {
|
||||
AMF3_UNDEFINED,
|
||||
AMF3_NULL,
|
||||
AMF3_FALSE,
|
||||
AMF3_TRUE,
|
||||
AMF3_INTEGER,
|
||||
AMF3_NUMBER,
|
||||
AMF3_STRING,
|
||||
AMF3_LEGACY_XML,
|
||||
AMF3_DATE,
|
||||
AMF3_ARRAY,
|
||||
AMF3_OBJECT,
|
||||
AMF3_XML,
|
||||
AMF3_BYTE_ARRAY,
|
||||
};
|
||||
|
||||
////////////////////////////////Encoder//////////////////////////////////////////
|
||||
AMFEncoder & AMFEncoder::operator <<(const char *s) {
|
||||
if (s) {
|
||||
buf += char(AMF0_STRING);
|
||||
uint16_t str_len = htons(strlen(s));
|
||||
buf.append((char *) &str_len, 2);
|
||||
buf += s;
|
||||
} else {
|
||||
buf += char(AMF0_NULL);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
AMFEncoder & AMFEncoder::operator <<(const std::string &s) {
|
||||
if (!s.empty()) {
|
||||
buf += char(AMF0_STRING);
|
||||
uint16_t str_len = htons(s.size());
|
||||
buf.append((char *) &str_len, 2);
|
||||
buf += s;
|
||||
} else {
|
||||
buf += char(AMF0_NULL);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
AMFEncoder & AMFEncoder::operator <<(std::nullptr_t) {
|
||||
buf += char(AMF0_NULL);
|
||||
return *this;
|
||||
}
|
||||
AMFEncoder & AMFEncoder::write_undefined() {
|
||||
buf += char(AMF0_UNDEFINED);
|
||||
return *this;
|
||||
|
||||
}
|
||||
AMFEncoder & AMFEncoder::operator <<(const int n){
|
||||
return (*this) << (double)n;
|
||||
}
|
||||
AMFEncoder & AMFEncoder::operator <<(const double n) {
|
||||
buf += char(AMF0_NUMBER);
|
||||
uint64_t encoded = 0;
|
||||
memcpy(&encoded, &n, 8);
|
||||
uint32_t val = htonl(encoded >> 32);
|
||||
buf.append((char *) &val, 4);
|
||||
val = htonl(encoded);
|
||||
buf.append((char *) &val, 4);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AMFEncoder & AMFEncoder::operator <<(const bool b) {
|
||||
buf += char(AMF0_BOOLEAN);
|
||||
buf += char(b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AMFEncoder & AMFEncoder::operator <<(const AMFValue& value) {
|
||||
switch ((int) value.type()) {
|
||||
case AMF_STRING:
|
||||
*this << value.as_string();
|
||||
break;
|
||||
case AMF_NUMBER:
|
||||
*this << value.as_number();
|
||||
break;
|
||||
case AMF_INTEGER:
|
||||
*this << value.as_integer();
|
||||
break;
|
||||
case AMF_BOOLEAN:
|
||||
*this << value.as_boolean();
|
||||
break;
|
||||
case AMF_OBJECT: {
|
||||
buf += char(AMF0_OBJECT);
|
||||
for (auto &pr : value.getMap()) {
|
||||
write_key(pr.first);
|
||||
*this << pr.second;
|
||||
}
|
||||
write_key("");
|
||||
buf += char(AMF0_OBJECT_END);
|
||||
}
|
||||
break;
|
||||
case AMF_ECMA_ARRAY: {
|
||||
buf += char(AMF0_ECMA_ARRAY);
|
||||
uint32_t sz = htonl(value.getMap().size());
|
||||
buf.append((char *) &sz, 4);
|
||||
for (auto &pr : value.getMap()) {
|
||||
write_key(pr.first);
|
||||
*this << pr.second;
|
||||
}
|
||||
write_key("");
|
||||
buf += char(AMF0_OBJECT_END);
|
||||
}
|
||||
break;
|
||||
case AMF_NULL:
|
||||
*this << nullptr;
|
||||
break;
|
||||
case AMF_UNDEFINED:
|
||||
this->write_undefined();
|
||||
break;
|
||||
case AMF_STRICT_ARRAY: {
|
||||
buf += char(AMF0_STRICT_ARRAY);
|
||||
uint32_t sz = htonl(value.getArr().size());
|
||||
buf.append((char *) &sz, 4);
|
||||
for (auto &val : value.getArr()) {
|
||||
*this << val;
|
||||
}
|
||||
//write_key("");
|
||||
//buf += char(AMF0_OBJECT_END);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
void AMFEncoder::write_key(const std::string& s) {
|
||||
uint16_t str_len = htons(s.size());
|
||||
buf.append((char *) &str_len, 2);
|
||||
buf += s;
|
||||
}
|
||||
|
||||
//////////////////Decoder//////////////////
|
||||
|
||||
uint8_t AMFDecoder::front() {
|
||||
if (pos >= buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
return uint8_t(buf[pos]);
|
||||
}
|
||||
|
||||
uint8_t AMFDecoder::pop_front() {
|
||||
if (version == 0 && front() == AMF0_SWITCH_AMF3) {
|
||||
InfoL << "entering AMF3 mode";
|
||||
pos++;
|
||||
version = 3;
|
||||
}
|
||||
|
||||
if (pos >= buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
return uint8_t(buf[pos++]);
|
||||
}
|
||||
|
||||
template<>
|
||||
double AMFDecoder::load<double>() {
|
||||
if (pop_front() != AMF0_NUMBER) {
|
||||
throw std::runtime_error("Expected a number");
|
||||
}
|
||||
if (pos + 8 > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
uint64_t val = ((uint64_t) load_be32(&buf[pos]) << 32)
|
||||
| load_be32(&buf[pos + 4]);
|
||||
double n = 0;
|
||||
memcpy(&n, &val, 8);
|
||||
pos += 8;
|
||||
return n;
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
bool AMFDecoder::load<bool>() {
|
||||
if (pop_front() != AMF0_BOOLEAN) {
|
||||
throw std::runtime_error("Expected a boolean");
|
||||
}
|
||||
return pop_front() != 0;
|
||||
}
|
||||
template<>
|
||||
unsigned int AMFDecoder::load<unsigned int>() {
|
||||
unsigned int value = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uint8_t b = pop_front();
|
||||
if (i == 3) {
|
||||
/* use all bits from 4th byte */
|
||||
value = (value << 8) | b;
|
||||
break;
|
||||
}
|
||||
value = (value << 7) | (b & 0x7f);
|
||||
if ((b & 0x80) == 0)
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
template<>
|
||||
int AMFDecoder::load<int>() {
|
||||
if (version == 3) {
|
||||
return load<unsigned int>();
|
||||
} else {
|
||||
return load<double>();
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string AMFDecoder::load<std::string>() {
|
||||
size_t str_len = 0;
|
||||
uint8_t type = pop_front();
|
||||
if (version == 3) {
|
||||
if (type != AMF3_STRING) {
|
||||
throw std::runtime_error("Expected a string");
|
||||
}
|
||||
str_len = load<unsigned int>() / 2;
|
||||
|
||||
} else {
|
||||
if (type != AMF0_STRING) {
|
||||
throw std::runtime_error("Expected a string");
|
||||
}
|
||||
if (pos + 2 > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
str_len = load_be16(&buf[pos]);
|
||||
pos += 2;
|
||||
}
|
||||
if (pos + str_len > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
std::string s(buf, pos, str_len);
|
||||
pos += str_len;
|
||||
return s;
|
||||
}
|
||||
|
||||
template<>
|
||||
AMFValue AMFDecoder::load<AMFValue>() {
|
||||
uint8_t type = front();
|
||||
if (version == 3) {
|
||||
switch (type) {
|
||||
case AMF3_STRING:
|
||||
return load<std::string>();
|
||||
case AMF3_NUMBER:
|
||||
return load<double>();
|
||||
case AMF3_INTEGER:
|
||||
return load<int>();
|
||||
case AMF3_FALSE:
|
||||
pos++;
|
||||
return false;
|
||||
case AMF3_TRUE:
|
||||
pos++;
|
||||
return true;
|
||||
case AMF3_OBJECT:
|
||||
return load_object();
|
||||
case AMF3_ARRAY:
|
||||
return load_ecma();
|
||||
case AMF3_NULL:
|
||||
pos++;
|
||||
return AMF_NULL;
|
||||
case AMF3_UNDEFINED:
|
||||
pos++;
|
||||
return AMF_UNDEFINED;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
StrPrinter << "Unsupported AMF3 type:" << (int) type << endl);
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case AMF0_STRING:
|
||||
return load<std::string>();
|
||||
case AMF0_NUMBER:
|
||||
return load<double>();
|
||||
case AMF0_BOOLEAN:
|
||||
return load<bool>();
|
||||
case AMF0_OBJECT:
|
||||
return load_object();
|
||||
case AMF0_ECMA_ARRAY:
|
||||
return load_ecma();
|
||||
case AMF0_NULL:
|
||||
pos++;
|
||||
return AMF_NULL;
|
||||
case AMF0_UNDEFINED:
|
||||
pos++;
|
||||
return AMF_UNDEFINED;
|
||||
case AMF0_STRICT_ARRAY:
|
||||
return load_arr();
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
StrPrinter << "Unsupported AMF type:" << (int) type << endl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string AMFDecoder::load_key() {
|
||||
if (pos + 2 > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
size_t str_len = load_be16(&buf[pos]);
|
||||
pos += 2;
|
||||
if (pos + str_len > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
std::string s(buf, pos, str_len);
|
||||
pos += str_len;
|
||||
return s;
|
||||
|
||||
}
|
||||
|
||||
AMFValue AMFDecoder::load_object() {
|
||||
AMFValue object(AMF_OBJECT);
|
||||
if (pop_front() != AMF0_OBJECT) {
|
||||
throw std::runtime_error("Expected an object");
|
||||
}
|
||||
while (1) {
|
||||
std::string key = load_key();
|
||||
if (key.empty())
|
||||
break;
|
||||
AMFValue value = load<AMFValue>();
|
||||
object.set(key, value);
|
||||
}
|
||||
if (pop_front() != AMF0_OBJECT_END) {
|
||||
throw std::runtime_error("expected object end");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
AMFValue AMFDecoder::load_ecma() {
|
||||
/* ECMA array is the same as object, with 4 extra zero bytes */
|
||||
AMFValue object(AMF_ECMA_ARRAY);
|
||||
if (pop_front() != AMF0_ECMA_ARRAY) {
|
||||
throw std::runtime_error("Expected an ECMA array");
|
||||
}
|
||||
if (pos + 4 > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
pos += 4;
|
||||
while (1) {
|
||||
std::string key = load_key();
|
||||
if (key.empty())
|
||||
break;
|
||||
AMFValue value = load<AMFValue>();
|
||||
object.set(key, value);
|
||||
}
|
||||
if (pop_front() != AMF0_OBJECT_END) {
|
||||
throw std::runtime_error("expected object end");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
AMFValue AMFDecoder::load_arr() {
|
||||
/* ECMA array is the same as object, with 4 extra zero bytes */
|
||||
AMFValue object(AMF_STRICT_ARRAY);
|
||||
if (pop_front() != AMF0_STRICT_ARRAY) {
|
||||
throw std::runtime_error("Expected an STRICT array");
|
||||
}
|
||||
if (pos + 4 > buf.size()) {
|
||||
throw std::runtime_error("Not enough data");
|
||||
}
|
||||
int arrSize = load_be32(&buf[pos]);
|
||||
pos += 4;
|
||||
while (arrSize--) {
|
||||
AMFValue value = load<AMFValue>();
|
||||
object.add(value);
|
||||
}
|
||||
/*pos += 2;
|
||||
if (pop_front() != AMF0_OBJECT_END) {
|
||||
throw std::runtime_error("expected object end");
|
||||
}*/
|
||||
return object;
|
||||
}
|
||||
223
src/Rtmp/amf.h
Normal file
223
src/Rtmp/amf.h
Normal file
@@ -0,0 +1,223 @@
|
||||
#ifndef __amf_h
|
||||
#define __amf_h
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
enum AMFType {
|
||||
AMF_NUMBER,
|
||||
AMF_INTEGER,
|
||||
AMF_BOOLEAN,
|
||||
AMF_STRING,
|
||||
AMF_OBJECT,
|
||||
AMF_NULL,
|
||||
AMF_UNDEFINED,
|
||||
AMF_ECMA_ARRAY,
|
||||
AMF_STRICT_ARRAY,
|
||||
};
|
||||
|
||||
class AMFValue;
|
||||
|
||||
class AMFValue {
|
||||
public:
|
||||
|
||||
AMFValue(AMFType type = AMF_NULL);
|
||||
AMFValue(const char *s);
|
||||
AMFValue(const std::string &s);
|
||||
AMFValue(double n);
|
||||
AMFValue(int i);
|
||||
AMFValue(bool b);
|
||||
AMFValue(const AMFValue &from);
|
||||
AMFValue(AMFValue &&from);
|
||||
AMFValue &operator =(const AMFValue &from);
|
||||
AMFValue &operator =(AMFValue &&from);
|
||||
~AMFValue();
|
||||
|
||||
void clear() {
|
||||
switch (m_type) {
|
||||
case AMF_STRING:
|
||||
m_value.string->clear();
|
||||
break;
|
||||
case AMF_OBJECT:
|
||||
case AMF_ECMA_ARRAY:
|
||||
m_value.object->clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AMFType type() const {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
const std::string &as_string() const {
|
||||
if(m_type != AMF_STRING){
|
||||
throw std::runtime_error("AMF not a string");
|
||||
}
|
||||
return *m_value.string;
|
||||
}
|
||||
double as_number() const {
|
||||
switch (m_type) {
|
||||
case AMF_NUMBER:
|
||||
return m_value.number;
|
||||
case AMF_INTEGER:
|
||||
return m_value.integer;
|
||||
case AMF_BOOLEAN:
|
||||
return m_value.boolean;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("AMF not a number");
|
||||
break;
|
||||
}
|
||||
}
|
||||
int as_integer() const {
|
||||
switch (m_type) {
|
||||
case AMF_NUMBER:
|
||||
return m_value.number;
|
||||
case AMF_INTEGER:
|
||||
return m_value.integer;
|
||||
case AMF_BOOLEAN:
|
||||
return m_value.boolean;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("AMF not a integer");
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool as_boolean() const {
|
||||
switch (m_type) {
|
||||
case AMF_NUMBER:
|
||||
return m_value.number;
|
||||
case AMF_INTEGER:
|
||||
return m_value.integer;
|
||||
case AMF_BOOLEAN:
|
||||
return m_value.boolean;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("AMF not a boolean");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const AMFValue &operator[](const std::string &s) const {
|
||||
if (m_type != AMF_OBJECT && m_type != AMF_ECMA_ARRAY) {
|
||||
throw std::runtime_error("AMF not a object");
|
||||
}
|
||||
auto i = m_value.object->find(s);
|
||||
if (i == m_value.object->end()) {
|
||||
static AMFValue val(AMF_NULL);
|
||||
return val;
|
||||
}
|
||||
return i->second;
|
||||
}
|
||||
template<typename FUN>
|
||||
void object_for_each(const FUN &fun) const {
|
||||
if (m_type != AMF_OBJECT && m_type != AMF_ECMA_ARRAY) {
|
||||
throw std::runtime_error("AMF not a object");
|
||||
}
|
||||
for (auto & pr : *(m_value.object)) {
|
||||
fun(pr.first, pr.second);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator()() const {
|
||||
return m_type != AMF_NULL;
|
||||
}
|
||||
void set(const std::string &s, const AMFValue &val) {
|
||||
if (m_type != AMF_OBJECT && m_type != AMF_ECMA_ARRAY) {
|
||||
throw std::runtime_error("AMF not a object");
|
||||
}
|
||||
m_value.object->emplace(s, val);
|
||||
}
|
||||
void add(const AMFValue &val) {
|
||||
if (m_type != AMF_STRICT_ARRAY) {
|
||||
throw std::runtime_error("AMF not a array");
|
||||
}
|
||||
assert(m_type == AMF_STRICT_ARRAY);
|
||||
m_value.array->push_back(val);
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::unordered_map<std::string, AMFValue> mapType;
|
||||
typedef std::vector<AMFValue> arrayType;
|
||||
|
||||
AMFType m_type;
|
||||
union {
|
||||
std::string *string;
|
||||
double number;
|
||||
int integer;
|
||||
bool boolean;
|
||||
mapType *object;
|
||||
arrayType *array;
|
||||
} m_value;
|
||||
|
||||
friend class AMFEncoder;
|
||||
const mapType &getMap() const {
|
||||
if (m_type != AMF_OBJECT && m_type != AMF_ECMA_ARRAY) {
|
||||
throw std::runtime_error("AMF not a object");
|
||||
}
|
||||
return *m_value.object;
|
||||
}
|
||||
const arrayType &getArr() const {
|
||||
if (m_type != AMF_STRICT_ARRAY) {
|
||||
throw std::runtime_error("AMF not a array");
|
||||
}
|
||||
return *m_value.array;
|
||||
}
|
||||
inline void destroy();
|
||||
inline void init();
|
||||
};
|
||||
|
||||
class AMFDecoder {
|
||||
public:
|
||||
AMFDecoder(const std::string &_buf, size_t _pos, int _version = 0) :
|
||||
buf(_buf), pos(_pos), version(_version) {
|
||||
}
|
||||
|
||||
int getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
template<typename TP>
|
||||
TP load();
|
||||
|
||||
|
||||
|
||||
private:
|
||||
const std::string &buf;
|
||||
size_t pos;
|
||||
int version;
|
||||
|
||||
std::string load_key();
|
||||
AMFValue load_object();
|
||||
AMFValue load_ecma();
|
||||
AMFValue load_arr();
|
||||
uint8_t front();
|
||||
uint8_t pop_front();
|
||||
};
|
||||
|
||||
class AMFEncoder {
|
||||
public:
|
||||
AMFEncoder & operator <<(const char *s);
|
||||
AMFEncoder & operator <<(const std::string &s);
|
||||
AMFEncoder & operator <<(std::nullptr_t);
|
||||
AMFEncoder & operator <<(const int n);
|
||||
AMFEncoder & operator <<(const double n);
|
||||
AMFEncoder & operator <<(const bool b);
|
||||
AMFEncoder & operator <<(const AMFValue &value);
|
||||
const std::string data() const {
|
||||
return buf;
|
||||
}
|
||||
void clear() {
|
||||
buf.clear();
|
||||
}
|
||||
private:
|
||||
void write_key(const std::string &s);
|
||||
AMFEncoder &write_undefined();
|
||||
std::string buf;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
64
src/Rtmp/utils.cpp
Normal file
64
src/Rtmp/utils.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "utils.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/*
|
||||
* Used to do unaligned loads on archs that don't support them. GCC can mostly
|
||||
* optimize these away.
|
||||
*/
|
||||
uint32_t load_be32(const void *p)
|
||||
{
|
||||
uint32_t val;
|
||||
memcpy(&val, p, sizeof val);
|
||||
return ntohl(val);
|
||||
}
|
||||
|
||||
uint16_t load_be16(const void *p)
|
||||
{
|
||||
uint16_t val;
|
||||
memcpy(&val, p, sizeof val);
|
||||
return ntohs(val);
|
||||
}
|
||||
|
||||
uint32_t load_le32(const void *p)
|
||||
{
|
||||
const uint8_t *data = (const uint8_t *) p;
|
||||
return data[0] | ((uint32_t) data[1] << 8) |
|
||||
((uint32_t) data[2] << 16) | ((uint32_t) data[3] << 24);
|
||||
}
|
||||
|
||||
uint32_t load_be24(const void *p)
|
||||
{
|
||||
const uint8_t *data = (const uint8_t *) p;
|
||||
return data[2] | ((uint32_t) data[1] << 8) | ((uint32_t) data[0] << 16);
|
||||
}
|
||||
|
||||
void set_be24(void *p, uint32_t val)
|
||||
{
|
||||
uint8_t *data = (uint8_t *) p;
|
||||
data[0] = val >> 16;
|
||||
data[1] = val >> 8;
|
||||
data[2] = val;
|
||||
}
|
||||
|
||||
void set_le32(void *p, uint32_t val)
|
||||
{
|
||||
uint8_t *data = (uint8_t *) p;
|
||||
data[0] = val;
|
||||
data[1] = val >> 8;
|
||||
data[2] = val >> 16;
|
||||
data[3] = val >> 24;
|
||||
}
|
||||
|
||||
void set_be32(void *p, uint32_t val)
|
||||
{
|
||||
uint8_t *data = (uint8_t *) p;
|
||||
data[3] = val;
|
||||
data[2] = val >> 8;
|
||||
data[1] = val >> 16;
|
||||
data[0] = val >> 24;
|
||||
}
|
||||
|
||||
17
src/Rtmp/utils.h
Normal file
17
src/Rtmp/utils.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef __utils_h
|
||||
#define __utils_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
uint32_t load_be32(const void *p);
|
||||
uint16_t load_be16(const void *p);
|
||||
uint32_t load_be24(const void *p);
|
||||
uint32_t load_le32(const void *p);
|
||||
void set_be24(void *p, uint32_t val);
|
||||
void set_le32(void *p, uint32_t val);
|
||||
void set_be32(void *p, uint32_t val);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user