整理代码
This commit is contained in:
commit
c7b4e178ec
364 changed files with 59691 additions and 0 deletions
237
video/mediaserver/MPEGMuxer.cpp
Normal file
237
video/mediaserver/MPEGMuxer.cpp
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
*******************************************************************************
|
||||
* INCLUDE
|
||||
*******************************************************************************
|
||||
*/
|
||||
#include "MPEGMuxer.h"
|
||||
#include "../core/VideoEncoder.h"
|
||||
#include "../core/AudioEncoder.h"
|
||||
#include "../core/SampleFormat.h"
|
||||
#include "../core/System.h"
|
||||
|
||||
namespace Video
|
||||
{
|
||||
namespace MediaServer
|
||||
{
|
||||
/*
|
||||
*******************************************************************************
|
||||
* TYPEDEFS
|
||||
*******************************************************************************
|
||||
*/
|
||||
struct CodecIDItem
|
||||
{
|
||||
Core::CodecID id;
|
||||
int type;
|
||||
};
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* LOCAL VARIABLES
|
||||
*******************************************************************************
|
||||
*/
|
||||
static const std::array<CodecIDItem, 8> codecMap = {{
|
||||
{Core::CodecID::H264, PSI_STREAM_H264},
|
||||
{Core::CodecID::H265, PSI_STREAM_H265},
|
||||
{Core::CodecID::MJPEG, PSI_STREAM_JPEG_2000},
|
||||
{Core::CodecID::MP3, PSI_STREAM_MP3},
|
||||
{Core::CodecID::AAC, PSI_STREAM_AAC},
|
||||
{Core::CodecID::OPUS, PSI_STREAM_AUDIO_OPUS},
|
||||
{Core::CodecID::G711A, PSI_STREAM_AUDIO_G711A},
|
||||
{Core::CodecID::G711U, PSI_STREAM_AUDIO_G711U},
|
||||
}};
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DEFINE
|
||||
*******************************************************************************
|
||||
*/
|
||||
MPEGMuxer::Stream::Stream(const std::string& name, int id, Core::PacketQueue::Ptr pQueue)
|
||||
: _name(name), _id(id), _pQueue(pQueue)
|
||||
{
|
||||
}
|
||||
|
||||
MPEGMuxer::Stream::~Stream()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DEFINE
|
||||
*******************************************************************************
|
||||
*/
|
||||
static int isMulticastAddr(const char* ip)
|
||||
{
|
||||
int r;
|
||||
struct addrinfo* ai;
|
||||
r = getaddrinfo(ip, 0, NULL, &ai);
|
||||
if (0 != r)
|
||||
return 0;
|
||||
|
||||
if (AF_INET == ai->ai_addr->sa_family)
|
||||
{
|
||||
const struct sockaddr_in* in = (const struct sockaddr_in*)ai->ai_addr;
|
||||
assert(sizeof(struct sockaddr_in) == ai->ai_addrlen);
|
||||
r = (ntohl(in->sin_addr.s_addr) & 0xf0000000) == 0xe0000000 ? 1 : 0;
|
||||
}
|
||||
else if (AF_INET6 == ai->ai_addr->sa_family)
|
||||
{
|
||||
const struct sockaddr_in6* in6 = (const struct sockaddr_in6*)ai->ai_addr;
|
||||
assert(sizeof(struct sockaddr_in6) == ai->ai_addrlen);
|
||||
r = in6->sin6_addr.s6_addr[0] == 0xff ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(0);
|
||||
r = 0;
|
||||
}
|
||||
|
||||
freeaddrinfo(ai);
|
||||
return r;
|
||||
}
|
||||
|
||||
Video::MediaServer::MPEGMuxer::MPEGMuxer(const std::string& url)
|
||||
: Core::Muxer(Poco::format("MediaServer::MPEGMuxer(%s)", url), url)
|
||||
{
|
||||
}
|
||||
|
||||
MPEGMuxer::~MPEGMuxer()
|
||||
{
|
||||
}
|
||||
|
||||
void MPEGMuxer::connect()
|
||||
{
|
||||
Poco::URI uri(url());
|
||||
|
||||
std::string scheme = uri.getScheme();
|
||||
std::string host = uri.getHost();
|
||||
short port = uri.getPort();
|
||||
|
||||
_socket = socket_udp_bind_ipv4(nullptr, 50609);
|
||||
if (socket_invalid == _socket)
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: Create socket FAIL", name()));
|
||||
|
||||
if (isMulticastAddr(host.c_str()))
|
||||
socket_udp_multicast(_socket, host.c_str(), nullptr, 16);
|
||||
|
||||
memset(&_destAddr, 0, sizeof(_destAddr));
|
||||
socket_addr_from_ipv4(&_destAddr, host.c_str(), port);
|
||||
|
||||
static mpeg_muxer_func_t func = {[](void* param, size_t bytes) -> void*
|
||||
{
|
||||
MPEGMuxer* pMuxer = (MPEGMuxer*)param;
|
||||
if (pMuxer->_writeIndex + bytes > sizeof(pMuxer->_buffer))
|
||||
pMuxer->flush();
|
||||
|
||||
return &pMuxer->_buffer[pMuxer->_writeIndex];
|
||||
},
|
||||
[](void* param, void* packet) -> void {},
|
||||
[](void* param, int stream, void* packet, size_t bytes) -> int
|
||||
{
|
||||
MPEGMuxer* pMuxer = (MPEGMuxer*)param;
|
||||
pMuxer->_writeIndex += bytes;
|
||||
return 0;
|
||||
}};
|
||||
|
||||
_pMuxer = mpeg_muxer_create(0, &func, this);
|
||||
if (!_pMuxer)
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: Create MPEG-Muxer FAIL!", name()));
|
||||
|
||||
// mpeg_muxer_add_program(_pMuxer, 1, "XXXX", 4); /* 多节目方式 */
|
||||
previousComponents().foreach (
|
||||
[&](Core::Component::Ptr pPrevious) -> void
|
||||
{
|
||||
auto pVideoEncoder = pPrevious.cast<Core::VideoEncoder>();
|
||||
if (!pVideoEncoder.isNull())
|
||||
{
|
||||
int id = mpeg_muxer_add_stream(_pMuxer, getCodecID(pVideoEncoder->getCodecID()), nullptr, 0);
|
||||
|
||||
_streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pVideoEncoder->name()), id,
|
||||
packetQueues().find(pPrevious)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto pAudioEncoder = pPrevious.cast<Core::AudioEncoder>();
|
||||
if (!pAudioEncoder.isNull())
|
||||
{
|
||||
int id = mpeg_muxer_add_stream(_pMuxer, getCodecID(pAudioEncoder->getCodecID()), nullptr, 0);
|
||||
|
||||
_streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pAudioEncoder->name()), id,
|
||||
packetQueues().find(pPrevious)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MPEGMuxer::mux()
|
||||
{
|
||||
std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; });
|
||||
auto& pStream = *_streams.begin();
|
||||
|
||||
Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000);
|
||||
if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode()))
|
||||
throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name()));
|
||||
|
||||
pStream->setTimeline(pPacket->pts());
|
||||
|
||||
int flags = 0;
|
||||
if (pPacket->isVideo())
|
||||
flags |= pPacket->isKeyFrame() ? MPEG_FLAG_IDR_FRAME : 0;
|
||||
|
||||
mpeg_muxer_input(_pMuxer, pStream->id(), flags, pPacket->pts() * 90000 / 1000000, pPacket->dts() * 90000 / 1000000,
|
||||
pPacket->data(), pPacket->length());
|
||||
|
||||
#if (TRACE_VIDEO_FRAME_RATE)
|
||||
if (pPacket->isVideo())
|
||||
{
|
||||
count++;
|
||||
uint64_t now = Core::System::timestamp();
|
||||
int64_t delta = now - timeline;
|
||||
if (delta >= 1000000)
|
||||
{
|
||||
timeline = now;
|
||||
std::cout << " ==> " << name() << " fps: " << (double)count / (delta / 1000000) << std::endl;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MPEGMuxer::disconnect()
|
||||
{
|
||||
if (_writeIndex > 0 && _socket != socket_invalid)
|
||||
flush();
|
||||
|
||||
if (_pMuxer)
|
||||
{
|
||||
mpeg_muxer_destroy(_pMuxer);
|
||||
_pMuxer = nullptr;
|
||||
}
|
||||
|
||||
if (_socket != socket_invalid)
|
||||
{
|
||||
socket_close(_socket);
|
||||
_socket = socket_invalid;
|
||||
}
|
||||
|
||||
_streams.clear();
|
||||
}
|
||||
|
||||
void MPEGMuxer::flush()
|
||||
{
|
||||
socket_sendto_by_time(_socket, _buffer, _writeIndex, 0, (const struct sockaddr*)&_destAddr, sizeof(_destAddr),
|
||||
1000);
|
||||
_writeIndex = 0;
|
||||
}
|
||||
|
||||
const int MPEGMuxer::getCodecID(Core::CodecID id)
|
||||
{
|
||||
for (const auto& m : codecMap)
|
||||
{
|
||||
if (id == m.id)
|
||||
return m.type;
|
||||
}
|
||||
|
||||
throw Poco::NotFoundException(
|
||||
Poco::format("Video::MediaServer: Can't find %s in codec-map", Core::codecIDToString(id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
98
video/mediaserver/MPEGMuxer.h
Normal file
98
video/mediaserver/MPEGMuxer.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#ifndef F9CED6CA_74FC_49C3_8D58_95A62F2EA1EC
|
||||
#define F9CED6CA_74FC_49C3_8D58_95A62F2EA1EC
|
||||
/*
|
||||
*******************************************************************************
|
||||
* INCLUDE
|
||||
*******************************************************************************
|
||||
*/
|
||||
#include "../core/Muxer.h"
|
||||
#include "util/ClassAssistant.h"
|
||||
#include <sdk/include/sockutil.h>
|
||||
#include <media-server/libmpeg/mpeg-muxer.h>
|
||||
#include <media-server/libmpeg/mpeg-proto.h>
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* MACROS
|
||||
*******************************************************************************
|
||||
*/
|
||||
#define TRACE_VIDEO_FRAME_RATE (0)
|
||||
|
||||
namespace Video
|
||||
{
|
||||
namespace MediaServer
|
||||
{
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DECLARE
|
||||
*******************************************************************************
|
||||
*/
|
||||
class MPEGMuxer: public Core::Muxer
|
||||
{
|
||||
private:
|
||||
class Stream
|
||||
{
|
||||
public:
|
||||
using Ptr = Poco::SharedPtr<Stream>;
|
||||
|
||||
public:
|
||||
Stream(const std::string& name, int id, Core::PacketQueue::Ptr pQueue);
|
||||
~Stream();
|
||||
|
||||
public:
|
||||
bool operator<(const Stream& other);
|
||||
Core::PacketQueue::Ptr queue();
|
||||
Core::SerialCodeWatcher& serialCodeWatcher();
|
||||
|
||||
PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public);
|
||||
READONLY_PROPERTY_DECLARE(name, std::string)
|
||||
READONLY_PROPERTY_DECLARE(id, int)
|
||||
|
||||
private:
|
||||
Core::PacketQueue::Ptr _pQueue;
|
||||
Core::SerialCodeWatcher _serialCodeWatcher;
|
||||
};
|
||||
|
||||
public:
|
||||
MPEGMuxer(const std::string& url);
|
||||
~MPEGMuxer();
|
||||
|
||||
protected:
|
||||
void connect() override;
|
||||
void mux() override;
|
||||
void disconnect() override;
|
||||
void flush();
|
||||
|
||||
const int getCodecID(Core::CodecID id);
|
||||
|
||||
private:
|
||||
socket_t _socket;
|
||||
struct sockaddr_in _destAddr;
|
||||
mpeg_muxer_t* _pMuxer;
|
||||
std::vector<Stream::Ptr> _streams;
|
||||
|
||||
uint8_t _buffer[1316];
|
||||
int _writeIndex;
|
||||
#if (TRACE_VIDEO_FRAME_RATE)
|
||||
int count = 0;
|
||||
uint64_t timeline = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
inline bool MPEGMuxer::Stream::operator<(const Stream& other)
|
||||
{
|
||||
return _Timeline < other._Timeline;
|
||||
}
|
||||
|
||||
inline Core::PacketQueue::Ptr MPEGMuxer::Stream::queue()
|
||||
{
|
||||
return _pQueue;
|
||||
}
|
||||
|
||||
inline Core::SerialCodeWatcher& MPEGMuxer::Stream::serialCodeWatcher()
|
||||
{
|
||||
return _serialCodeWatcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
554
video/mediaserver/RTMPDemuxHandler.cpp
Normal file
554
video/mediaserver/RTMPDemuxHandler.cpp
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
/*
|
||||
*******************************************************************************
|
||||
* INCLUDE
|
||||
*******************************************************************************
|
||||
*/
|
||||
#include "RTMPDemuxHandler.h"
|
||||
#include "Logger.h"
|
||||
#include "../core/System.h"
|
||||
#include "util/Math.h"
|
||||
#include <list>
|
||||
|
||||
namespace Video
|
||||
{
|
||||
namespace MediaServer
|
||||
{
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DEFINE
|
||||
*******************************************************************************
|
||||
*/
|
||||
RTMPDemuxHandler::RTMPDemuxHandler(const std::string& uri)
|
||||
: Core::DemuxHandler(Poco::format("MediaServer::DemuxHandler(%s)", uri), uri)
|
||||
{
|
||||
}
|
||||
|
||||
RTMPDemuxHandler::~RTMPDemuxHandler()
|
||||
{
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder,
|
||||
const Post& post, std::atomic<bool>& isCancelled)
|
||||
{
|
||||
_pVideoEncoder = pVideoEncoder;
|
||||
_pAudioEncoder = pAudioEncoder;
|
||||
_post = post;
|
||||
|
||||
Poco::URI uriParser(uri());
|
||||
std::string scheme = uriParser.getScheme();
|
||||
std::string host = uriParser.getHost();
|
||||
short port = uriParser.getPort();
|
||||
port = port <= 0 ? 1935 : port;
|
||||
std::string path = uriParser.getPath();
|
||||
|
||||
if (!path.empty() && path[0] == '/')
|
||||
path = path.substr(1);
|
||||
|
||||
size_t pos = path.find_first_of('/');
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
logger().error("%s: Invalid URL: %s", name(), uri());
|
||||
Poco::Thread::sleep(2000);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string turl = scheme + "://" + host + ":" + std::to_string(port) + "/" + path.substr(0, pos);
|
||||
std::string appname = path.substr(0, pos);
|
||||
std::string playpath = path.substr(pos + 1);
|
||||
|
||||
struct rtmp_client_handler_t handler;
|
||||
handler.send = RTMPDemuxHandler::clientSend;
|
||||
handler.onaudio = RTMPDemuxHandler::clientOnAudio;
|
||||
handler.onvideo = RTMPDemuxHandler::clientOnVideo;
|
||||
handler.onscript = RTMPDemuxHandler::clientOnScript;
|
||||
|
||||
const size_t buffsz = 2 * 1024 * 1024;
|
||||
Poco::SharedPtr<uint8_t> pBuffer = new uint8_t[buffsz];
|
||||
int rc;
|
||||
while (!isCancelled)
|
||||
{
|
||||
_audioInfoParsed = false;
|
||||
_videoInfoParsed = false;
|
||||
|
||||
while (!isCancelled)
|
||||
{
|
||||
_socket = socket_connect_host(host.c_str(), port, 2000);
|
||||
if (_socket != socket_invalid)
|
||||
{
|
||||
socket_setnonblock(_socket, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
Poco::Thread::sleep(2000);
|
||||
continue;
|
||||
}
|
||||
|
||||
_pCient = rtmp_client_create(appname.c_str(), playpath.c_str(), turl.c_str(), this, &handler);
|
||||
if (!_pCient)
|
||||
{
|
||||
logger().error("%s: Create RTMP-Client FAIL!(URL: %s)", name(), uri());
|
||||
Poco::Thread::sleep(2000);
|
||||
goto rtmp_create_fail;
|
||||
}
|
||||
|
||||
_pDemuxer = flv_demuxer_create(RTMPDemuxHandler::flvDemuxerHandler, this);
|
||||
if (!_pDemuxer)
|
||||
{
|
||||
logger().error("%s: Create FLV Debuxer FAIL!(URL: %s)", name(), uri());
|
||||
Poco::Thread::sleep(2000);
|
||||
goto demuxer_fail;
|
||||
}
|
||||
|
||||
rc = rtmp_client_start(_pCient, 1);
|
||||
if (rc != 0)
|
||||
{
|
||||
logger().error("%s: Start RTMP-Client FAIL!(URL: %s)", name(), uri());
|
||||
Poco::Thread::sleep(2000);
|
||||
goto rmtp_start_fail;
|
||||
}
|
||||
|
||||
while (!isCancelled)
|
||||
{
|
||||
rc = socket_recv(_socket, pBuffer.get(), buffsz, 0);
|
||||
if (rc <= 0)
|
||||
{
|
||||
logger().error("%s: Socket recv FAIL: %d(URL: %s)", name(), rc, uri());
|
||||
break;
|
||||
}
|
||||
|
||||
rc = rtmp_client_input(_pCient, pBuffer.get(), rc);
|
||||
if (rc != 0)
|
||||
{
|
||||
logger().error("%s: RTMP client input FAIL: %d(URL: %s)", name(), rc, uri());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rtmp_client_stop(_pCient);
|
||||
|
||||
rmtp_start_fail:
|
||||
flv_demuxer_destroy(_pDemuxer);
|
||||
|
||||
demuxer_fail:
|
||||
rtmp_client_destroy(_pCient);
|
||||
|
||||
rtmp_create_fail:
|
||||
socket_close(_socket);
|
||||
_socket = socket_invalid;
|
||||
Poco::Thread::sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::parseFLVMetaData(const uint8_t* data, size_t bytes)
|
||||
{
|
||||
double audiocodecid = 0;
|
||||
double audiodatarate = 0;
|
||||
double audiodelay = 0;
|
||||
double audiosamplerate = 0;
|
||||
double audiosamplesize = 0;
|
||||
int stereo = 0;
|
||||
double videocodecid = 0;
|
||||
double videodatarate = 0;
|
||||
double framerate = 0;
|
||||
double height = 0;
|
||||
double width = 0;
|
||||
|
||||
struct amf_object_item_t prop[16];
|
||||
struct amf_object_item_t items[1];
|
||||
char buffer[64];
|
||||
|
||||
#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) \
|
||||
{ \
|
||||
v.type = amf_type; \
|
||||
v.name = amf_name; \
|
||||
v.value = amf_value; \
|
||||
v.size = amf_size; \
|
||||
}
|
||||
int i = 0;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiocodecid", &audiocodecid, sizeof(audiocodecid));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiodatarate", &audiodatarate, sizeof(audiodatarate));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiodelay", &audiodelay, sizeof(audiodelay));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiosamplerate", &audiosamplerate, sizeof(audiosamplerate));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "audiosamplesize", &audiosamplesize, sizeof(audiosamplesize));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_BOOLEAN, "stereo", &stereo, sizeof(stereo));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "videocodecid", &videocodecid, sizeof(videocodecid));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "videodatarate", &videodatarate, sizeof(videodatarate));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "framerate", &framerate, sizeof(framerate));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "height", &height, sizeof(height));
|
||||
i++;
|
||||
AMF_OBJECT_ITEM_VALUE(prop[i], AMF_NUMBER, "width", &width, sizeof(width));
|
||||
i++;
|
||||
|
||||
AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "onMetaData", prop, i);
|
||||
|
||||
const uint8_t* end = data + bytes;
|
||||
if (AMF_STRING != data[0] || NULL == (data = AMFReadString(data + 1, end, 0, buffer, sizeof(buffer) - 1)))
|
||||
return -1;
|
||||
|
||||
if (0 == strcmp(buffer, "@setDataFrame"))
|
||||
{
|
||||
if (AMF_STRING != data[0] || NULL == (data = AMFReadString(data + 1, end, 0, buffer, sizeof(buffer) - 1)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != strcmp(buffer, "onMetaData"))
|
||||
return 0;
|
||||
|
||||
if (amf_read_items(data, end, items, sizeof(items) / sizeof(items[0])))
|
||||
{
|
||||
if (framerate > 0)
|
||||
_pVideoEncoder->setFrameRate(framerate > 60 ? 60 : framerate);
|
||||
|
||||
if (videodatarate > 0)
|
||||
_pVideoEncoder->setBitrate((int64_t)videodatarate);
|
||||
|
||||
if (audiodatarate > 0)
|
||||
_pAudioEncoder->setBitrate((int64_t)audiodatarate);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline const uint8_t* RTMPDemuxHandler::annexBStartcode(const uint8_t* data, size_t bytes)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 2; i + 1 < bytes; i++)
|
||||
{
|
||||
if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2])
|
||||
return data + i + 1;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool RTMPDemuxHandler::isH264BFrame(const void* h264, size_t bytes)
|
||||
{
|
||||
ptrdiff_t n;
|
||||
const uint8_t *p, *next, *end;
|
||||
|
||||
end = (const uint8_t*)h264 + bytes;
|
||||
p = annexBStartcode((const uint8_t*)h264, bytes);
|
||||
|
||||
while (p)
|
||||
{
|
||||
next = annexBStartcode(p, end - p);
|
||||
if (next)
|
||||
n = next - p - 3;
|
||||
else
|
||||
n = end - p;
|
||||
|
||||
while (n > 0 && 0 == p[n - 1])
|
||||
n--;
|
||||
|
||||
assert(n > 0);
|
||||
if (n > 0)
|
||||
{
|
||||
if ((p[0] & 0x1f) == 1)
|
||||
{
|
||||
bitstream_t stream;
|
||||
bitstream_init(&stream, (const uint8_t*)(p + 1), n - 1);
|
||||
bitstream_read_ue(&stream);
|
||||
int32_t sliceType = bitstream_read_ue(&stream);
|
||||
if (sliceType == 1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
p = next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processH264Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
if (!_videoInfoParsed)
|
||||
mpeg4_h264_annexb_nalu(data, bytes, RTMPDemuxHandler::h264NALhandler, this);
|
||||
|
||||
enqueueVideoPacket(data, bytes, pts, dts, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processH265Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
if (!_videoInfoParsed)
|
||||
mpeg4_h264_annexb_nalu(data, bytes, RTMPDemuxHandler::h265NALhandler, this);
|
||||
|
||||
enqueueVideoPacket(data, bytes, pts, dts, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processASCData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
struct mpeg4_aac_t aac;
|
||||
if (mpeg4_aac_audio_specific_config_load((const uint8_t*)data, bytes, &aac) >= 0)
|
||||
{
|
||||
_pAudioEncoder->setCodecID(Core::CodecID::AAC);
|
||||
_pAudioEncoder->setFrameSize(1024);
|
||||
_pAudioEncoder->setSampleFormat(Core::SampleFormat::FLTP);
|
||||
_pAudioEncoder->setSampleRate((int)aac.sampling_frequency);
|
||||
_pAudioEncoder->setChannelLayout(aac.channels == 2 ? Core::ChannelLayout::STEREO : Core::ChannelLayout::MONO);
|
||||
_pAudioEncoder->commit();
|
||||
logger().information("%s: Audio found: AAC %dHz %s", name(), (int)aac.sampling_frequency,
|
||||
Core::channelLayoutToString(_pAudioEncoder->getChannelLayout()));
|
||||
;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processAACData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
enqueueAudioPacket(data, bytes, pts, dts, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processOpusHeadData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
struct opus_head_t opus;
|
||||
if (opus_head_load((const uint8_t*)data, bytes, &opus) >= 0)
|
||||
{
|
||||
_pAudioEncoder->setCodecID(Core::CodecID::OPUS);
|
||||
_pAudioEncoder->setFrameSize(960);
|
||||
_pAudioEncoder->setSampleFormat(Core::SampleFormat::S16);
|
||||
_pAudioEncoder->setSampleRate((int)opus.input_sample_rate);
|
||||
_pAudioEncoder->setChannelLayout(opus_head_channels(&opus) == 2 ? Core::ChannelLayout::STEREO
|
||||
: Core::ChannelLayout::MONO);
|
||||
_pAudioEncoder->commit();
|
||||
logger().information("%s: Audio found: OPUS %dHz %s", name(), (int)opus.input_sample_rate,
|
||||
Core::channelLayoutToString(_pAudioEncoder->getChannelLayout()));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processOpusData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
enqueueAudioPacket(data, bytes, pts, dts, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processG711AData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
if (!_audioInfoParsed)
|
||||
{
|
||||
_pAudioEncoder->setCodecID(Core::CodecID::G711A);
|
||||
_pAudioEncoder->setFrameSize(1024);
|
||||
_pAudioEncoder->setSampleFormat(Core::SampleFormat::S16P);
|
||||
_pAudioEncoder->setSampleRate((int)8000);
|
||||
_pAudioEncoder->setChannelLayout(Core::ChannelLayout::MONO);
|
||||
_pAudioEncoder->commit();
|
||||
logger().information("%s: Audio found: G711A %dHz %s", name(), 8000, std::string("MONO"));
|
||||
|
||||
_audioInfoParsed = true;
|
||||
}
|
||||
|
||||
enqueueAudioPacket(data, bytes, pts, dts, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::processG711UData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
if (!_audioInfoParsed)
|
||||
{
|
||||
_pAudioEncoder->setCodecID(Core::CodecID::G711U);
|
||||
_pAudioEncoder->setFrameSize(1024);
|
||||
_pAudioEncoder->setSampleFormat(Core::SampleFormat::S16P);
|
||||
_pAudioEncoder->setSampleRate((int)8000);
|
||||
_pAudioEncoder->setChannelLayout(Core::ChannelLayout::MONO);
|
||||
_pAudioEncoder->commit();
|
||||
logger().information("%s: Audio found: G711U %dHz %s", name(), 8000, std::string("MONO"));
|
||||
|
||||
_audioInfoParsed = true;
|
||||
}
|
||||
|
||||
enqueueAudioPacket(data, bytes, pts, dts, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::enqueueVideoPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
uint8_t* pBuffer = new uint8_t[bytes];
|
||||
memcpy(pBuffer, data, bytes);
|
||||
|
||||
uint64_t localPTS = pts;
|
||||
uint64_t localDTS = dts;
|
||||
|
||||
adjustTimestamp(localPTS, localDTS);
|
||||
updateTimeline(localPTS);
|
||||
|
||||
_post(new Core::Packet(_pVideoEncoder->code(),
|
||||
Core::Packet::FLAG_IS_VIDEO | (flags ? Core::Packet::FLAG_IS_KEY : 0), pBuffer, bytes,
|
||||
localPTS, localDTS),
|
||||
Core::MediaType::VIDEO);
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::enqueueAudioPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
|
||||
{
|
||||
uint8_t* pBuffer = new uint8_t[bytes];
|
||||
memcpy(pBuffer, data, bytes);
|
||||
|
||||
uint64_t localPTS = pts;
|
||||
uint64_t localDTS = dts;
|
||||
|
||||
adjustTimestamp(localPTS, localDTS);
|
||||
updateTimeline(localPTS);
|
||||
_post(new Core::Packet(_pAudioEncoder->code(), flags, pBuffer, bytes, localPTS, localDTS), Core::MediaType::AUDIO);
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes)
|
||||
{
|
||||
socket_bufvec_t vec[2];
|
||||
socket_setbufvec(vec, 0, (void*)header, len);
|
||||
socket_setbufvec(vec, 1, (void*)data, bytes);
|
||||
return socket_send_v_all_by_time(((RTMPDemuxHandler*)param)->_socket, vec, bytes ? 2 : 1, 0, 2000);
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::clientOnAudio(void* param, const void* data, size_t bytes, uint32_t timestamp)
|
||||
{
|
||||
return flv_demuxer_input(((RTMPDemuxHandler*)param)->_pDemuxer, FLV_TYPE_AUDIO, data, bytes, timestamp);
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::clientOnVideo(void* param, const void* data, size_t bytes, uint32_t timestamp)
|
||||
{
|
||||
return flv_demuxer_input(((RTMPDemuxHandler*)param)->_pDemuxer, FLV_TYPE_VIDEO, data, bytes, timestamp);
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::clientOnScript(void* param, const void* data, size_t bytes, uint32_t timestamp)
|
||||
{
|
||||
return flv_demuxer_input(((RTMPDemuxHandler*)param)->_pDemuxer, FLV_TYPE_SCRIPT, data, bytes, timestamp);
|
||||
}
|
||||
|
||||
int RTMPDemuxHandler::flvDemuxerHandler(void* param, int codec, const void* data, size_t bytes, uint32_t pts,
|
||||
uint32_t dts, int flags)
|
||||
{
|
||||
int rc;
|
||||
RTMPDemuxHandler* pHandler = (RTMPDemuxHandler*)param;
|
||||
|
||||
switch (codec)
|
||||
{
|
||||
case FLV_SCRIPT_METADATA:
|
||||
rc = pHandler->parseFLVMetaData((const uint8_t*)data, bytes);
|
||||
break;
|
||||
|
||||
case FLV_VIDEO_AVCC:
|
||||
break;
|
||||
|
||||
case FLV_VIDEO_H264:
|
||||
rc = pHandler->processH264Data(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_VIDEO_HVCC:
|
||||
break;
|
||||
|
||||
case FLV_VIDEO_H265:
|
||||
rc = pHandler->processH265Data(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_AUDIO_ASC:
|
||||
rc = pHandler->processASCData(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_AUDIO_AAC:
|
||||
rc = pHandler->processAACData(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_AUDIO_OPUS_HEAD:
|
||||
rc = pHandler->processOpusHeadData(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_AUDIO_OPUS:
|
||||
rc = pHandler->processOpusData(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_AUDIO_G711A:
|
||||
rc = pHandler->processG711AData(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
case FLV_AUDIO_G711U:
|
||||
rc = pHandler->processG711UData(data, bytes, pts, dts, flags);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger().warning("%s: UNIMPLEMENTED FLV-CODEC type: %d(%#x)", pHandler->name(), codec, (uint32_t)codec);
|
||||
break;
|
||||
}
|
||||
|
||||
(void)rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::h264NALhandler(void* param, const uint8_t* nalu, size_t bytes)
|
||||
{
|
||||
RTMPDemuxHandler* pHandler = (RTMPDemuxHandler*)param;
|
||||
|
||||
struct h264_sps_t sps;
|
||||
if (h264_sps_parse(nalu, bytes, &sps) == 0)
|
||||
{
|
||||
int x, y, width, height;
|
||||
|
||||
h264_display_rect(&sps, &x, &y, &width, &height);
|
||||
|
||||
auto pEncoder = pHandler->_pVideoEncoder;
|
||||
pEncoder->setCodecID(Core::CodecID::H264);
|
||||
pEncoder->setWidth(width);
|
||||
pEncoder->setHeight(height);
|
||||
pEncoder->commit();
|
||||
|
||||
pHandler->_videoInfoParsed = true;
|
||||
|
||||
logger().information("%s: Video found: H.264 %dx%d", pHandler->name(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::h265NALhandler(void* param, const uint8_t* nalu, size_t bytes)
|
||||
{
|
||||
RTMPDemuxHandler* pHandler = (RTMPDemuxHandler*)param;
|
||||
|
||||
struct h265_sps_t sps;
|
||||
if (h265_sps_parse(nalu, bytes, &sps) == 0)
|
||||
{
|
||||
int x, y, width, height;
|
||||
|
||||
h265_display_rect(&sps, &x, &y, &width, &height);
|
||||
|
||||
auto pEncoder = pHandler->_pVideoEncoder;
|
||||
pEncoder->setCodecID(Core::CodecID::H265);
|
||||
pEncoder->setWidth(width);
|
||||
pEncoder->setHeight(height);
|
||||
pEncoder->commit();
|
||||
|
||||
pHandler->_videoInfoParsed = true;
|
||||
|
||||
logger().information("%s: Video found: H.265 %dx%d", pHandler->name(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::adjustTimestamp(uint64_t& pts, uint64_t& dts)
|
||||
{
|
||||
pts *= 1000ull;
|
||||
dts *= 1000ull;
|
||||
|
||||
auto drift = getTimeDrift(pts);
|
||||
pts += drift;
|
||||
dts += drift;
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::updateTimeline(uint64_t ts)
|
||||
{
|
||||
if (getTimeline() < ts)
|
||||
setTimeline(ts);
|
||||
}
|
||||
|
||||
void RTMPDemuxHandler::seek(uint64_t ts)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
97
video/mediaserver/RTMPDemuxHandler.h
Normal file
97
video/mediaserver/RTMPDemuxHandler.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef E3F40CBC_D6F1_4B8F_B90E_6765659CF90F
|
||||
#define E3F40CBC_D6F1_4B8F_B90E_6765659CF90F
|
||||
/*
|
||||
*******************************************************************************
|
||||
* INCLUDE
|
||||
*******************************************************************************
|
||||
*/
|
||||
#include "../core/Demuxer.h"
|
||||
extern "C"
|
||||
{
|
||||
#include <media-server/librtmp/rtmp-client.h>
|
||||
#include <media-server/libflv/flv-demuxer.h>
|
||||
#include <media-server/libflv/flv-proto.h>
|
||||
#include <media-server/libflv/opus-head.h>
|
||||
#include <media-server/libflv/mpeg4-aac.h>
|
||||
#include <media-server/libflv/amf0.h>
|
||||
#include <media-server/libflv/mpeg4-avc.h>
|
||||
#include <media-server/libflv/mpeg4-hevc.h>
|
||||
#include <avcodec/h264/bitstream.h>
|
||||
#include <avcodec/h264/h264-nal.h>
|
||||
#include <avcodec/h264/h264-sps.h>
|
||||
#include <avcodec/h264/h264-pps.h>
|
||||
#include <avcodec/h264/h264-util.h>
|
||||
#include <avcodec/h264/h264-internal.h>
|
||||
#include <avcodec/h265/h265-parser.h>
|
||||
#include <sdk/include/sockutil.h>
|
||||
}
|
||||
|
||||
namespace Video
|
||||
{
|
||||
namespace MediaServer
|
||||
{
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DECLARE
|
||||
*******************************************************************************
|
||||
*/
|
||||
class RTMPDemuxHandler: public Core::DemuxHandler
|
||||
{
|
||||
public:
|
||||
RTMPDemuxHandler(const std::string& url);
|
||||
~RTMPDemuxHandler();
|
||||
|
||||
public:
|
||||
void run(Core::VideoEncoder::Ptr pVideoEncoder, Core::AudioEncoder::Ptr pAudioEncoder, const Post& post,
|
||||
std::atomic<bool>& isCancelled) override;
|
||||
|
||||
protected:
|
||||
int parseFLVMetaData(const uint8_t* pData, size_t length);
|
||||
int processH264Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processH265Data(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processASCData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processAACData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processOpusHeadData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processOpusData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processG711AData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
int processG711UData(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
|
||||
void enqueueVideoPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
void enqueueAudioPacket(const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags);
|
||||
|
||||
inline const uint8_t* annexBStartcode(const uint8_t* data, size_t bytes);
|
||||
bool isH264BFrame(const void* h264, size_t bytes);
|
||||
void adjustTimestamp(uint64_t& pts, uint64_t& dts);
|
||||
void updateTimeline(uint64_t ts);
|
||||
void seek(uint64_t) override;
|
||||
|
||||
private:
|
||||
socket_t _socket;
|
||||
rtmp_client_t* _pCient;
|
||||
flv_demuxer_t* _pDemuxer;
|
||||
bool _audioInfoParsed;
|
||||
bool _videoInfoParsed;
|
||||
uint32_t _iFrameLength;
|
||||
Core::VideoEncoder::Ptr _pVideoEncoder;
|
||||
Core::AudioEncoder::Ptr _pAudioEncoder;
|
||||
Post _post;
|
||||
union
|
||||
{
|
||||
struct mpeg4_avc_t avc;
|
||||
struct mpeg4_hevc_t hevc;
|
||||
} _v;
|
||||
|
||||
protected:
|
||||
static int clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes);
|
||||
static int clientOnAudio(void* param, const void* data, size_t bytes, uint32_t timestamp);
|
||||
static int clientOnVideo(void* param, const void* data, size_t bytes, uint32_t timestamp);
|
||||
static int clientOnScript(void* param, const void* data, size_t bytes, uint32_t timestamp);
|
||||
|
||||
static int flvDemuxerHandler(void* param, int codec, const void* data, size_t bytes, uint32_t pts, uint32_t dts,
|
||||
int flags);
|
||||
static void h264NALhandler(void* param, const uint8_t* nalu, size_t bytes);
|
||||
static void h265NALhandler(void* param, const uint8_t* nalu, size_t bytes);
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
369
video/mediaserver/RTMPMuxer.cpp
Normal file
369
video/mediaserver/RTMPMuxer.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
*******************************************************************************
|
||||
* INCLUDE
|
||||
*******************************************************************************
|
||||
*/
|
||||
#include "RTMPMuxer.h"
|
||||
#include "../core/VideoEncoder.h"
|
||||
#include "../core/AudioEncoder.h"
|
||||
#include "../core/SampleFormat.h"
|
||||
#include "../core/System.h"
|
||||
#include <media-server/libflv/flv-proto.h>
|
||||
|
||||
namespace Video
|
||||
{
|
||||
namespace MediaServer
|
||||
{
|
||||
/*
|
||||
*******************************************************************************
|
||||
* TYPEDEFS
|
||||
*******************************************************************************
|
||||
*/
|
||||
struct CodecIDItem
|
||||
{
|
||||
Core::CodecID id;
|
||||
int type;
|
||||
};
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* LOCAL VARIABLES
|
||||
*******************************************************************************
|
||||
*/
|
||||
static const std::array<CodecIDItem, 7> codecMap = {{
|
||||
{Core::CodecID::H264, FLV_VIDEO_H264},
|
||||
{Core::CodecID::H265, FLV_VIDEO_H265},
|
||||
|
||||
{Core::CodecID::MP3, FLV_AUDIO_MP3},
|
||||
{Core::CodecID::AAC, FLV_AUDIO_AAC},
|
||||
{Core::CodecID::OPUS, FLV_AUDIO_OPUS},
|
||||
{Core::CodecID::G711A, FLV_AUDIO_G711A},
|
||||
{Core::CodecID::G711U, FLV_AUDIO_G711U},
|
||||
}};
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DEFINE
|
||||
*******************************************************************************
|
||||
*/
|
||||
RTMPMuxer::Stream::Stream(const std::string& name, Core::CodecID codecID, Core::PacketQueue::Ptr pQueue)
|
||||
: _name(name), _codecID(codecID), _pQueue(pQueue)
|
||||
{
|
||||
switch (codecID)
|
||||
{
|
||||
case Core::CodecID::H264:
|
||||
_pMuxerMethod = flv_muxer_avc;
|
||||
break;
|
||||
|
||||
case Core::CodecID::H265:
|
||||
_pMuxerMethod = flv_muxer_hevc;
|
||||
break;
|
||||
|
||||
case Core::CodecID::MP3:
|
||||
_pMuxerMethod = flv_muxer_mp3;
|
||||
break;
|
||||
|
||||
case Core::CodecID::AAC:
|
||||
_pMuxerMethod = flv_muxer_aac;
|
||||
break;
|
||||
|
||||
case Core::CodecID::OPUS:
|
||||
_pMuxerMethod = flv_muxer_opus;
|
||||
break;
|
||||
|
||||
case Core::CodecID::G711A:
|
||||
_pMuxerMethod = flv_muxer_g711a;
|
||||
break;
|
||||
|
||||
case Core::CodecID::G711U:
|
||||
_pMuxerMethod = flv_muxer_g711u;
|
||||
break;
|
||||
|
||||
default:
|
||||
_pMuxerMethod = nullptr;
|
||||
throw Poco::NotImplementedException(
|
||||
Poco::format("%s: Unsupported codec: %s", name, Core::codecIDToString(codecID)));
|
||||
}
|
||||
}
|
||||
|
||||
RTMPMuxer::Stream::~Stream()
|
||||
{
|
||||
}
|
||||
|
||||
inline int Video::MediaServer::RTMPMuxer::Stream::muxer(flv_muxer_t* muxer, const void* data, size_t bytes,
|
||||
uint32_t pts, uint32_t dts)
|
||||
{
|
||||
if (!_pMuxerMethod)
|
||||
throw Poco::BugcheckException(Poco::format("%s: muxer method is NULL!", name()));
|
||||
|
||||
return _pMuxerMethod(muxer, data, bytes, pts, dts);
|
||||
}
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DEFINE
|
||||
*******************************************************************************
|
||||
*/
|
||||
Video::MediaServer::RTMPMuxer::RTMPMuxer(const std::string& url)
|
||||
: Core::Muxer(Poco::format("MediaServer::RTMPMuxer(%s)", url), url)
|
||||
{
|
||||
}
|
||||
|
||||
RTMPMuxer::~RTMPMuxer()
|
||||
{
|
||||
}
|
||||
|
||||
void RTMPMuxer::connect()
|
||||
{
|
||||
Poco::URI uri(url());
|
||||
|
||||
std::string scheme = uri.getScheme();
|
||||
std::string host = uri.getHost();
|
||||
short port = uri.getPort();
|
||||
if (port <= 0)
|
||||
port = 1935;
|
||||
|
||||
std::string path = uri.getPath();
|
||||
|
||||
if (!path.empty() && path[0] == '/')
|
||||
path = path.substr(1);
|
||||
|
||||
size_t pos = path.find_first_of('/');
|
||||
if (pos == std::string::npos)
|
||||
throw Poco::InvalidArgumentException(Poco::format("%s: Invalid URL: %s", name(), url()));
|
||||
|
||||
_socket = socket_connect_host(host.c_str(), port, 2000);
|
||||
if (socket_invalid == _socket)
|
||||
{
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: Connect FAIL", name()));
|
||||
}
|
||||
|
||||
socket_setnonblock(_socket, 0);
|
||||
|
||||
std::string turl = scheme + "://" + host + ":" + std::to_string(port) + "/" + path.substr(0, pos);
|
||||
std::string appname = path.substr(0, pos);
|
||||
std::string playpath = path.substr(pos + 1);
|
||||
|
||||
struct rtmp_client_handler_t handler = {};
|
||||
handler.send = RTMPMuxer::clientSend;
|
||||
_pClient = rtmp_client_create(appname.c_str(), playpath.c_str(), turl.c_str(), this, &handler);
|
||||
if (!_pClient)
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: Create RTMP-Client FAIL!", name()));
|
||||
|
||||
_pMuxer = flv_muxer_create(RTMPMuxer::muxerHandler, this);
|
||||
if (!_pMuxer)
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: Create RTMP-Muxer FAIL!", name()));
|
||||
|
||||
int rc = rtmp_client_start(_pClient, 0);
|
||||
if (rc != 0)
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: Start RTMP-Client FAIL: %d", name(), rc));
|
||||
|
||||
int limit = 50;
|
||||
uint8_t buffer[4096];
|
||||
while (--limit > 0 && 4 != rtmp_client_getstate(_pClient) &&
|
||||
(rc = socket_recv(_socket, buffer, sizeof(buffer), 0)) > 0)
|
||||
{
|
||||
if (rtmp_client_input(_pClient, buffer, rc) != 0)
|
||||
throw Poco::InvalidAccessException(Poco::format("%s: RTMP-Client input handle FAIL!", name()));
|
||||
}
|
||||
|
||||
struct flv_metadata_t metadata = {};
|
||||
uint8_t opusHead[19] = {0};
|
||||
bool hasOpus = false;
|
||||
previousComponents().foreach (
|
||||
[&](Core::Component::Ptr pPrevious) -> void
|
||||
{
|
||||
auto pVideoEncoder = pPrevious.cast<Core::VideoEncoder>();
|
||||
if (!pVideoEncoder.isNull())
|
||||
{
|
||||
_streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pVideoEncoder->name()),
|
||||
pVideoEncoder->getCodecID(), packetQueues().find(pPrevious)));
|
||||
|
||||
metadata.videocodecid = getCodecID(pVideoEncoder->getCodecID());
|
||||
metadata.videodatarate = pVideoEncoder->getBitrate() / 1000;
|
||||
metadata.framerate = pVideoEncoder->getFrameRate();
|
||||
metadata.width = pVideoEncoder->getWidth();
|
||||
metadata.height = pVideoEncoder->getHeight();
|
||||
return;
|
||||
}
|
||||
|
||||
auto pAudioEncoder = pPrevious.cast<Core::AudioEncoder>();
|
||||
if (!pAudioEncoder.isNull())
|
||||
{
|
||||
_streams.push_back(new Stream(Poco::format("%s/Stream(%s)", name(), pAudioEncoder->name()),
|
||||
pAudioEncoder->getCodecID(), packetQueues().find(pPrevious)));
|
||||
|
||||
metadata.audiocodecid = getCodecID(pAudioEncoder->getCodecID());
|
||||
metadata.audiocodecid >>= 4;
|
||||
metadata.audiosamplerate = pAudioEncoder->getSampleRate();
|
||||
metadata.audiodatarate = pAudioEncoder->getBitrate() / 1000;
|
||||
metadata.audiosamplesize = Core::sampleFormatBitWidth(pAudioEncoder->getSampleFormat());
|
||||
metadata.stereo = pAudioEncoder->getChannelLayout() == Core::ChannelLayout::STEREO ? true : false;
|
||||
|
||||
if (pAudioEncoder->getCodecID() == Core::CodecID::OPUS)
|
||||
{
|
||||
memcpy(opusHead, "OpusHead", 8);
|
||||
opusHead[8] = 1;
|
||||
opusHead[9] = (uint8_t)(metadata.stereo ? 2 : 1);
|
||||
uint32_t sr = (uint32_t)metadata.audiosamplerate;
|
||||
opusHead[12] = (uint8_t)(sr & 0xFF);
|
||||
opusHead[13] = (uint8_t)((sr >> 8) & 0xFF);
|
||||
opusHead[14] = (uint8_t)((sr >> 16) & 0xFF);
|
||||
opusHead[15] = (uint8_t)((sr >> 24) & 0xFF);
|
||||
hasOpus = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
flv_muxer_metadata(_pMuxer, &metadata);
|
||||
|
||||
if (hasOpus)
|
||||
{
|
||||
for (auto& pStream : _streams)
|
||||
{
|
||||
if (pStream->codecID() == Core::CodecID::OPUS)
|
||||
{
|
||||
pStream->muxer(_pMuxer, opusHead, 19, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RTMPMuxer::mux()
|
||||
{
|
||||
drainInput();
|
||||
|
||||
std::sort(_streams.begin(), _streams.end(), [](auto a, auto b) { return *a < *b; });
|
||||
auto& pStream = *_streams.begin();
|
||||
|
||||
Core::Packet::Ptr pPacket = pStream->queue()->dequeue(4000);
|
||||
if (pStream->serialCodeWatcher().isChanged(pPacket->serialCode()))
|
||||
throw Poco::ApplicationException(Poco::format("%s: Stream changed, restart...", name()));
|
||||
|
||||
pStream->setTimeline(pPacket->pts());
|
||||
int rc = pStream->muxer(_pMuxer, pPacket->data(), pPacket->length(), pPacket->pts() / 1000, pPacket->dts() / 1000);
|
||||
if (rc < 0)
|
||||
throw Poco::ProtocolException(Poco::format("%s: mux %s FAIL: %d (len:%u, pts:%Lu, dts:%Lu)", name(),
|
||||
(pPacket->isVideo() ? std::string("video") : std::string("audio")),
|
||||
rc, pPacket->length(), pPacket->pts(), pPacket->dts()));
|
||||
|
||||
#if (TRACE_VIDEO_FRAME_RATE)
|
||||
if (pPacket->isVideo())
|
||||
{
|
||||
count++;
|
||||
uint64_t now = Core::System::timestamp();
|
||||
int64_t delta = now - timeline;
|
||||
if (delta >= 1000000)
|
||||
{
|
||||
timeline = now;
|
||||
std::cout << " ==> " << name() << " fps: " << (double)count / (delta / 1000000) << std::endl;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RTMPMuxer::disconnect()
|
||||
{
|
||||
if (_pClient)
|
||||
{
|
||||
rtmp_client_stop(_pClient);
|
||||
rtmp_client_destroy(_pClient);
|
||||
_pClient = nullptr;
|
||||
}
|
||||
|
||||
Poco::Thread::sleep(100);
|
||||
if (_pMuxer)
|
||||
{
|
||||
flv_muxer_destroy(_pMuxer);
|
||||
_pMuxer = nullptr;
|
||||
}
|
||||
|
||||
if (_socket != socket_invalid)
|
||||
{
|
||||
socket_close(_socket);
|
||||
_socket = socket_invalid;
|
||||
}
|
||||
|
||||
_streams.clear();
|
||||
}
|
||||
|
||||
void RTMPMuxer::drainInput()
|
||||
{
|
||||
uint8_t buffer[2048];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int rc = socket_recv_by_time(_socket, buffer, sizeof(buffer), 0, 0);
|
||||
|
||||
if (SOCKET_TIMEDOUT == rc)
|
||||
break;
|
||||
|
||||
if (0 == rc)
|
||||
throw Poco::IOException(Poco::format("%s: Connection closed by peer", name()));
|
||||
|
||||
if (rc < 0)
|
||||
throw Poco::IOException(Poco::format("%s: socket_recv FAIL: %d", name(), rc));
|
||||
|
||||
if (rtmp_client_input(_pClient, buffer, (size_t)rc) != 0)
|
||||
throw Poco::ProtocolException(Poco::format("%s: RTMP-Client input handle FAIL!", name()));
|
||||
}
|
||||
}
|
||||
|
||||
int RTMPMuxer::clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes)
|
||||
{
|
||||
RTMPMuxer* pPusher = (RTMPMuxer*)param;
|
||||
|
||||
socket_bufvec_t vec[2];
|
||||
socket_setbufvec(vec, 0, (void*)header, len);
|
||||
socket_setbufvec(vec, 1, (void*)data, bytes);
|
||||
|
||||
#if defined(CORRUPT_RTMP_CHUNK_DATA)
|
||||
|
||||
rtmp_corrupt_data(header, len);
|
||||
rtmp_corrupt_data(data, bytes);
|
||||
#endif
|
||||
|
||||
return socket_send_v_all_by_time(pPusher->_socket, vec, bytes > 0 ? 2 : 1, 0, 5000);
|
||||
}
|
||||
|
||||
int RTMPMuxer::muxerHandler(void* param, int type, const void* data, size_t bytes, uint32_t timestamp)
|
||||
{
|
||||
RTMPMuxer* pPusher = (RTMPMuxer*)param;
|
||||
int rc;
|
||||
switch (type)
|
||||
{
|
||||
case FLV_TYPE_SCRIPT:
|
||||
rc = rtmp_client_push_script(pPusher->_pClient, data, bytes, timestamp);
|
||||
break;
|
||||
|
||||
case FLV_TYPE_VIDEO:
|
||||
rc = rtmp_client_push_video(pPusher->_pClient, data, bytes, timestamp);
|
||||
break;
|
||||
|
||||
case FLV_TYPE_AUDIO:
|
||||
rc = rtmp_client_push_audio(pPusher->_pClient, data, bytes, timestamp);
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
const int RTMPMuxer::getCodecID(Core::CodecID id)
|
||||
{
|
||||
for (const auto& m : codecMap)
|
||||
{
|
||||
if (id == m.id)
|
||||
return m.type;
|
||||
}
|
||||
|
||||
throw Poco::NotFoundException(
|
||||
Poco::format("Video::MediaServer: Can't find %s in codec-map", Core::codecIDToString(id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
115
video/mediaserver/RTMPMuxer.h
Normal file
115
video/mediaserver/RTMPMuxer.h
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#ifndef F2B5C47C_73F6_4CFD_A4D6_9C8A1CFB55F5
|
||||
#define F2B5C47C_73F6_4CFD_A4D6_9C8A1CFB55F5
|
||||
/*
|
||||
*******************************************************************************
|
||||
* INCLUDE
|
||||
*******************************************************************************
|
||||
*/
|
||||
#include "../core/Muxer.h"
|
||||
#include "util/ClassAssistant.h"
|
||||
#include <sdk/include/sockutil.h>
|
||||
#include <media-server/librtmp/rtmp-client.h>
|
||||
#include <media-server/libflv/flv-muxer.h>
|
||||
|
||||
/*
|
||||
*******************************************************************************
|
||||
* MACROS
|
||||
*******************************************************************************
|
||||
*/
|
||||
#define TRACE_VIDEO_FRAME_RATE (0)
|
||||
|
||||
namespace Video
|
||||
{
|
||||
namespace MediaServer
|
||||
{
|
||||
/*
|
||||
*******************************************************************************
|
||||
* CLASS DECLARE
|
||||
*******************************************************************************
|
||||
*/
|
||||
class RTMPMuxer: public Core::Muxer
|
||||
{
|
||||
private:
|
||||
class Stream
|
||||
{
|
||||
public:
|
||||
using Ptr = Poco::SharedPtr<Stream>;
|
||||
|
||||
public:
|
||||
Stream(const std::string& name, Core::CodecID codecID, Core::PacketQueue::Ptr pQueue);
|
||||
~Stream();
|
||||
|
||||
public:
|
||||
bool operator<(const Stream& other);
|
||||
|
||||
const std::string name() const;
|
||||
|
||||
Core::PacketQueue::Ptr queue();
|
||||
Core::SerialCodeWatcher& serialCodeWatcher();
|
||||
Core::CodecID codecID() const;
|
||||
|
||||
int muxer(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts);
|
||||
|
||||
PROPERTY_DECLARE(Timeline, uint64_t, 0, public, public);
|
||||
|
||||
private:
|
||||
const std::string _name;
|
||||
Core::CodecID _codecID;
|
||||
Core::PacketQueue::Ptr _pQueue;
|
||||
Core::SerialCodeWatcher _serialCodeWatcher;
|
||||
int (*_pMuxerMethod)(flv_muxer_t*, const void*, size_t, uint32_t, uint32_t);
|
||||
};
|
||||
|
||||
public:
|
||||
RTMPMuxer(const std::string& url);
|
||||
~RTMPMuxer();
|
||||
|
||||
protected:
|
||||
void connect() override;
|
||||
void mux() override;
|
||||
void disconnect() override;
|
||||
|
||||
protected:
|
||||
void drainInput();
|
||||
const int getCodecID(Core::CodecID id);
|
||||
static int clientSend(void* param, const void* header, size_t len, const void* data, size_t bytes);
|
||||
static int muxerHandler(void* param, int type, const void* data, size_t bytes, uint32_t timestamp);
|
||||
|
||||
private:
|
||||
socket_t _socket;
|
||||
rtmp_client_t* _pClient;
|
||||
flv_muxer_t* _pMuxer;
|
||||
std::vector<Stream::Ptr> _streams;
|
||||
#if (TRACE_VIDEO_FRAME_RATE)
|
||||
int count = 0;
|
||||
uint64_t timeline = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
inline bool RTMPMuxer::Stream::operator<(const Stream& other)
|
||||
{
|
||||
return _Timeline < other._Timeline;
|
||||
}
|
||||
|
||||
inline const std::string RTMPMuxer::Stream::name() const
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
inline Core::PacketQueue::Ptr RTMPMuxer::Stream::queue()
|
||||
{
|
||||
return _pQueue;
|
||||
}
|
||||
|
||||
inline Core::SerialCodeWatcher& RTMPMuxer::Stream::serialCodeWatcher()
|
||||
{
|
||||
return _serialCodeWatcher;
|
||||
}
|
||||
|
||||
inline Core::CodecID RTMPMuxer::Stream::codecID() const
|
||||
{
|
||||
return _codecID;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue