初始提交

This commit is contained in:
xzl
2017-04-01 16:35:56 +08:00
parent aef0ecbcb9
commit 3f73024a9b
131 changed files with 18216 additions and 0 deletions

223
src/Rtmp/Rtmp.h Normal file
View 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

View 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
View 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
View 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
View 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
View 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
View 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_ */

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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_ */

View 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 */

View 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
View 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
View 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
View 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
View 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