SRT improvements:
- Made SRT support optional - Make build options visible in cmake-gui - Improved generic connection stats for outputs - Added streamid handling configuration for MistInTSSRT - Push input support over SRT - Fixed support for SRT settings in push outputs - Fix parsing of SRT-passed stream names - Fixed hostnames in MistOutTSSRT, fixed PUSH_REWRITE trigger payload - Opus support in TS-SRT - Fixed SRT socket stats, fixed SRT socket address logic, improved SRT socket rolling restart support - Fixed SRT push deny
This commit is contained in:
parent
19199cbff8
commit
0bd5d742f6
19 changed files with 686 additions and 347 deletions
|
@ -893,18 +893,21 @@ namespace Mist{
|
|||
statComm.setCRC(getpid());
|
||||
statComm.setStream(streamName);
|
||||
statComm.setConnector("INPUT:" + capa["name"].asStringRef());
|
||||
statComm.setUp(0);
|
||||
statComm.setDown(streamByteCount());
|
||||
statComm.setTime(now - startTime);
|
||||
statComm.setLastSecond(0);
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
handleLossyStats(statComm);
|
||||
connStats(statComm);
|
||||
}
|
||||
|
||||
statTimer = Util::bootSecs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Input::connStats(Comms::Statistics &statComm){
|
||||
statComm.setUp(0);
|
||||
statComm.setDown(streamByteCount());
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
}
|
||||
|
||||
void Input::realtimeMainLoop(){
|
||||
uint64_t statTimer = 0;
|
||||
|
|
|
@ -69,11 +69,7 @@ namespace Mist{
|
|||
virtual void userOnActive(size_t id);
|
||||
virtual void userOnDisconnect(size_t id);
|
||||
virtual void userLeadOut();
|
||||
|
||||
virtual void handleLossyStats(Comms::Statistics & statComm){}
|
||||
|
||||
virtual bool preventBufferStart() {return false;}
|
||||
|
||||
virtual void connStats(Comms::Statistics & statComm);
|
||||
virtual void parseHeader();
|
||||
bool bufferFrame(size_t track, uint32_t keyNum);
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@ namespace Mist{
|
|||
inputProcess = 0;
|
||||
isFinished = false;
|
||||
|
||||
#ifndef WITH_SRT
|
||||
{
|
||||
pid_t srt_tx = -1;
|
||||
const char *args[] ={"srt-live-transmit", 0};
|
||||
|
@ -199,12 +200,13 @@ namespace Mist{
|
|||
capa["source_match"].append("srt://*");
|
||||
capa["always_match"].append("srt://*");
|
||||
capa["desc"] =
|
||||
capa["desc"].asStringRef() + " SRT support (srt://*) is installed and available.";
|
||||
capa["desc"].asStringRef() + " Non-native SRT support (srt://*) is installed and available.";
|
||||
}else{
|
||||
capa["desc"] = capa["desc"].asStringRef() +
|
||||
" To enable SRT support, please install the srt-live-transmit binary.";
|
||||
" To enable non-native SRT support, please install the srt-live-transmit binary.";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
capa["optional"]["DVR"]["name"] = "Buffer time (ms)";
|
||||
capa["optional"]["DVR"]["help"] =
|
||||
|
@ -534,43 +536,7 @@ namespace Mist{
|
|||
gettingData = true;
|
||||
INFO_MSG("Now receiving UDP data...");
|
||||
}
|
||||
size_t offset = 0;
|
||||
size_t amount = 188-leftData.size();
|
||||
if (leftData.size() && udpCon.data.size() >= amount){
|
||||
//Attempt to re-assemble a packet from the leftovers of last time + current head
|
||||
if (udpCon.data.size() == amount || udpCon.data[amount] == 0x47){
|
||||
VERYHIGH_MSG("Assembled scrap packet");
|
||||
//Success!
|
||||
leftData.append(udpCon.data, amount);
|
||||
liveStream.add(leftData);
|
||||
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
|
||||
offset = amount;
|
||||
leftData.assign(0,0);
|
||||
}
|
||||
//On failure, hope we might live to succeed another day
|
||||
}
|
||||
// Try to read full TS Packets
|
||||
// Watch out! We push here to a global, in order for threads to be able to access it.
|
||||
size_t junk = 0;
|
||||
while (offset < udpCon.data.size()){
|
||||
if (udpCon.data[offset] == 0x47 && (offset+188 >= udpCon.data.size() || udpCon.data[offset+188] == 0x47)){// check for sync byte
|
||||
if (junk){
|
||||
INFO_MSG("%zu bytes of non-sync-byte data received", junk);
|
||||
junk = 0;
|
||||
}
|
||||
if (offset + 188 <= udpCon.data.size()){
|
||||
tsBuf.FromPointer(udpCon.data + offset);
|
||||
liveStream.add(tsBuf);
|
||||
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
|
||||
}else{
|
||||
leftData.assign(udpCon.data + offset, udpCon.data.size() - offset);
|
||||
}
|
||||
offset += 188;
|
||||
}else{
|
||||
++junk;
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
assembler.assemble(liveStream, udpCon.data, udpCon.data.size());
|
||||
}
|
||||
if (!received){
|
||||
Util::sleep(100);
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Mist{
|
|||
void streamMainLoop();
|
||||
void finish();
|
||||
FILE *inFile; ///< The input file with ts data
|
||||
Util::ResizeablePointer leftData;
|
||||
TS::Assembler assembler;
|
||||
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
|
||||
Socket::UDPConnection udpCon;
|
||||
Socket::Connection tcpCon;
|
||||
|
|
|
@ -24,9 +24,17 @@
|
|||
|
||||
Util::Config *cfgPointer = NULL;
|
||||
std::string baseStreamName;
|
||||
Socket::SRTServer sSock;
|
||||
|
||||
void (*oldSignal)(int, siginfo_t *,void *) = 0;
|
||||
|
||||
void signal_handler(int signum, siginfo_t *sigInfo, void *ignore){
|
||||
sSock.close();
|
||||
if (oldSignal){
|
||||
oldSignal(signum, sigInfo, ignore);
|
||||
}
|
||||
}
|
||||
|
||||
/// Global, so that all tracks stay in sync
|
||||
int64_t timeStampOffset = 0;
|
||||
|
||||
// We use threads here for multiple input pushes, because of the internals of the SRT Library
|
||||
static void callThreadCallbackSRT(void *socknum){
|
||||
|
@ -54,8 +62,10 @@ namespace Mist{
|
|||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("MPEG2");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["codecs"][0u][1u].append("MP2");
|
||||
capa["codecs"][0u][1u].append("opus");
|
||||
|
||||
JSON::Value option;
|
||||
option["arg"] = "integer";
|
||||
|
@ -64,6 +74,7 @@ namespace Mist{
|
|||
option["help"] = "DVR buffer time in ms";
|
||||
option["value"].append(50000);
|
||||
config->addOption("bufferTime", option);
|
||||
option.null();
|
||||
capa["optional"]["DVR"]["name"] = "Buffer time (ms)";
|
||||
capa["optional"]["DVR"]["help"] =
|
||||
"The target available buffer time for this live stream, in milliseconds. This is the time "
|
||||
|
@ -73,13 +84,44 @@ namespace Mist{
|
|||
capa["optional"]["DVR"]["type"] = "uint";
|
||||
capa["optional"]["DVR"]["default"] = 50000;
|
||||
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "acceptable";
|
||||
option["short"] = "T";
|
||||
option["help"] = "Acceptable pushed streamids (0 = use streamid as wildcard, 1 = ignore all streamids, 2 = disallow non-matching streamids)";
|
||||
option["value"].append(0);
|
||||
config->addOption("acceptable", option);
|
||||
capa["optional"]["acceptable"]["name"] = "Acceptable pushed streamids";
|
||||
capa["optional"]["acceptable"]["help"] = "What to do with the streamids for incoming pushes, if this is a listener SRT connection";
|
||||
capa["optional"]["acceptable"]["option"] = "--acceptable";
|
||||
capa["optional"]["acceptable"]["short"] = "T";
|
||||
capa["optional"]["acceptable"]["default"] = 0;
|
||||
capa["optional"]["acceptable"]["type"] = "select";
|
||||
capa["optional"]["acceptable"]["select"][0u][0u] = 0;
|
||||
capa["optional"]["acceptable"]["select"][0u][1u] = "Set streamid as wildcard";
|
||||
capa["optional"]["acceptable"]["select"][1u][0u] = 1;
|
||||
capa["optional"]["acceptable"]["select"][1u][1u] = "Ignore all streamids";
|
||||
capa["optional"]["acceptable"]["select"][2u][0u] = 2;
|
||||
capa["optional"]["acceptable"]["select"][2u][1u] = "Disallow non-matching streamid";
|
||||
|
||||
|
||||
// Setup if we are called form with a thread for push-based input.
|
||||
if (s != -1){
|
||||
srtConn = Socket::SRTConnection(s);
|
||||
streamName = baseStreamName;
|
||||
if (srtConn.getStreamName() != ""){streamName += "+" + srtConn.getStreamName();}
|
||||
std::string streamid = srtConn.getStreamName();
|
||||
int64_t acc = config->getInteger("acceptable");
|
||||
if (acc == 0){
|
||||
if (streamid.size()){streamName += "+" + streamid;}
|
||||
}else if(acc == 2){
|
||||
if (streamName != streamid){
|
||||
FAIL_MSG("Stream ID '%s' does not match stream name, push blocked", streamid.c_str());
|
||||
srtConn.close();
|
||||
}
|
||||
}
|
||||
Util::setStreamName(streamName);
|
||||
}
|
||||
lastTimeStamp = 0;
|
||||
timeStampOffset = 0;
|
||||
singularFlag = true;
|
||||
}
|
||||
|
||||
|
@ -96,9 +138,27 @@ namespace Mist{
|
|||
INFO_MSG("Parsed url: %s", u.getUrl().c_str());
|
||||
if (Socket::interpretSRTMode(u) == "listener"){
|
||||
sSock = Socket::SRTServer(u.getPort(), u.host, false);
|
||||
config->registerSRTSockPtr(&sSock);
|
||||
struct sigaction new_action;
|
||||
struct sigaction cur_action;
|
||||
new_action.sa_sigaction = signal_handler;
|
||||
sigemptyset(&new_action.sa_mask);
|
||||
new_action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGINT, &new_action, &cur_action);
|
||||
if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){
|
||||
if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");}
|
||||
oldSignal = cur_action.sa_sigaction;
|
||||
}
|
||||
sigaction(SIGHUP, &new_action, &cur_action);
|
||||
if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){
|
||||
if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");}
|
||||
oldSignal = cur_action.sa_sigaction;
|
||||
}
|
||||
sigaction(SIGTERM, &new_action, &cur_action);
|
||||
if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){
|
||||
if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");}
|
||||
oldSignal = cur_action.sa_sigaction;
|
||||
}
|
||||
}else{
|
||||
INFO_MSG("A");
|
||||
std::map<std::string, std::string> arguments;
|
||||
HTTP::parseVars(u.args, arguments);
|
||||
size_t connectCnt = 0;
|
||||
|
@ -117,34 +177,12 @@ namespace Mist{
|
|||
void inputTSSRT::getNext(size_t idx){
|
||||
thisPacket.null();
|
||||
bool hasPacket = tsStream.hasPacket();
|
||||
bool firstloop = true;
|
||||
while (!hasPacket && srtConn.connected() && config->is_active){
|
||||
firstloop = false;
|
||||
// Receive data from the socket. SRT Sockets handle some internal timing as well, based on the provided settings.
|
||||
leftBuffer.append(srtConn.RecvNow());
|
||||
if (leftBuffer.size()){
|
||||
size_t offset = 0;
|
||||
size_t garbage = 0;
|
||||
while ((offset + 188) < leftBuffer.size()){
|
||||
if (leftBuffer[offset] != 0x47){
|
||||
++garbage;
|
||||
if (garbage % 100 == 0){INFO_MSG("Accumulated %zu bytes of garbage", garbage);}
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
if (garbage != 0){
|
||||
WARN_MSG("Thrown away %zu bytes of garbage data", garbage);
|
||||
garbage = 0;
|
||||
}
|
||||
if (offset + 188 <= leftBuffer.size()){
|
||||
tsBuf.FromPointer(leftBuffer.data() + offset);
|
||||
tsStream.parse(tsBuf, 0);
|
||||
offset += 188;
|
||||
}
|
||||
}
|
||||
leftBuffer.erase(0, offset);
|
||||
hasPacket = tsStream.hasPacket();
|
||||
}else if (srtConn.connected()){
|
||||
while (!hasPacket && srtConn && config->is_active){
|
||||
|
||||
size_t recvSize = srtConn.RecvNow();
|
||||
if (recvSize){
|
||||
if (assembler.assemble(tsStream, srtConn.recvbuf, recvSize, true)){hasPacket = tsStream.hasPacket();}
|
||||
}else if (srtConn){
|
||||
// This should not happen as the SRT socket is read blocking and won't return until there is
|
||||
// data. But if it does, wait before retry
|
||||
Util::sleep(10);
|
||||
|
@ -153,7 +191,12 @@ namespace Mist{
|
|||
if (hasPacket){tsStream.getEarliestPacket(thisPacket);}
|
||||
|
||||
if (!thisPacket){
|
||||
INFO_MSG("Could not getNext TS packet!");
|
||||
if (srtConn){
|
||||
INFO_MSG("Could not getNext TS packet!");
|
||||
Util::logExitReason("internal TS parser error");
|
||||
}else{
|
||||
Util::logExitReason("SRT connection close");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -208,7 +251,10 @@ namespace Mist{
|
|||
|
||||
void inputTSSRT::setSingular(bool newSingular){singularFlag = newSingular;}
|
||||
|
||||
void inputTSSRT::handleLossyStats(Comms::Statistics &statComm){
|
||||
void inputTSSRT::connStats(Comms::Statistics &statComm){
|
||||
statComm.setUp(srtConn.dataUp());
|
||||
statComm.setDown(srtConn.dataDown());
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
statComm.setPacketCount(srtConn.packetCount());
|
||||
statComm.setPacketLostCount(srtConn.packetLostCount());
|
||||
statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount());
|
||||
|
|
|
@ -8,13 +8,17 @@
|
|||
#include <string>
|
||||
|
||||
namespace Mist{
|
||||
/// This class contains all functions needed to implement TS Input
|
||||
|
||||
class inputTSSRT : public Input{
|
||||
public:
|
||||
inputTSSRT(Util::Config *cfg, SRTSOCKET s = -1);
|
||||
~inputTSSRT();
|
||||
void setSingular(bool newSingular);
|
||||
virtual bool needsLock();
|
||||
virtual std::string getConnectedBinHost(){
|
||||
if (srtConn){return srtConn.getBinHost();}
|
||||
return Input::getConnectedBinHost();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Private Functions
|
||||
|
@ -22,7 +26,6 @@ namespace Mist{
|
|||
bool preRun();
|
||||
virtual void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
virtual bool needHeader(){return false;}
|
||||
virtual bool preventBufferStart(){return srtConn.getSocket() == -1;}
|
||||
virtual bool isSingular(){return singularFlag;}
|
||||
virtual bool isThread(){return !singularFlag;}
|
||||
|
||||
|
@ -31,17 +34,14 @@ namespace Mist{
|
|||
void streamMainLoop();
|
||||
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
|
||||
TS::Packet tsBuf;
|
||||
std::string leftBuffer;
|
||||
TS::Assembler assembler;
|
||||
int64_t timeStampOffset;
|
||||
uint64_t lastTimeStamp;
|
||||
|
||||
Socket::SRTServer sSock;
|
||||
Socket::SRTConnection srtConn;
|
||||
bool singularFlag;
|
||||
size_t tmpIdx;
|
||||
virtual size_t streamByteCount(){
|
||||
return srtConn.dataDown();
|
||||
}; // For live streams: to update the stats with correct values.
|
||||
virtual void handleLossyStats(Comms::Statistics &statComm);
|
||||
virtual void connStats(Comms::Statistics &statComm);
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
|
@ -5,20 +5,51 @@
|
|||
#include <mist/socket_srt.h>
|
||||
#include <mist/util.h>
|
||||
|
||||
int spawnForked(Socket::SRTConnection &S){
|
||||
int fds[2];
|
||||
pipe(fds);
|
||||
Socket::Connection Sconn(fds[0], fds[1]);
|
||||
Socket::SRTServer server_socket;
|
||||
static uint64_t sockCount = 0;
|
||||
|
||||
mistOut tmp(Sconn, S.getSocket());
|
||||
return tmp.run();
|
||||
void (*oldSignal)(int, siginfo_t *,void *) = 0;
|
||||
void signal_handler(int signum, siginfo_t *sigInfo, void *ignore){
|
||||
server_socket.close();
|
||||
if (oldSignal){
|
||||
oldSignal(signum, sigInfo, ignore);
|
||||
}
|
||||
}
|
||||
|
||||
void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){
|
||||
HIGH_MSG("USR1 received - triggering rolling restart");
|
||||
Util::Config::is_restarting = true;
|
||||
Util::logExitReason("signal USR1");
|
||||
Util::Config::is_active = false;
|
||||
if (!sockCount){
|
||||
INFO_MSG("USR1 received - triggering rolling restart (no connections active)");
|
||||
Util::Config::is_restarting = true;
|
||||
Util::logExitReason("signal USR1, no connections");
|
||||
server_socket.close();
|
||||
Util::Config::is_active = false;
|
||||
}else{
|
||||
INFO_MSG("USR1 received - triggering rolling restart when connection count reaches zero");
|
||||
Util::Config::is_restarting = true;
|
||||
Util::logExitReason("signal USR1, after disconnect wait");
|
||||
}
|
||||
}
|
||||
|
||||
// Callback for SRT-serving threads
|
||||
static void callThreadCallbackSRT(void *srtPtr){
|
||||
sockCount++;
|
||||
Socket::SRTConnection & srtSock = *(Socket::SRTConnection*)srtPtr;
|
||||
int fds[2];
|
||||
pipe(fds);
|
||||
Socket::Connection Sconn(fds[0], fds[1]);
|
||||
HIGH_MSG("Started thread for socket %i", srtSock.getSocket());
|
||||
mistOut tmp(Sconn,srtSock);
|
||||
tmp.run();
|
||||
HIGH_MSG("Closing thread for socket %i", srtSock.getSocket());
|
||||
Sconn.close();
|
||||
srtSock.close();
|
||||
delete &srtSock;
|
||||
sockCount--;
|
||||
if (!sockCount && Util::Config::is_restarting){
|
||||
server_socket.close();
|
||||
Util::Config::is_active = false;
|
||||
INFO_MSG("Last active connection closed; triggering rolling restart now!");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
@ -41,15 +72,56 @@ int main(int argc, char *argv[]){
|
|||
new_action.sa_flags = 0;
|
||||
sigaction(SIGUSR1, &new_action, NULL);
|
||||
}
|
||||
mistOut::listener(conf, spawnForked);
|
||||
if (conf.is_restarting && Socket::checkTrueSocket(0)){
|
||||
INFO_MSG("Reloading input while re-using server socket");
|
||||
if (conf.getInteger("port") && conf.getString("interface").size()){
|
||||
server_socket = Socket::SRTServer(conf.getInteger("port"), conf.getString("interface"), false, "output");
|
||||
}
|
||||
if (!server_socket.connected()){
|
||||
DEVEL_MSG("Failure to open socket");
|
||||
return 1;
|
||||
}
|
||||
struct sigaction new_action;
|
||||
struct sigaction cur_action;
|
||||
new_action.sa_sigaction = signal_handler;
|
||||
sigemptyset(&new_action.sa_mask);
|
||||
new_action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGINT, &new_action, &cur_action);
|
||||
if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){
|
||||
if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");}
|
||||
oldSignal = cur_action.sa_sigaction;
|
||||
}
|
||||
sigaction(SIGHUP, &new_action, &cur_action);
|
||||
if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){
|
||||
if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");}
|
||||
oldSignal = cur_action.sa_sigaction;
|
||||
}
|
||||
sigaction(SIGTERM, &new_action, &cur_action);
|
||||
if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){
|
||||
if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");}
|
||||
oldSignal = cur_action.sa_sigaction;
|
||||
}
|
||||
Util::Procs::socketList.insert(server_socket.getSocket());
|
||||
while (conf.is_active && server_socket.connected()){
|
||||
Socket::SRTConnection S = server_socket.accept(false, "output");
|
||||
if (S.connected()){// check if the new connection is valid
|
||||
// spawn a new thread for this connection
|
||||
tthread::thread T(callThreadCallbackSRT, (void *)new Socket::SRTConnection(S));
|
||||
// detach it, no need to keep track of it anymore
|
||||
T.detach();
|
||||
}else{
|
||||
Util::sleep(10); // sleep 10ms
|
||||
}
|
||||
}
|
||||
Util::Procs::socketList.erase(server_socket.getSocket());
|
||||
server_socket.close();
|
||||
if (conf.is_restarting){
|
||||
INFO_MSG("Reloading input...");
|
||||
execvp(argv[0], argv);
|
||||
FAIL_MSG("Error reloading: %s", strerror(errno));
|
||||
}
|
||||
}else{
|
||||
Socket::Connection S(fileno(stdout), fileno(stdin));
|
||||
mistOut tmp(S, -1);
|
||||
Socket::SRTConnection tmpSock;
|
||||
mistOut tmp(S, tmpSock);
|
||||
return tmp.run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1686,9 +1686,7 @@ namespace Mist{
|
|||
statComm.setCRC(crc);
|
||||
statComm.setStream(streamName);
|
||||
statComm.setConnector(getStatsName());
|
||||
statComm.setUp(myConn.dataUp());
|
||||
statComm.setDown(myConn.dataDown());
|
||||
statComm.setTime(now - myConn.connTime());
|
||||
connStats(now, statComm);
|
||||
statComm.setLastSecond(thisPacket ? thisPacket.getTime() : 0);
|
||||
statComm.setPid(getpid());
|
||||
|
||||
|
@ -1722,6 +1720,12 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
|
||||
void Output::connStats(uint64_t now, Comms::Statistics &statComm){
|
||||
statComm.setUp(myConn.dataUp());
|
||||
statComm.setDown(myConn.dataDown());
|
||||
statComm.setTime(now - myConn.connTime());
|
||||
}
|
||||
|
||||
bool Output::dropPushTrack(uint32_t trackId, const std::string & dropReason){
|
||||
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
|
||||
if (it->second.getTrack() == trackId){
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace Mist{
|
|||
/// This function is called whenever a packet is ready for sending.
|
||||
/// Inside it, thisPacket is guaranteed to contain a valid packet.
|
||||
virtual void sendNext(){}// REQUIRED! Others are optional.
|
||||
virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason);
|
||||
virtual bool dropPushTrack(uint32_t trackId, const std::string &dropReason);
|
||||
bool getKeyFrame();
|
||||
bool prepareNext();
|
||||
virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true);
|
||||
|
@ -103,10 +103,10 @@ namespace Mist{
|
|||
uint64_t lastStats; ///< Time of last sending of stats.
|
||||
|
||||
std::set<sortedPageInfo> buffer; ///< A sorted list of next-to-be-loaded packets.
|
||||
bool sought; ///< If a seek has been done, this is set to true. Used for seeking on
|
||||
///< prepareNext().
|
||||
bool sought; ///< If a seek has been done, this is set to true. Used for seeking on
|
||||
///< prepareNext().
|
||||
std::string prevHost; ///< Old value for getConnectedBinHost, for caching
|
||||
protected: // these are to be messed with by child classes
|
||||
protected: // these are to be messed with by child classes
|
||||
virtual bool inlineRestartCapable() const{
|
||||
return false;
|
||||
}///< True if the output is capable of restarting mid-stream. This is used for swapping recording files
|
||||
|
@ -122,6 +122,8 @@ namespace Mist{
|
|||
virtual std::string getStatsName();
|
||||
virtual bool hasSessionIDs(){return false;}
|
||||
|
||||
virtual void connStats(uint64_t now, Comms::Statistics &statComm);
|
||||
|
||||
std::set<size_t> getSupportedTracks(const std::string &type = "") const;
|
||||
|
||||
inline virtual bool keepGoing(){return config->is_active && myConn;}
|
||||
|
|
|
@ -77,6 +77,7 @@ namespace Mist{
|
|||
capa["push_urls"].append("/*.ts");
|
||||
capa["push_urls"].append("ts-exec:*");
|
||||
|
||||
#ifndef WITH_SRT
|
||||
{
|
||||
pid_t srt_tx = -1;
|
||||
const char *args[] ={"srt-live-transmit", 0};
|
||||
|
@ -84,13 +85,14 @@ namespace Mist{
|
|||
if (srt_tx > 1){
|
||||
capa["push_urls"].append("srt://*");
|
||||
capa["desc"] = capa["desc"].asStringRef() +
|
||||
". SRT push output support (srt://*) is installed and available.";
|
||||
". Non-native SRT push output support (srt://*) is installed and available.";
|
||||
}else{
|
||||
capa["desc"] =
|
||||
capa["desc"].asStringRef() +
|
||||
". To enable SRT push output support, please install the srt-live-transmit binary.";
|
||||
". To enable non-native SRT push output support, please install the srt-live-transmit binary.";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
JSON::Value opt;
|
||||
opt["arg"] = "string";
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
#include "mist/socket_srt.h"
|
||||
#include <mist/socket_srt.h>
|
||||
#include "output_tssrt.h"
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/url.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/triggers.h>
|
||||
|
||||
namespace Mist{
|
||||
OutTSSRT::OutTSSRT(Socket::Connection &conn, SRTSOCKET _srtSock) : TSOutput(conn){
|
||||
OutTSSRT::OutTSSRT(Socket::Connection &conn, Socket::SRTConnection & _srtSock) : TSOutput(conn), srtConn(_srtSock){
|
||||
// NOTE: conn is useless for SRT, as it uses a different socket type.
|
||||
sendRepeatingHeaders = 500; // PAT/PMT every 500ms (DVB spec)
|
||||
streamName = config->getString("streamname");
|
||||
Util::setStreamName(streamName);
|
||||
pushOut = false;
|
||||
std::string tracks;
|
||||
// Push output configuration
|
||||
if (config->getString("target").size()){
|
||||
HTTP::URL target(config->getString("target"));
|
||||
target = HTTP::URL(config->getString("target"));
|
||||
if (target.protocol != "srt"){
|
||||
FAIL_MSG("Target %s must begin with srt://, aborting", target.getUrl().c_str());
|
||||
onFail("Invalid srt target: doesn't start with srt://", true);
|
||||
|
@ -25,29 +28,108 @@ namespace Mist{
|
|||
return;
|
||||
}
|
||||
pushOut = true;
|
||||
if (targetParams.count("tracks")){tracks = targetParams["tracks"];}
|
||||
std::map<std::string, std::string> arguments;
|
||||
HTTP::parseVars(target.args, arguments);
|
||||
for (std::map<std::string, std::string>::iterator it = arguments.begin(); it != arguments.end(); ++it){
|
||||
targetParams[it->first] = it->second;
|
||||
}
|
||||
size_t connectCnt = 0;
|
||||
do{
|
||||
srtConn.connect(target.host, target.getPort(), "output");
|
||||
if (!srtConn){Util::sleep(1000);}
|
||||
srtConn.connect(target.host, target.getPort(), "output", targetParams);
|
||||
if (!srtConn){
|
||||
Util::sleep(1000);
|
||||
}else{
|
||||
INFO_MSG("Connect success on attempt %zu", connectCnt+1);
|
||||
break;
|
||||
}
|
||||
++connectCnt;
|
||||
}while (!srtConn && connectCnt < 10);
|
||||
}while (!srtConn && connectCnt < 5);
|
||||
wantRequest = false;
|
||||
parseData = true;
|
||||
initialize();
|
||||
}else{
|
||||
// Pull output configuration, In this case we have an srt connection in the second constructor parameter.
|
||||
srtConn = Socket::SRTConnection(_srtSock);
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
|
||||
// Handle override / append of streamname options
|
||||
std::string sName = srtConn.getStreamName();
|
||||
if (sName != ""){
|
||||
streamName = sName;
|
||||
HIGH_MSG("Requesting stream %s", streamName.c_str());
|
||||
Util::sanitizeName(streamName);
|
||||
Util::setStreamName(streamName);
|
||||
}
|
||||
|
||||
int64_t accTypes = config->getInteger("acceptable");
|
||||
if (accTypes == 0){//Allow both directions
|
||||
srtConn.setBlocking(false);
|
||||
//Try to read the socket 10 times. If any reads succeed, assume they are pushing in
|
||||
size_t retries = 60;
|
||||
while (!accTypes && srtConn && retries){
|
||||
size_t recvSize = srtConn.Recv();
|
||||
if (recvSize){
|
||||
accTypes = 2;
|
||||
INFO_MSG("Connection put into ingest mode");
|
||||
assembler.assemble(tsIn, srtConn.recvbuf, recvSize, true);
|
||||
}else{
|
||||
Util::sleep(50);
|
||||
}
|
||||
--retries;
|
||||
}
|
||||
//If not, assume they are receiving.
|
||||
if (!accTypes){
|
||||
accTypes = 1;
|
||||
INFO_MSG("Connection put into egress mode");
|
||||
}
|
||||
}
|
||||
if (accTypes == 1){// Only allow outgoing
|
||||
srtConn.setBlocking(true);
|
||||
srtConn.direction = "output";
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
initialize();
|
||||
}else if (accTypes == 2){//Only allow incoming
|
||||
srtConn.setBlocking(false);
|
||||
srtConn.direction = "input";
|
||||
if (Triggers::shouldTrigger("PUSH_REWRITE")){
|
||||
HTTP::URL reqUrl;
|
||||
reqUrl.protocol = "srt";
|
||||
reqUrl.port = config->getString("port");
|
||||
reqUrl.host = config->getString("interface");
|
||||
reqUrl.args = "streamid="+Encodings::URL::encode(sName);
|
||||
std::string payload = reqUrl.getUrl() + "\n" + getConnectedHost();
|
||||
std::string newUrl = "";
|
||||
Triggers::doTrigger("PUSH_REWRITE", payload, "", false, newUrl);
|
||||
if (!newUrl.size()){
|
||||
FAIL_MSG("Push from %s to URL %s rejected - PUSH_REWRITE trigger blanked the URL",
|
||||
getConnectedHost().c_str(), reqUrl.getUrl().c_str());
|
||||
Util::logExitReason(
|
||||
"Push from %s to URL %s rejected - PUSH_REWRITE trigger blanked the URL",
|
||||
getConnectedHost().c_str(), reqUrl.getUrl().c_str());
|
||||
onFinish();
|
||||
return;
|
||||
}
|
||||
reqUrl = HTTP::URL(newUrl);
|
||||
if (reqUrl.args.size()){
|
||||
std::map<std::string, std::string> args;
|
||||
HTTP::parseVars(reqUrl.args, args);
|
||||
if (args.count("streamid")){
|
||||
streamName = args["streamid"];
|
||||
Util::sanitizeName(streamName);
|
||||
Util::setStreamName(streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
myConn.setHost(srtConn.remotehost);
|
||||
if (!allowPush("")){
|
||||
onFinish();
|
||||
srtConn.close();
|
||||
return;
|
||||
}
|
||||
parseData = false;
|
||||
wantRequest = true;
|
||||
}
|
||||
|
||||
}
|
||||
initialize();
|
||||
lastTimeStamp = 0;
|
||||
timeStampOffset = 0;
|
||||
}
|
||||
|
||||
OutTSSRT::~OutTSSRT(){}
|
||||
|
@ -58,13 +140,29 @@ namespace Mist{
|
|||
capa["friendly"] = "TS over SRT";
|
||||
capa["desc"] = "Real time streaming of TS data over SRT";
|
||||
capa["deps"] = "";
|
||||
capa["required"]["streamname"]["name"] = "Stream";
|
||||
capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add "
|
||||
"this protocol multiple times using different ports, "
|
||||
"or use the streamid option on the srt connection";
|
||||
capa["required"]["streamname"]["type"] = "str";
|
||||
capa["required"]["streamname"]["option"] = "--stream";
|
||||
capa["required"]["streamname"]["short"] = "s";
|
||||
|
||||
capa["optional"]["streamname"]["name"] = "Stream";
|
||||
capa["optional"]["streamname"]["help"] = "What streamname to serve if no streamid is given by the other end of the connection";
|
||||
capa["optional"]["streamname"]["type"] = "str";
|
||||
capa["optional"]["streamname"]["option"] = "--stream";
|
||||
capa["optional"]["streamname"]["short"] = "s";
|
||||
capa["optional"]["streamname"]["default"] = "";
|
||||
|
||||
capa["optional"]["acceptable"]["name"] = "Acceptable connection types";
|
||||
capa["optional"]["acceptable"]["help"] =
|
||||
"Whether to allow only incoming pushes (2), only outgoing pulls (1), or both (0, default)";
|
||||
capa["optional"]["acceptable"]["option"] = "--acceptable";
|
||||
capa["optional"]["acceptable"]["short"] = "T";
|
||||
capa["optional"]["acceptable"]["default"] = 0;
|
||||
capa["optional"]["acceptable"]["type"] = "select";
|
||||
capa["optional"]["acceptable"]["select"][0u][0u] = 0;
|
||||
capa["optional"]["acceptable"]["select"][0u][1u] =
|
||||
"Allow both incoming and outgoing connections";
|
||||
capa["optional"]["acceptable"]["select"][1u][0u] = 1;
|
||||
capa["optional"]["acceptable"]["select"][1u][1u] = "Allow only outgoing connections";
|
||||
capa["optional"]["acceptable"]["select"][2u][0u] = 2;
|
||||
capa["optional"]["acceptable"]["select"][2u][1u] = "Allow only incoming connections";
|
||||
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("MPEG2");
|
||||
|
@ -72,6 +170,7 @@ namespace Mist{
|
|||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["codecs"][0u][1u].append("MP2");
|
||||
capa["codecs"][0u][1u].append("opus");
|
||||
cfg->addConnectorOptions(8889, capa);
|
||||
config = cfg;
|
||||
capa["push_urls"].append("srt://*");
|
||||
|
@ -84,29 +183,88 @@ namespace Mist{
|
|||
cfg->addOption("target", opt);
|
||||
}
|
||||
|
||||
// Buffer internally in the class, and send once we have over 1000 bytes of data.
|
||||
// Buffers TS packets and sends after 7 are buffered.
|
||||
void OutTSSRT::sendTS(const char *tsData, size_t len){
|
||||
if (packetBuffer.size() >= 1000){
|
||||
srtConn.SendNow(packetBuffer);
|
||||
if (!srtConn){
|
||||
// Allow for proper disconnect
|
||||
parseData = false;
|
||||
}
|
||||
packetBuffer.clear();
|
||||
}
|
||||
packetBuffer.append(tsData, len);
|
||||
if (packetBuffer.size() >= 1316){//7 whole TS packets
|
||||
if (!srtConn){
|
||||
if (config->getString("target").size()){
|
||||
INFO_MSG("Reconnecting...");
|
||||
srtConn.connect(target.host, target.getPort(), "output", targetParams);
|
||||
if (!srtConn){Util::sleep(500);}
|
||||
}else{
|
||||
Util::logExitReason("SRT connection closed");
|
||||
myConn.close();
|
||||
parseData = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (srtConn){
|
||||
srtConn.SendNow(packetBuffer, packetBuffer.size());
|
||||
if (!srtConn){
|
||||
if (!config->getString("target").size()){
|
||||
Util::logExitReason("SRT connection closed");
|
||||
myConn.close();
|
||||
parseData = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
packetBuffer.assign(0,0);
|
||||
}
|
||||
}
|
||||
|
||||
bool OutTSSRT::setAlternateConnectionStats(Comms::Statistics &statComm){
|
||||
void OutTSSRT::requestHandler(){
|
||||
size_t recvSize = srtConn.Recv();
|
||||
if (!recvSize){
|
||||
if (!srtConn){
|
||||
myConn.close();
|
||||
wantRequest = false;
|
||||
}else{
|
||||
Util::sleep(50);
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastRecv = Util::bootSecs();
|
||||
if (!assembler.assemble(tsIn, srtConn.recvbuf, recvSize, true)){return;}
|
||||
while (tsIn.hasPacket()){
|
||||
tsIn.getEarliestPacket(thisPacket);
|
||||
if (!thisPacket){
|
||||
INFO_MSG("Could not get TS packet");
|
||||
myConn.close();
|
||||
wantRequest = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tsIn.initializeMetadata(meta);
|
||||
size_t thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid());
|
||||
if (thisIdx == INVALID_TRACK_ID){return;}
|
||||
if (!userSelect.count(thisIdx)){
|
||||
userSelect[thisIdx].reload(streamName, thisIdx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
}
|
||||
|
||||
uint64_t adjustTime = thisPacket.getTime() + timeStampOffset;
|
||||
if (lastTimeStamp || timeStampOffset){
|
||||
if (lastTimeStamp + 5000 < adjustTime || lastTimeStamp > adjustTime + 5000){
|
||||
INFO_MSG("Timestamp jump " PRETTY_PRINT_MSTIME " -> " PRETTY_PRINT_MSTIME ", compensating.",
|
||||
PRETTY_ARG_MSTIME(lastTimeStamp), PRETTY_ARG_MSTIME(adjustTime));
|
||||
timeStampOffset += (lastTimeStamp - adjustTime);
|
||||
adjustTime = thisPacket.getTime() + timeStampOffset;
|
||||
}
|
||||
}
|
||||
lastTimeStamp = adjustTime;
|
||||
thisPacket.setTime(adjustTime);
|
||||
bufferLivePacket(thisPacket);
|
||||
}
|
||||
}
|
||||
|
||||
void OutTSSRT::connStats(uint64_t now, Comms::Statistics &statComm){
|
||||
if (!srtConn){return;}
|
||||
statComm.setUp(srtConn.dataUp());
|
||||
statComm.setDown(srtConn.dataDown());
|
||||
statComm.setTime(Util::bootSecs() - srtConn.connTime());
|
||||
return true;
|
||||
}
|
||||
|
||||
void OutTSSRT::handleLossyStats(Comms::Statistics &statComm){
|
||||
statComm.setTime(now - srtConn.connTime());
|
||||
statComm.setPacketCount(srtConn.packetCount());
|
||||
statComm.setPacketLostCount(srtConn.packetLostCount());
|
||||
statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount());
|
||||
}
|
||||
|
||||
}// namespace Mist
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
#include "output_ts_base.h"
|
||||
#include <mist/ts_stream.h>
|
||||
|
||||
#include <mist/socket_srt.h>
|
||||
|
||||
namespace Mist{
|
||||
class OutTSSRT : public TSOutput{
|
||||
public:
|
||||
OutTSSRT(Socket::Connection &conn, SRTSOCKET _srtSock);
|
||||
OutTSSRT(Socket::Connection &conn, Socket::SRTConnection & _srtSock);
|
||||
~OutTSSRT();
|
||||
|
||||
static bool listenMode(){return !(config->getString("target").size());}
|
||||
|
@ -14,19 +13,23 @@ namespace Mist{
|
|||
static void init(Util::Config *cfg);
|
||||
void sendTS(const char *tsData, size_t len = 188);
|
||||
bool isReadyForPlay(){return true;}
|
||||
|
||||
virtual void requestHandler();
|
||||
protected:
|
||||
// Stats handling
|
||||
virtual bool setAlternateConnectionStats(Comms::Statistics &statComm);
|
||||
virtual void handleLossyStats(Comms::Statistics &statComm);
|
||||
virtual void connStats(uint64_t now, Comms::Statistics &statComm);
|
||||
virtual std::string getConnectedHost(){return srtConn.remotehost;}
|
||||
virtual std::string getConnectedBinHost(){return srtConn.getBinHost();}
|
||||
|
||||
private:
|
||||
HTTP::URL target;
|
||||
int64_t timeStampOffset;
|
||||
uint64_t lastTimeStamp;
|
||||
bool pushOut;
|
||||
std::string packetBuffer;
|
||||
Util::ResizeablePointer packetBuffer;
|
||||
Socket::UDPConnection pushSock;
|
||||
TS::Stream tsIn;
|
||||
TS::Assembler assembler;
|
||||
|
||||
Socket::SRTConnection srtConn;
|
||||
Socket::SRTConnection & srtConn;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue