TSSRT Support
This commit is contained in:
parent
974380ab30
commit
19199cbff8
17 changed files with 1471 additions and 15 deletions
|
@ -32,6 +32,9 @@
|
|||
#define STAT_CLI_BPS_UP 256
|
||||
#define STAT_CLI_CRC 512
|
||||
#define STAT_CLI_SESSID 1024
|
||||
#define STAT_CLI_PKTCOUNT 2048
|
||||
#define STAT_CLI_PKTLOST 4096
|
||||
#define STAT_CLI_PKTRETRANSMIT 8192
|
||||
#define STAT_CLI_ALL 0xFFFF
|
||||
// These are used to store "totals" field requests in a bitfield for speedup.
|
||||
#define STAT_TOT_CLIENTS 1
|
||||
|
@ -713,6 +716,9 @@ void Controller::statSession::wipeOld(uint64_t cutOff){
|
|||
if (it->log.size() == 1){
|
||||
wipedDown += it->log.begin()->second.down;
|
||||
wipedUp += it->log.begin()->second.up;
|
||||
wipedPktCount += it->log.begin()->second.pktCount;
|
||||
wipedPktLost += it->log.begin()->second.pktCount;
|
||||
wipedPktRetransmit += it->log.begin()->second.pktRetransmit;
|
||||
}
|
||||
it->log.erase(it->log.begin());
|
||||
}
|
||||
|
@ -800,6 +806,9 @@ void Controller::statSession::dropSession(const Controller::sessIndex &index){
|
|||
lastSec = 0;
|
||||
wipedUp = 0;
|
||||
wipedDown = 0;
|
||||
wipedPktCount = 0;
|
||||
wipedPktLost = 0;
|
||||
wipedPktRetransmit = 0;
|
||||
oldConns.clear();
|
||||
sessionType = SESS_UNSET;
|
||||
}
|
||||
|
@ -819,6 +828,9 @@ Controller::statSession::statSession(){
|
|||
sync = 1;
|
||||
wipedUp = 0;
|
||||
wipedDown = 0;
|
||||
wipedPktCount = 0;
|
||||
wipedPktLost = 0;
|
||||
wipedPktRetransmit = 0;
|
||||
sessionType = SESS_UNSET;
|
||||
noBWCount = 0;
|
||||
}
|
||||
|
@ -1014,6 +1026,97 @@ uint64_t Controller::statSession::getUp(){
|
|||
return retVal;
|
||||
}
|
||||
|
||||
uint64_t Controller::statSession::getPktCount(uint64_t t){
|
||||
uint64_t retVal = wipedPktCount;
|
||||
if (oldConns.size()){
|
||||
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
|
||||
if (it->hasDataFor(t)){retVal += it->getDataFor(t).pktCount;}
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
if (it->second.hasDataFor(t)){retVal += it->second.getDataFor(t).pktCount;}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// Returns the cumulative uploaded bytes for this session at timestamp t.
|
||||
uint64_t Controller::statSession::getPktCount(){
|
||||
uint64_t retVal = wipedPktCount;
|
||||
if (oldConns.size()){
|
||||
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
|
||||
if (it->log.size()){retVal += it->log.rbegin()->second.pktCount;}
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
if (it->second.log.size()){retVal += it->second.log.rbegin()->second.pktCount;}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
uint64_t Controller::statSession::getPktLost(uint64_t t){
|
||||
uint64_t retVal = wipedPktLost;
|
||||
if (oldConns.size()){
|
||||
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
|
||||
if (it->hasDataFor(t)){retVal += it->getDataFor(t).pktLost;}
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
if (it->second.hasDataFor(t)){retVal += it->second.getDataFor(t).pktLost;}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// Returns the cumulative uploaded bytes for this session at timestamp t.
|
||||
uint64_t Controller::statSession::getPktLost(){
|
||||
uint64_t retVal = wipedPktLost;
|
||||
if (oldConns.size()){
|
||||
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
|
||||
if (it->log.size()){retVal += it->log.rbegin()->second.pktLost;}
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
if (it->second.log.size()){retVal += it->second.log.rbegin()->second.pktLost;}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
uint64_t Controller::statSession::getPktRetransmit(uint64_t t){
|
||||
uint64_t retVal = wipedPktRetransmit;
|
||||
if (oldConns.size()){
|
||||
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
|
||||
if (it->hasDataFor(t)){retVal += it->getDataFor(t).pktRetransmit;}
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
if (it->second.hasDataFor(t)){retVal += it->second.getDataFor(t).pktRetransmit;}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// Returns the cumulative uploaded bytes for this session at timestamp t.
|
||||
uint64_t Controller::statSession::getPktRetransmit(){
|
||||
uint64_t retVal = wipedPktRetransmit;
|
||||
if (oldConns.size()){
|
||||
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
|
||||
if (it->log.size()){retVal += it->log.rbegin()->second.pktRetransmit;}
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
if (it->second.log.size()){retVal += it->second.log.rbegin()->second.pktRetransmit;}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// Returns the cumulative downloaded bytes per second for this session at timestamp t.
|
||||
uint64_t Controller::statSession::getBpsDown(uint64_t t){
|
||||
uint64_t aTime = t - 5;
|
||||
|
@ -1048,6 +1151,9 @@ Controller::statLog &Controller::statStorage::getDataFor(unsigned long long t){
|
|||
empty.lastSecond = 0;
|
||||
empty.down = 0;
|
||||
empty.up = 0;
|
||||
empty.pktCount = 0;
|
||||
empty.pktLost = 0;
|
||||
empty.pktRetransmit = 0;
|
||||
return empty;
|
||||
}
|
||||
std::map<unsigned long long, statLog>::iterator it = log.upper_bound(t);
|
||||
|
@ -1063,6 +1169,9 @@ void Controller::statStorage::update(Comms::Statistics &statComm, size_t index){
|
|||
tmp.lastSecond = statComm.getLastSecond(index);
|
||||
tmp.down = statComm.getDown(index);
|
||||
tmp.up = statComm.getUp(index);
|
||||
tmp.pktCount = statComm.getPacketCount(index);
|
||||
tmp.pktLost = statComm.getPacketLostCount(index);
|
||||
tmp.pktRetransmit = statComm.getPacketRetransmitCount(index);
|
||||
log[statComm.getNow(index)] = tmp;
|
||||
// wipe data older than approx. STAT_CUTOFF seconds
|
||||
/// \todo Remove least interesting data first.
|
||||
|
@ -1132,7 +1241,7 @@ bool Controller::hasViewers(std::string streamName){
|
|||
/// //array of protocols to accumulate. Empty means all.
|
||||
/// "protocols": ["HLS", "HSS"],
|
||||
/// //list of requested data fields. Empty means all.
|
||||
/// "fields": ["host", "stream", "protocol", "conntime", "position", "down", "up", "downbps", "upbps"],
|
||||
/// "fields": ["host", "stream", "protocol", "conntime", "position", "down", "up", "downbps", "upbps","pktcount","pktlost","pktretransmit"],
|
||||
/// //unix timestamp of measuring moment. Negative means X seconds ago. Empty means now.
|
||||
/// "time": 1234567
|
||||
///}
|
||||
|
@ -1186,6 +1295,9 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
|
|||
if ((*it).asStringRef() == "downbps"){fields |= STAT_CLI_BPS_DOWN;}
|
||||
if ((*it).asStringRef() == "upbps"){fields |= STAT_CLI_BPS_UP;}
|
||||
if ((*it).asStringRef() == "sessid"){fields |= STAT_CLI_SESSID;}
|
||||
if ((*it).asStringRef() == "pktcount"){fields |= STAT_CLI_PKTCOUNT;}
|
||||
if ((*it).asStringRef() == "pktlost"){fields |= STAT_CLI_PKTLOST;}
|
||||
if ((*it).asStringRef() == "pktretransmit"){fields |= STAT_CLI_PKTRETRANSMIT;}
|
||||
}
|
||||
}
|
||||
// select all, if none selected
|
||||
|
@ -1213,6 +1325,9 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
|
|||
if (fields & STAT_CLI_BPS_UP){rep["fields"].append("upbps");}
|
||||
if (fields & STAT_CLI_CRC){rep["fields"].append("crc");}
|
||||
if (fields & STAT_CLI_SESSID){rep["fields"].append("sessid");}
|
||||
if (fields & STAT_CLI_PKTCOUNT){rep["fields"].append("pktcount");}
|
||||
if (fields & STAT_CLI_PKTLOST){rep["fields"].append("pktlost");}
|
||||
if (fields & STAT_CLI_PKTRETRANSMIT){rep["fields"].append("pktretransmit");}
|
||||
// output the data itself
|
||||
rep["data"].null();
|
||||
// loop over all sessions
|
||||
|
@ -1237,6 +1352,9 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
|
|||
if (fields & STAT_CLI_BPS_UP){d.append(it->second.getBpsUp(time));}
|
||||
if (fields & STAT_CLI_CRC){d.append(it->first.crc);}
|
||||
if (fields & STAT_CLI_SESSID){d.append(it->first.ID);}
|
||||
if (fields & STAT_CLI_PKTCOUNT){d.append(it->second.getPktCount(time));}
|
||||
if (fields & STAT_CLI_PKTLOST){d.append(it->second.getPktLost(time));}
|
||||
if (fields & STAT_CLI_PKTRETRANSMIT){d.append(it->second.getPktRetransmit(time));}
|
||||
rep["data"].append(d);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ namespace Controller{
|
|||
uint64_t lastSecond;
|
||||
uint64_t down;
|
||||
uint64_t up;
|
||||
uint64_t pktCount;
|
||||
uint64_t pktLost;
|
||||
uint64_t pktRetransmit;
|
||||
};
|
||||
|
||||
enum sessType{SESS_UNSET = 0, SESS_INPUT, SESS_OUTPUT, SESS_VIEWER};
|
||||
|
@ -74,6 +77,9 @@ namespace Controller{
|
|||
uint64_t lastSec;
|
||||
uint64_t wipedUp;
|
||||
uint64_t wipedDown;
|
||||
uint64_t wipedPktCount;
|
||||
uint64_t wipedPktLost;
|
||||
uint64_t wipedPktRetransmit;
|
||||
std::deque<statStorage> oldConns;
|
||||
sessType sessionType;
|
||||
bool tracked;
|
||||
|
@ -104,6 +110,12 @@ namespace Controller{
|
|||
uint64_t getUp();
|
||||
uint64_t getDown();
|
||||
uint64_t getUp(uint64_t time);
|
||||
uint64_t getPktCount();
|
||||
uint64_t getPktCount(uint64_t time);
|
||||
uint64_t getPktLost();
|
||||
uint64_t getPktLost(uint64_t time);
|
||||
uint64_t getPktRetransmit();
|
||||
uint64_t getPktRetransmit(uint64_t time);
|
||||
uint64_t getBpsDown(uint64_t time);
|
||||
uint64_t getBpsUp(uint64_t time);
|
||||
uint64_t getBpsDown(uint64_t start, uint64_t end);
|
||||
|
|
|
@ -679,12 +679,16 @@ namespace Mist{
|
|||
// if not shutting down, wait 1 second before looping
|
||||
if (config->is_active){Util::wait(INPUT_USER_INTERVAL);}
|
||||
}
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
|
||||
config->is_active = false;
|
||||
if (!isThread()){
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
|
||||
config->is_active = false;
|
||||
}
|
||||
finish();
|
||||
INFO_MSG("Input closing clean, reason: %s", Util::exitReason);
|
||||
userSelect.clear();
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_OFF;}
|
||||
if (!isThread()){
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_OFF;}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function checks if an input in serve mode should keep running or not.
|
||||
|
@ -729,7 +733,6 @@ namespace Mist{
|
|||
/// - if there are tracks, register as a non-viewer on the user page of the buffer
|
||||
/// - call getNext() in a loop, buffering packets
|
||||
void Input::stream(){
|
||||
|
||||
std::map<std::string, std::string> overrides;
|
||||
overrides["throughboot"] = "";
|
||||
if (config->getBool("realtime") ||
|
||||
|
@ -895,6 +898,7 @@ namespace Mist{
|
|||
statComm.setTime(now - startTime);
|
||||
statComm.setLastSecond(0);
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
handleLossyStats(statComm);
|
||||
}
|
||||
|
||||
statTimer = Util::bootSecs();
|
||||
|
|
|
@ -41,6 +41,7 @@ namespace Mist{
|
|||
virtual bool readHeader();
|
||||
virtual bool needHeader(){return !readExistingHeader();}
|
||||
virtual bool preRun(){return true;}
|
||||
virtual bool isThread(){return false;}
|
||||
virtual bool isSingular(){return !config->getBool("realtime");}
|
||||
virtual bool readExistingHeader();
|
||||
virtual bool atKeyFrame();
|
||||
|
@ -69,6 +70,10 @@ namespace Mist{
|
|||
virtual void userOnDisconnect(size_t id);
|
||||
virtual void userLeadOut();
|
||||
|
||||
virtual void handleLossyStats(Comms::Statistics & statComm){}
|
||||
|
||||
virtual bool preventBufferStart() {return false;}
|
||||
|
||||
virtual void parseHeader();
|
||||
bool bufferFrame(size_t track, uint32_t keyNum);
|
||||
|
||||
|
|
217
src/input/input_tssrt.cpp
Normal file
217
src/input/input_tssrt.cpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
#include "input_tssrt.h"
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/downloader.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/socket_srt.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/util.h>
|
||||
#include <string>
|
||||
|
||||
#include <mist/procs.h>
|
||||
#include <mist/tinythread.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
Util::Config *cfgPointer = NULL;
|
||||
std::string baseStreamName;
|
||||
|
||||
/// 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){
|
||||
SRTSOCKET sock = *((SRTSOCKET *)socknum);
|
||||
// use the accepted socket as the second parameter
|
||||
Mist::inputTSSRT inp(cfgPointer, sock);
|
||||
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, SRTSOCKET s) : Input(cfg){
|
||||
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"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("MPEG2");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["codecs"][0u][1u].append("MP2");
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
// 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();}
|
||||
}
|
||||
lastTimeStamp = 0;
|
||||
singularFlag = true;
|
||||
}
|
||||
|
||||
inputTSSRT::~inputTSSRT(){}
|
||||
|
||||
bool inputTSSRT::checkArguments(){return true;}
|
||||
|
||||
/// Live Setup of SRT Input. Runs only if we are the "main" thread
|
||||
bool inputTSSRT::preRun(){
|
||||
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"){
|
||||
sSock = Socket::SRTServer(u.getPort(), u.host, false);
|
||||
config->registerSRTSockPtr(&sSock);
|
||||
}else{
|
||||
INFO_MSG("A");
|
||||
std::map<std::string, std::string> 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();
|
||||
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()){
|
||||
// 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){
|
||||
INFO_MSG("Could not getNext TS packet!");
|
||||
return;
|
||||
}
|
||||
|
||||
tsStream.initializeMetadata(meta);
|
||||
size_t thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid());
|
||||
if (thisIdx == INVALID_TRACK_ID){getNext(idx);}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool inputTSSRT::openStreamSource(){return true;}
|
||||
|
||||
void inputTSSRT::parseStreamHeader(){
|
||||
// Placeholder empty track to force normal code to continue despite no tracks available
|
||||
tmpIdx = meta.addTrack(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void inputTSSRT::streamMainLoop(){
|
||||
meta.removeTrack(tmpIdx);
|
||||
// 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
|
||||
SRTSOCKET sock = S.getSocket();
|
||||
// spawn a new thread for this connection
|
||||
tthread::thread T(callThreadCallbackSRT, (void *)&sock);
|
||||
// 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();
|
||||
}
|
||||
|
||||
bool inputTSSRT::needsLock(){return false;}
|
||||
|
||||
void inputTSSRT::setSingular(bool newSingular){singularFlag = newSingular;}
|
||||
|
||||
void inputTSSRT::handleLossyStats(Comms::Statistics &statComm){
|
||||
statComm.setPacketCount(srtConn.packetCount());
|
||||
statComm.setPacketLostCount(srtConn.packetLostCount());
|
||||
statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount());
|
||||
}
|
||||
|
||||
}// namespace Mist
|
48
src/input/input_tssrt.h
Normal file
48
src/input/input_tssrt.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/nal.h>
|
||||
#include <mist/socket_srt.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/ts_stream.h>
|
||||
#include <set>
|
||||
#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();
|
||||
|
||||
protected:
|
||||
// Private Functions
|
||||
bool checkArguments();
|
||||
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;}
|
||||
|
||||
bool openStreamSource();
|
||||
void parseStreamHeader();
|
||||
void streamMainLoop();
|
||||
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
|
||||
TS::Packet tsBuf;
|
||||
std::string leftBuffer;
|
||||
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);
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
typedef Mist::inputTSSRT mistIn;
|
58
src/output/mist_out_srt.cpp
Normal file
58
src/output/mist_out_srt.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include OUTPUTTYPE
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/socket.h>
|
||||
#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]);
|
||||
|
||||
mistOut tmp(Sconn, S.getSocket());
|
||||
return tmp.run();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
DTSC::trackValidMask = TRACK_VALID_EXT_HUMAN;
|
||||
Util::redirectLogsIfNeeded();
|
||||
Util::Config conf(argv[0]);
|
||||
mistOut::init(&conf);
|
||||
if (conf.parseArgs(argc, argv)){
|
||||
if (conf.getBool("json")){
|
||||
mistOut::capa["version"] = PACKAGE_VERSION;
|
||||
std::cout << mistOut::capa.toString() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
conf.activate();
|
||||
if (mistOut::listenMode()){
|
||||
{
|
||||
struct sigaction new_action;
|
||||
new_action.sa_sigaction = handleUSR1;
|
||||
sigemptyset(&new_action.sa_mask);
|
||||
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");
|
||||
execvp(argv[0], argv);
|
||||
FAIL_MSG("Error reloading: %s", strerror(errno));
|
||||
}
|
||||
}else{
|
||||
Socket::Connection S(fileno(stdout), fileno(stdin));
|
||||
mistOut tmp(S, -1);
|
||||
return tmp.run();
|
||||
}
|
||||
}
|
||||
INFO_MSG("Exit reason: %s", Util::exitReason);
|
||||
return 0;
|
||||
}
|
112
src/output/output_tssrt.cpp
Normal file
112
src/output/output_tssrt.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "mist/socket_srt.h"
|
||||
#include "output_tssrt.h"
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/url.h>
|
||||
|
||||
namespace Mist{
|
||||
OutTSSRT::OutTSSRT(Socket::Connection &conn, SRTSOCKET _srtSock) : TSOutput(conn){
|
||||
// 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");
|
||||
pushOut = false;
|
||||
std::string tracks;
|
||||
// Push output configuration
|
||||
if (config->getString("target").size()){
|
||||
HTTP::URL target(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);
|
||||
return;
|
||||
}
|
||||
if (!target.getPort()){
|
||||
FAIL_MSG("Target %s must contain a port, aborting", target.getUrl().c_str());
|
||||
onFail("Invalid srt target: missing port", true);
|
||||
return;
|
||||
}
|
||||
pushOut = true;
|
||||
if (targetParams.count("tracks")){tracks = targetParams["tracks"];}
|
||||
size_t connectCnt = 0;
|
||||
do{
|
||||
srtConn.connect(target.host, target.getPort(), "output");
|
||||
if (!srtConn){Util::sleep(1000);}
|
||||
++connectCnt;
|
||||
}while (!srtConn && connectCnt < 10);
|
||||
wantRequest = false;
|
||||
parseData = true;
|
||||
}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());
|
||||
}
|
||||
}
|
||||
initialize();
|
||||
}
|
||||
|
||||
OutTSSRT::~OutTSSRT(){}
|
||||
|
||||
void OutTSSRT::init(Util::Config *cfg){
|
||||
Output::init(cfg);
|
||||
capa["name"] = "TSSRT";
|
||||
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["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
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");
|
||||
cfg->addConnectorOptions(8889, capa);
|
||||
config = cfg;
|
||||
capa["push_urls"].append("srt://*");
|
||||
|
||||
JSON::Value opt;
|
||||
opt["arg"] = "string";
|
||||
opt["default"] = "";
|
||||
opt["arg_num"] = 1;
|
||||
opt["help"] = "Target srt:// URL to push out towards.";
|
||||
cfg->addOption("target", opt);
|
||||
}
|
||||
|
||||
// Buffer internally in the class, and send once we have over 1000 bytes of data.
|
||||
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);
|
||||
}
|
||||
|
||||
bool OutTSSRT::setAlternateConnectionStats(Comms::Statistics &statComm){
|
||||
statComm.setUp(srtConn.dataUp());
|
||||
statComm.setDown(srtConn.dataDown());
|
||||
statComm.setTime(Util::bootSecs() - srtConn.connTime());
|
||||
return true;
|
||||
}
|
||||
|
||||
void OutTSSRT::handleLossyStats(Comms::Statistics &statComm){
|
||||
statComm.setPacketCount(srtConn.packetCount());
|
||||
statComm.setPacketLostCount(srtConn.packetLostCount());
|
||||
statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount());
|
||||
}
|
||||
}// namespace Mist
|
33
src/output/output_tssrt.h
Normal file
33
src/output/output_tssrt.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#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();
|
||||
|
||||
static bool listenMode(){return !(config->getString("target").size());}
|
||||
|
||||
static void init(Util::Config *cfg);
|
||||
void sendTS(const char *tsData, size_t len = 188);
|
||||
bool isReadyForPlay(){return true;}
|
||||
|
||||
protected:
|
||||
// Stats handling
|
||||
virtual bool setAlternateConnectionStats(Comms::Statistics &statComm);
|
||||
virtual void handleLossyStats(Comms::Statistics &statComm);
|
||||
|
||||
private:
|
||||
bool pushOut;
|
||||
std::string packetBuffer;
|
||||
Socket::UDPConnection pushSock;
|
||||
TS::Stream tsIn;
|
||||
|
||||
Socket::SRTConnection srtConn;
|
||||
};
|
||||
}// namespace Mist
|
||||
|
||||
typedef Mist::OutTSSRT mistOut;
|
Loading…
Add table
Add a link
Reference in a new issue