p26001-store-acs-soc-fw-app/video/mediaserver/RTMPMuxer.cpp
2026-09-28 21:33:04 +08:00

369 lines
12 KiB
C++

/*
*******************************************************************************
* 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)));
}
}
}