#include "input_tssrt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Util::Config *cfgPointer = NULL; std::string baseStreamName; Socket::SRTServer sSock; bool rawMode = false; 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); } } // We use threads here for multiple input pushes, because of the internals of the SRT Library static void callThreadCallbackSRT(void *socknum){ // use the accepted socket as the second parameter Mist::InputTSSRT inp(cfgPointer, *(Socket::SRTConnection *)socknum); inp.setSingular(false); inp.run(); } namespace Mist{ /// Constructor of TS Input /// \arg cfg Util::Config that contains all current configurations. InputTSSRT::InputTSSRT(Util::Config *cfg, Socket::SRTConnection s) : Input(cfg){ rawIdx = INVALID_TRACK_ID; lastRawPacket = 0; bootMSOffsetCalculated = false; assembler.setLive(); capa["name"] = "TSSRT"; capa["desc"] = "This input allows for processing MPEG2-TS-based SRT streams. Use mode=listener " "for push input."; capa["source_match"].append("srt://*"); // These can/may be set to always-on mode capa["always_match"].append("srt://*"); capa["incoming_push_url"] = "srt://$host:$port"; capa["incoming_push_url_match"] = "srt://*"; capa["priority"] = 9; capa["codecs"]["video"].append("H264"); capa["codecs"]["video"].append("HEVC"); capa["codecs"]["video"].append("MPEG2"); capa["codecs"]["audio"].append("AAC"); capa["codecs"]["audio"].append("MP3"); capa["codecs"]["audio"].append("AC3"); capa["codecs"]["audio"].append("MP2"); capa["codecs"]["audio"].append("opus"); capa["codecs"]["passthrough"].append("rawts"); JSON::Value option; option["arg"] = "integer"; option["long"] = "buffer"; option["short"] = "b"; 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 " "available to seek around in, and will automatically be extended to fit whole keyframes as " "well as the minimum duration needed for stable playback."; capa["optional"]["DVR"]["option"] = "--buffer"; 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"; capa["optional"]["raw"]["name"] = "Raw input mode"; capa["optional"]["raw"]["help"] = "Enable raw MPEG-TS passthrough mode"; capa["optional"]["raw"]["option"] = "--raw"; option.null(); option["long"] = "raw"; option["short"] = "R"; option["help"] = "Enable raw MPEG-TS passthrough mode"; config->addOption("raw", option); capa["optional"]["datatrack"]["name"] = "MPEG Data track parser"; capa["optional"]["datatrack"]["help"] = "Which parser to use for data tracks"; capa["optional"]["datatrack"]["type"] = "select"; capa["optional"]["datatrack"]["option"] = "--datatrack"; capa["optional"]["datatrack"]["short"] = "D"; capa["optional"]["datatrack"]["default"] = ""; capa["optional"]["datatrack"]["select"][0u][0u] = ""; capa["optional"]["datatrack"]["select"][0u][1u] = "None / disabled"; capa["optional"]["datatrack"]["select"][1u][0u] = "json"; capa["optional"]["datatrack"]["select"][1u][1u] = "2b size-prepended JSON"; option.null(); option["long"] = "datatrack"; option["short"] = "D"; option["arg"] = "string"; option["default"] = ""; option["help"] = "Which parser to use for data tracks"; config->addOption("datatrack", option); // Setup if we are called form with a thread for push-based input. if (s.connected()){ srtConn = s; streamName = baseStreamName; 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; } InputTSSRT::~InputTSSRT(){} bool InputTSSRT::checkArguments(){ if (config->getString("datatrack") == "json"){ tsStream.setRawDataParser(TS::JSON); } return true; } /// Live Setup of SRT Input. Runs only if we are the "main" thread bool InputTSSRT::preRun(){ rawMode = config->getBool("raw"); if (rawMode){INFO_MSG("Entering raw mode");} if (srtConn.getSocket() == -1){ std::string source = config->getString("input"); standAlone = false; HTTP::URL u(source); INFO_MSG("Parsed url: %s", u.getUrl().c_str()); if (Socket::interpretSRTMode(u) == "listener"){ std::map arguments; HTTP::parseVars(u.args, arguments); sSock = Socket::SRTServer(u.getPort(), u.host, arguments, false); 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{ std::map arguments; HTTP::parseVars(u.args, arguments); size_t connectCnt = 0; do{ srtConn.connect(u.host, u.getPort(), "input", arguments); if (!srtConn){Util::sleep(1000);} ++connectCnt; }while (!srtConn && connectCnt < 10); if (!srtConn){WARN_MSG("Connecting to %s timed out", u.getUrl().c_str());} } } return true; } // Retrieve the next packet to be played from the srt connection. void InputTSSRT::getNext(size_t idx){ thisPacket.null(); bool hasPacket = tsStream.hasPacket(); while (!hasPacket && srtConn && config->is_active){ size_t recvSize = srtConn.RecvNow(); if (recvSize){ if (rawMode){ keepAlive(); rawBuffer.append(srtConn.recvbuf, recvSize); if (rawBuffer.size() >= 1316 && (lastRawPacket == 0 || lastRawPacket != Util::bootMS())){ if (rawIdx == INVALID_TRACK_ID){ rawIdx = meta.addTrack(); meta.setType(rawIdx, "meta"); meta.setCodec(rawIdx, "rawts"); meta.setID(rawIdx, 1); userSelect[rawIdx].reload(streamName, rawIdx, COMM_STATUS_SOURCE); } uint64_t packetTime = Util::bootMS(); thisPacket.genericFill(packetTime, 0, 1, rawBuffer, rawBuffer.size(), 0, 0); lastRawPacket = packetTime; rawBuffer.truncate(0); return; } continue; } 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); } } if (hasPacket){tsStream.getEarliestPacket(thisPacket);} if (!thisPacket){ if (srtConn){ INFO_MSG("Could not getNext TS packet!"); Util::logExitReason(ER_FORMAT_SPECIFIC, "internal TS parser error"); }else{ Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection close"); } return; } tsStream.initializeMetadata(meta); thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid()); if (thisIdx == INVALID_TRACK_ID){getNext(idx);} uint64_t pktTimeWithOffset = thisPacket.getTime() + timeStampOffset; if (lastTimeStamp || timeStampOffset){ uint64_t targetTime = Util::bootMS() - M.getBootMsOffset(); if (targetTime + 5000 < pktTimeWithOffset || targetTime > pktTimeWithOffset + 5000){ INFO_MSG("Timestamp jump " PRETTY_PRINT_MSTIME " -> " PRETTY_PRINT_MSTIME ", compensating.", PRETTY_ARG_MSTIME(targetTime), PRETTY_ARG_MSTIME(pktTimeWithOffset)); timeStampOffset += (targetTime - pktTimeWithOffset); pktTimeWithOffset = thisPacket.getTime() + timeStampOffset; } } if (!bootMSOffsetCalculated){ meta.setBootMsOffset((int64_t)Util::bootMS() - (int64_t)pktTimeWithOffset); bootMSOffsetCalculated = true; } lastTimeStamp = pktTimeWithOffset; thisPacket.setTime(pktTimeWithOffset); thisTime = pktTimeWithOffset; } bool InputTSSRT::openStreamSource(){return true;} void InputTSSRT::streamMainLoop(){ // If we do not have a srtConn here, we are the main thread and should start accepting pushes. if (srtConn.getSocket() == -1){ cfgPointer = config; baseStreamName = streamName; while (config->is_active && sSock.connected()){ Socket::SRTConnection S = sSock.accept(); if (S.connected()){// check if the new connection is valid // spawn a new thread for this connection tthread::thread T(callThreadCallbackSRT, (void *)&S); // detach it, no need to keep track of it anymore T.detach(); HIGH_MSG("Spawned new thread for socket %i", S.getSocket()); } } return; } // If we are here: we have a proper connection (either accepted or pull input) and should start parsing it as such Input::streamMainLoop(); srtConn.close(); } bool InputTSSRT::needsLock(){return false;} void InputTSSRT::setSingular(bool newSingular){singularFlag = newSingular;} void InputTSSRT::connStats(Comms::Connections &statComm){ statComm.setUp(srtConn.dataUp()); statComm.setDown(srtConn.dataDown()); statComm.setPacketCount(srtConn.packetCount()); statComm.setPacketLostCount(srtConn.packetLostCount()); statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount()); } }// namespace Mist