Various fixes, among which:
- Fixed segfault when attempting to initialseek on disconnected streams - Fix 100% CPU bug in controller's stats code - WebRTC UDP bind socket improvements - Several segfault fixes - Increased packet reordering buffer size from 30 to 150 packets - Tweaks to default output/buffer behaviour for incoming pushes - Added message for load balancer checks - Fixed HLS content type - Stats fixes - Exit reason fixes - Fixed socket IP address detection - Fixed non-string arguments for stream settings - Added caching for getConnectedBinHost() - Added WebRTC playback rate control - Added/completed VP8/VP9 support to WebRTC/RTSP - Added live seek option to WebRTC - Fixed seek to exactly newest timestamp - Fixed HLS input # Conflicts: # lib/defines.h # src/input/input.cpp
This commit is contained in:
parent
2b99f2f5ea
commit
0af992d405
75 changed files with 1512 additions and 790 deletions
|
@ -77,7 +77,8 @@ namespace Mist{
|
|||
option["help"] = "Generate .dtsh, then exit";
|
||||
config->addOption("headeronly", option);
|
||||
|
||||
/*LTS-START
|
||||
/*LTS-START*/
|
||||
/*
|
||||
//Encryption
|
||||
option.null();
|
||||
option["arg"] = "string";
|
||||
|
@ -86,31 +87,6 @@ namespace Mist{
|
|||
option["help"] = "a KID:KEY combo for auto-encrypting tracks";
|
||||
config->addOption("encryption", option);
|
||||
|
||||
|
||||
option.null();
|
||||
option["long"] = "realtime";
|
||||
option["short"] = "r";
|
||||
option["help"] = "Feed the results of this input in realtime to the buffer";
|
||||
config->addOption("realtime", option);
|
||||
capa["optional"]["realtime"]["name"] = "Simulated Live";
|
||||
capa["optional"]["realtime"]["help"] = "Make this input run as a simulated live stream";
|
||||
capa["optional"]["realtime"]["option"] = "--realtime";
|
||||
|
||||
option.null();
|
||||
option["long"] = "simulated-starttime";
|
||||
option["arg"] = "integer";
|
||||
option["short"] = "S";
|
||||
option["help"] = "Unix timestamp on which the simulated start of the stream is based.";
|
||||
option["value"].append(0);
|
||||
config->addOption("simulated-starttime", option);
|
||||
capa["optional"]["simulated-starttime"]["name"] = "Simulated start time";
|
||||
capa["optional"]["simulated-starttime"]["help"] =
|
||||
"The unix timestamp on which this stream is assumed to have started playback, or 0 for "
|
||||
"automatic";
|
||||
capa["optional"]["simulated-starttime"]["option"] = "--simulated-starttime";
|
||||
capa["optional"]["simulated-starttime"]["type"] = "uint";
|
||||
capa["optional"]["simulated-starttime"]["default"] = 0;
|
||||
|
||||
option.null();
|
||||
option["arg"] = "string";
|
||||
option["short"] = "B";
|
||||
|
@ -175,8 +151,33 @@ namespace Mist{
|
|||
capa["optional"]["playready"]["help"] = "The header to use for PlayReady encryption.";
|
||||
capa["optional"]["playready"]["option"] = "--playready";
|
||||
capa["optional"]["playready"]["type"] = "string";
|
||||
LTS-END*/
|
||||
*/
|
||||
|
||||
option.null();
|
||||
option["long"] = "realtime";
|
||||
option["short"] = "r";
|
||||
option["help"] = "Feed the results of this input in realtime to the buffer";
|
||||
config->addOption("realtime", option);
|
||||
capa["optional"]["realtime"]["name"] = "Simulated Live";
|
||||
capa["optional"]["realtime"]["help"] = "Make this input run as a simulated live stream";
|
||||
capa["optional"]["realtime"]["option"] = "--realtime";
|
||||
|
||||
option.null();
|
||||
option["long"] = "simulated-starttime";
|
||||
option["arg"] = "integer";
|
||||
option["short"] = "S";
|
||||
option["help"] = "Unix timestamp on which the simulated start of the stream is based.";
|
||||
option["value"].append(0);
|
||||
config->addOption("simulated-starttime", option);
|
||||
capa["optional"]["simulated-starttime"]["name"] = "Simulated start time";
|
||||
capa["optional"]["simulated-starttime"]["help"] =
|
||||
"The unix timestamp on which this stream is assumed to have started playback, or 0 for "
|
||||
"automatic";
|
||||
capa["optional"]["simulated-starttime"]["option"] = "--simulated-starttime";
|
||||
capa["optional"]["simulated-starttime"]["type"] = "uint";
|
||||
capa["optional"]["simulated-starttime"]["default"] = 0;
|
||||
|
||||
/*LTS-END*/
|
||||
capa["optional"]["debug"]["name"] = "debug";
|
||||
capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed.";
|
||||
capa["optional"]["debug"]["option"] = "--debug";
|
||||
|
@ -637,7 +638,7 @@ namespace Mist{
|
|||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
|
||||
config->is_active = false;
|
||||
finish();
|
||||
INFO_MSG("Input for stream %s closing clean", streamName.c_str());
|
||||
INFO_MSG("Input closing clean, reason: %s", Util::exitReason);
|
||||
userSelect.clear();
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_OFF;}
|
||||
}
|
||||
|
@ -669,6 +670,9 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
if (!ret && ((Util::bootSecs() - activityCounter) >= INPUT_TIMEOUT)){
|
||||
Util::logExitReason("no activity for %u seconds", Util::bootSecs() - activityCounter);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -682,28 +686,29 @@ namespace Mist{
|
|||
/// - call getNext() in a loop, buffering packets
|
||||
void Input::stream(){
|
||||
|
||||
if (!config->getBool("realtime") && Util::streamAlive(streamName)){
|
||||
WARN_MSG("Stream already online, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> overrides;
|
||||
overrides["throughboot"] = "";
|
||||
if (isSingular()){
|
||||
if (Util::streamAlive(streamName)){
|
||||
WARN_MSG("Stream already online, cancelling");
|
||||
return;
|
||||
}
|
||||
overrides["singular"] = "";
|
||||
}
|
||||
if (config->getBool("realtime") ||
|
||||
(capa.isMember("hardcoded") && capa["hardcoded"].isMember("resume") && capa["hardcoded"]["resume"])){
|
||||
overrides["resume"] = "1";
|
||||
}
|
||||
if (!Util::startInput(streamName, "push://INTERNAL_ONLY:" + config->getString("input"), true,
|
||||
true, overrides)){// manually override stream url to start the buffer
|
||||
WARN_MSG("Could not start buffer, cancelling");
|
||||
return;
|
||||
if (isSingular()){
|
||||
if (!config->getBool("realtime") && Util::streamAlive(streamName)){
|
||||
WARN_MSG("Stream already online, cancelling");
|
||||
return;
|
||||
}
|
||||
overrides["singular"] = "";
|
||||
if (!Util::startInput(streamName, "push://INTERNAL_ONLY:" + config->getString("input"), true,
|
||||
true, overrides)){// manually override stream url to start the buffer
|
||||
WARN_MSG("Could not start buffer, cancelling");
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
if (!Util::startInput(streamName, "push://INTERNAL_PUSH:" + capa["name"].asStringRef(), true,
|
||||
true, overrides)){// manually override stream url to start the buffer
|
||||
WARN_MSG("Could not start buffer, cancelling");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
INFO_MSG("Input started");
|
||||
|
@ -715,19 +720,23 @@ namespace Mist{
|
|||
}
|
||||
parseStreamHeader();
|
||||
|
||||
std::set<size_t> validTracks = M.getMySourceTracks(getpid());
|
||||
if (!validTracks.size()){
|
||||
userSelect.clear();
|
||||
finish();
|
||||
INFO_MSG("No tracks found, cancelling");
|
||||
return;
|
||||
std::set<size_t> validTracks;
|
||||
|
||||
if (publishesTracks()){
|
||||
validTracks = M.getMySourceTracks(getpid());
|
||||
if (!validTracks.size()){
|
||||
userSelect.clear();
|
||||
finish();
|
||||
INFO_MSG("No tracks found, cancelling");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
timeOffset = 0;
|
||||
uint64_t minFirstMs = 0;
|
||||
|
||||
// If resume mode is on, find matching tracks and set timeOffset values to make sure we append to the tracks.
|
||||
if (config->getBool("realtime")){
|
||||
if (publishesTracks() && config->getBool("realtime")){
|
||||
seek(0);
|
||||
|
||||
minFirstMs = 0xFFFFFFFFFFFFFFFFull;
|
||||
|
@ -736,12 +745,11 @@ namespace Mist{
|
|||
uint64_t maxLastMs = 0;
|
||||
|
||||
// track lowest firstms value
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
||||
it != myMeta.tracks.end(); ++it){
|
||||
if (it->second.firstms < minFirstMs){minFirstMs = it->second.firstms;}
|
||||
if (it->second.firstms > maxFirstMs){maxFirstMs = it->second.firstms;}
|
||||
if (it->second.lastms < minLastMs){minLastMs = it->second.lastms;}
|
||||
if (it->second.lastms > maxLastMs){maxLastMs = it->second.lastms;}
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
||||
if (meta.getFirstms(*it) < minFirstMs){minFirstMs = meta.getFirstms(*it);}
|
||||
if (meta.getFirstms(*it) > maxFirstMs){maxFirstMs = meta.getFirstms(*it);}
|
||||
if (meta.getLastms(*it) < minLastMs){minLastMs = meta.getLastms(*it);}
|
||||
if (meta.getLastms(*it) > maxLastMs){maxLastMs = meta.getLastms(*it);}
|
||||
}
|
||||
if (maxFirstMs - minFirstMs > 500){
|
||||
WARN_MSG("Begin timings of tracks for this file are %" PRIu64
|
||||
|
@ -756,10 +764,8 @@ namespace Mist{
|
|||
maxLastMs - minLastMs, minLastMs, maxLastMs);
|
||||
}
|
||||
// find highest current time
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator secondIt = tmpM.tracks.begin();
|
||||
secondIt != tmpM.tracks.end(); ++secondIt){
|
||||
VERYHIGH_MSG("Track %u starts at %" PRIu64, secondIt->first, secondIt->second.lastms);
|
||||
timeOffset = std::max(timeOffset, (int64_t)secondIt->second.lastms);
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
||||
timeOffset = std::max(timeOffset, (int64_t)meta.getLastms(*it));
|
||||
}
|
||||
|
||||
if (timeOffset){
|
||||
|
@ -771,13 +777,11 @@ namespace Mist{
|
|||
timeOffset -= minFirstMs; // we don't need to add the lowest firstms value to the offset, as it's already there
|
||||
}
|
||||
}
|
||||
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
|
||||
it != myMeta.tracks.end(); it++){
|
||||
it->second.firstms += timeOffset;
|
||||
it->second.lastms = 0;
|
||||
selectedTracks.insert(it->first);
|
||||
it->second.minKeepAway = SIMULATED_LIVE_BUFFER;
|
||||
if (publishesTracks()){
|
||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
||||
meta.setFirstms(*it, meta.getFirstms(*it)+timeOffset);
|
||||
meta.setLastms(*it, 0);
|
||||
}
|
||||
}
|
||||
|
||||
simStartTime = config->getInteger("simulated-starttime");
|
||||
|
@ -785,9 +789,9 @@ namespace Mist{
|
|||
|
||||
std::string reason;
|
||||
if (config->getBool("realtime")){
|
||||
reason = realtimeMainLoop();
|
||||
realtimeMainLoop();
|
||||
}else{
|
||||
reason = streamMainLoop();
|
||||
streamMainLoop();
|
||||
}
|
||||
|
||||
closeStreamSource();
|
||||
|
@ -795,11 +799,68 @@ namespace Mist{
|
|||
userSelect.clear();
|
||||
|
||||
finish();
|
||||
INFO_MSG("Input closing clean; reason: %s", reason.c_str());
|
||||
INFO_MSG("Input closing clean; reason: %s", Util::exitReason);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string Input::streamMainLoop(){
|
||||
void Input::streamMainLoop(){
|
||||
uint64_t statTimer = 0;
|
||||
uint64_t startTime = Util::bootSecs();
|
||||
size_t tid;
|
||||
size_t idx;
|
||||
Comms::Statistics statComm;
|
||||
getNext();
|
||||
tid = thisPacket.getTrackId();
|
||||
idx = M.trackIDToIndex(tid, getpid());
|
||||
if (thisPacket && !userSelect.count(idx)){
|
||||
userSelect[idx].reload(streamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
}
|
||||
while (thisPacket && config->is_active && userSelect[idx].isAlive()){
|
||||
if (userSelect[idx].getStatus() == COMM_STATUS_REQDISCONNECT){
|
||||
Util::logExitReason("buffer requested shutdown");
|
||||
break;
|
||||
}
|
||||
bufferLivePacket(thisPacket);
|
||||
userSelect[idx].keepAlive();
|
||||
getNext();
|
||||
if (!thisPacket){
|
||||
Util::logExitReason("invalid packet from getNext");
|
||||
break;
|
||||
}
|
||||
tid = thisPacket.getTrackId();
|
||||
idx = M.trackIDToIndex(tid, getpid());
|
||||
if (thisPacket && !userSelect.count(idx)){
|
||||
userSelect[idx].reload(streamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
}
|
||||
|
||||
if (Util::bootSecs() - statTimer > 1){
|
||||
// Connect to stats for INPUT detection
|
||||
if (!statComm){statComm.reload();}
|
||||
if (statComm){
|
||||
if (!statComm.isAlive()){
|
||||
config->is_active = false;
|
||||
Util::logExitReason("received shutdown request from controller");
|
||||
return;
|
||||
}
|
||||
uint64_t now = Util::bootSecs();
|
||||
statComm.setNow(now);
|
||||
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());
|
||||
statComm.keepAlive();
|
||||
}
|
||||
|
||||
statTimer = Util::bootSecs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Input::realtimeMainLoop(){
|
||||
uint64_t statTimer = 0;
|
||||
uint64_t startTime = Util::bootSecs();
|
||||
Comms::Statistics statComm;
|
||||
|
@ -809,7 +870,18 @@ namespace Mist{
|
|||
userSelect[tid].reload(streamName, tid, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
|
||||
}
|
||||
while (thisPacket && config->is_active && userSelect[thisPacket.getTrackId()].isAlive()){
|
||||
thisPacket.nullMember("bpos");
|
||||
while (config->is_active && userSelect[thisPacket.getTrackId()].isAlive() &&
|
||||
Util::bootMS() + SIMULATED_LIVE_BUFFER < (thisPacket.getTime() + timeOffset) + simStartTime){
|
||||
Util::sleep(std::min(((thisPacket.getTime() + timeOffset) + simStartTime) - (Util::getMS() + SIMULATED_LIVE_BUFFER),
|
||||
(uint64_t)1000));
|
||||
userSelect[thisPacket.getTrackId()].keepAlive();
|
||||
}
|
||||
uint64_t originalTime = thisPacket.getTime();
|
||||
thisPacket.setTime(originalTime + timeOffset);
|
||||
bufferLivePacket(thisPacket);
|
||||
thisPacket.setTime(originalTime);
|
||||
|
||||
userSelect[thisPacket.getTrackId()].keepAlive();
|
||||
getNext();
|
||||
if (thisPacket && !userSelect.count(thisPacket.getTrackId())){
|
||||
|
@ -823,50 +895,31 @@ namespace Mist{
|
|||
if (statComm){
|
||||
if (!statComm.isAlive()){
|
||||
config->is_active = false;
|
||||
return "received shutdown request from controller";
|
||||
Util::logExitReason("received shutdown request from controller");
|
||||
return;
|
||||
}
|
||||
uint64_t now = Util::bootSecs();
|
||||
statComm.setNow(now);
|
||||
statComm.setCRC(getpid());
|
||||
statComm.setStream(streamName);
|
||||
statComm.setConnector("INPUT");
|
||||
statComm.setConnector("INPUT:" + capa["name"].asStringRef());
|
||||
statComm.setUp(0);
|
||||
statComm.setDown(streamByteCount());
|
||||
statComm.setTime(now - startTime);
|
||||
statComm.setLastSecond(0);
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
statComm.keepAlive();
|
||||
}
|
||||
|
||||
statTimer = Util::bootSecs();
|
||||
}
|
||||
}
|
||||
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
|
||||
if (!config->is_active){return "received deactivate signal";}
|
||||
if (!thisPacket){return "Invalid packet";}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string Input::realtimeMainLoop(){
|
||||
getNext();
|
||||
while (thisPacket && config->is_active && nProxy.userClient.isAlive()){
|
||||
thisPacket.nullMember("bpos");
|
||||
while (config->is_active && nProxy.userClient.isAlive() &&
|
||||
Util::bootMS() + SIMULATED_LIVE_BUFFER < (thisPacket.getTime() + timeOffset) + simStartTime){
|
||||
Util::sleep(std::min(((thisPacket.getTime() + timeOffset) + simStartTime) - (Util::getMS() + SIMULATED_LIVE_BUFFER),
|
||||
(uint64_t)1000));
|
||||
nProxy.userClient.keepAlive();
|
||||
}
|
||||
uint64_t originalTime = thisPacket.getTime();
|
||||
thisPacket.setTime(originalTime + timeOffset);
|
||||
nProxy.bufferLivePacket(thisPacket, myMeta);
|
||||
thisPacket.setTime(originalTime);
|
||||
getNext();
|
||||
nProxy.userClient.keepAlive();
|
||||
if (!thisPacket){
|
||||
Util::logExitReason("invalid packet from getNext");
|
||||
}
|
||||
if (thisPacket && !userSelect[thisPacket.getTrackId()].isAlive()){
|
||||
Util::logExitReason("buffer shutdown");
|
||||
}
|
||||
if (!thisPacket){return "end of file";}
|
||||
if (!config->is_active){return "received deactivate signal";}
|
||||
if (!userSelect[thisPacket.getTrackId()].isAlive()){return "buffer shutdown";}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
void Input::finish(){
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace Mist{
|
|||
bool hasMeta() const;
|
||||
static Util::Config *config;
|
||||
virtual bool needsLock(){return !config->getBool("realtime");}
|
||||
virtual bool publishesTracks(){return true;}
|
||||
|
||||
protected:
|
||||
virtual bool checkArguments() = 0;
|
||||
|
@ -54,11 +55,12 @@ namespace Mist{
|
|||
virtual void convert();
|
||||
virtual void serve();
|
||||
virtual void stream();
|
||||
virtual std::string getConnectedBinHost(){return std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001", 16);}
|
||||
virtual size_t streamByteCount(){
|
||||
return 0;
|
||||
}; // For live streams: to update the stats with correct values.
|
||||
virtual std::string streamMainLoop();
|
||||
virtual std::string realtimeMainLoop();
|
||||
virtual void streamMainLoop();
|
||||
virtual void realtimeMainLoop();
|
||||
bool isAlwaysOn();
|
||||
|
||||
virtual void userLeadIn();
|
||||
|
|
|
@ -16,6 +16,73 @@ namespace Mist{
|
|||
capa["source_match"] = "balance:*";
|
||||
capa["priority"] = 9;
|
||||
capa["morphic"] = 1;
|
||||
|
||||
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;
|
||||
|
||||
option.null();
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "cut";
|
||||
option["short"] = "c";
|
||||
option["help"] = "Any timestamps before this will be cut from the live buffer";
|
||||
option["value"].append(0);
|
||||
config->addOption("cut", option);
|
||||
capa["optional"]["cut"]["name"] = "Cut time (ms)";
|
||||
capa["optional"]["cut"]["help"] =
|
||||
"Any timestamps before this will be cut from the live buffer.";
|
||||
capa["optional"]["cut"]["option"] = "--cut";
|
||||
capa["optional"]["cut"]["type"] = "uint";
|
||||
capa["optional"]["cut"]["default"] = 0;
|
||||
|
||||
option.null();
|
||||
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "resume";
|
||||
option["short"] = "R";
|
||||
option["help"] = "Enable resuming support (1) or disable resuming support (0, default)";
|
||||
option["value"].append(0);
|
||||
config->addOption("resume", option);
|
||||
capa["optional"]["resume"]["name"] = "Resume support";
|
||||
capa["optional"]["resume"]["help"] =
|
||||
"If enabled, the buffer will linger after source disconnect to allow resuming the stream "
|
||||
"later. If disabled, the buffer will instantly close on source disconnect.";
|
||||
capa["optional"]["resume"]["option"] = "--resume";
|
||||
capa["optional"]["resume"]["type"] = "select";
|
||||
capa["optional"]["resume"]["select"][0u][0u] = "0";
|
||||
capa["optional"]["resume"]["select"][0u][1u] = "Disabled";
|
||||
capa["optional"]["resume"]["select"][1u][0u] = "1";
|
||||
capa["optional"]["resume"]["select"][1u][1u] = "Enabled";
|
||||
capa["optional"]["resume"]["default"] = 0;
|
||||
|
||||
option.null();
|
||||
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "segment-size";
|
||||
option["short"] = "S";
|
||||
option["help"] = "Target time duration in milliseconds for segments";
|
||||
option["value"].append(5000);
|
||||
config->addOption("segmentsize", option);
|
||||
capa["optional"]["segmentsize"]["name"] = "Segment size (ms)";
|
||||
capa["optional"]["segmentsize"]["help"] = "Target time duration in milliseconds for segments.";
|
||||
capa["optional"]["segmentsize"]["option"] = "--segment-size";
|
||||
capa["optional"]["segmentsize"]["type"] = "uint";
|
||||
capa["optional"]["segmentsize"]["default"] = 5000;
|
||||
capa["codecs"][0u][0u].append("*");
|
||||
capa["codecs"][0u][1u].append("*");
|
||||
capa["codecs"][0u][2u].append("*");
|
||||
}
|
||||
|
||||
int inputBalancer::boot(int argc, char *argv[]){
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Mist{
|
|||
|
||||
capa["optional"].removeMember("realtime");
|
||||
|
||||
lastReTime = 0; /*LTS*/
|
||||
finalMillis = 0;
|
||||
liveMeta = 0;
|
||||
capa["name"] = "Buffer";
|
||||
|
@ -118,6 +119,7 @@ namespace Mist{
|
|||
cutTime = 0;
|
||||
segmentSize = 1900;
|
||||
hasPush = false;
|
||||
everHadPush = false;
|
||||
resumeMode = false;
|
||||
}
|
||||
|
||||
|
@ -360,7 +362,7 @@ namespace Mist{
|
|||
if (!(users.getStatus(i) & COMM_STATUS_SOURCE)){continue;}
|
||||
if (users.getTrack(i) != tid){continue;}
|
||||
// We have found the right track here (pid matches, and COMM_STATUS_SOURCE set)
|
||||
users.setStatus(COMM_STATUS_DISCONNECT, i);
|
||||
users.setStatus(COMM_STATUS_REQDISCONNECT, i);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -450,7 +452,7 @@ namespace Mist{
|
|||
// firstVideo = 1 happens when there are no tracks, in which case we don't care any more
|
||||
uint32_t firstKey = keys.getFirstValid();
|
||||
uint32_t endKey = keys.getEndValid();
|
||||
if (type != "video"){
|
||||
if (type != "video" && videoFirstms != 0xFFFFFFFFFFFFFFFFull){
|
||||
if ((endKey - firstKey) < 2 || keys.getTime(firstKey + 1) > videoFirstms){continue;}
|
||||
}
|
||||
// Buffer cutting
|
||||
|
@ -464,19 +466,6 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
updateMeta();
|
||||
if (config->is_active){
|
||||
if (streamStatus){streamStatus.mapped[0] = hasPush ? STRMSTAT_READY : STRMSTAT_WAIT;}
|
||||
}
|
||||
static bool everHadPush = false;
|
||||
if (hasPush){
|
||||
hasPush = false;
|
||||
everHadPush = true;
|
||||
}else if (everHadPush && !resumeMode && config->is_active){
|
||||
INFO_MSG("Shutting down buffer because resume mode is disabled and the source disconnected");
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
|
||||
config->is_active = false;
|
||||
userSelect.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void inputBuffer::userLeadIn(){
|
||||
|
@ -487,22 +476,21 @@ namespace Mist{
|
|||
/*LTS-END*/
|
||||
connectedUsers = 0;
|
||||
|
||||
//Store child process PIDs in generatePids.
|
||||
//These are controlled by the buffer (usually processes) and should not count towards incoming pushes
|
||||
generatePids.clear();
|
||||
for (std::map<std::string, pid_t>::iterator it = runningProcs.begin(); it != runningProcs.end(); it++){
|
||||
generatePids.insert(it->second);
|
||||
}
|
||||
hasPush = false;
|
||||
}
|
||||
void inputBuffer::userOnActive(size_t id){
|
||||
///\todo Add tracing of earliest watched keys, to prevent data going out of memory for
|
||||
/// still-watching viewers
|
||||
if (users.getStatus(id) != COMM_STATUS_DISCONNECT && users.getStatus(id) & COMM_STATUS_SOURCE){
|
||||
sourcePids[users.getPid(id)].insert(users.getTrack(id));
|
||||
if (!M.trackValid(users.getTrack(id))){
|
||||
users.setStatus(COMM_STATUS_DISCONNECT, id);
|
||||
return;
|
||||
}
|
||||
// GeneratePids holds the pids of the process that generate data, so ignore those for determining if a push is ingested.
|
||||
if (!generatePids.count(users.getPid(id))){hasPush = true;}
|
||||
if (M.trackValid(users.getTrack(id)) && !generatePids.count(users.getPid(id))){hasPush = true;}
|
||||
}
|
||||
|
||||
if (!(users.getStatus(id) & COMM_STATUS_DONOTTRACK)){++connectedUsers;}
|
||||
|
@ -516,11 +504,20 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
void inputBuffer::userLeadOut(){
|
||||
if (config->is_active && streamStatus){streamStatus.mapped[0] = hasPush ? STRMSTAT_READY : STRMSTAT_WAIT;}
|
||||
if (hasPush){everHadPush = true;}
|
||||
if (!hasPush && everHadPush && !resumeMode && config->is_active){
|
||||
Util::logExitReason("source disconnected for non-resumable stream");
|
||||
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
|
||||
config->is_active = false;
|
||||
userSelect.clear();
|
||||
}
|
||||
/*LTS-START*/
|
||||
static std::set<size_t> prevValidTracks;
|
||||
|
||||
std::set<size_t> validTracks = M.getValidTracks();
|
||||
if (validTracks != prevValidTracks){
|
||||
MEDIUM_MSG("Valid tracks count changed from %lu to %lu", prevValidTracks.size(), validTracks.size());
|
||||
prevValidTracks = validTracks;
|
||||
if (Triggers::shouldTrigger("LIVE_TRACK_LIST")){
|
||||
JSON::Value triggerPayload;
|
||||
|
@ -545,7 +542,6 @@ namespace Mist{
|
|||
|
||||
bool inputBuffer::preRun(){
|
||||
// This function gets run periodically to make sure runtime updates of the config get parsed.
|
||||
lastReTime = Util::epoch(); /*LTS*/
|
||||
std::string strName = config->getString("streamname");
|
||||
Util::sanitizeName(strName);
|
||||
strName = strName.substr(0, (strName.find_first_of("+ ")));
|
||||
|
@ -553,16 +549,21 @@ namespace Mist{
|
|||
snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, strName.c_str());
|
||||
Util::DTSCShmReader rStrmConf(tmpBuf);
|
||||
DTSC::Scan streamCfg = rStrmConf.getScan();
|
||||
if (streamCfg){
|
||||
JSON::Value configuredProcesses = streamCfg.getMember("processes").asJSON();
|
||||
checkProcesses(configuredProcesses);
|
||||
}
|
||||
|
||||
//Check if bufferTime setting is correct
|
||||
uint64_t tmpNum = retrieveSetting(streamCfg, "DVR", "bufferTime");
|
||||
if (tmpNum < 1000){tmpNum = 1000;}
|
||||
// if the new value is different, print a message and apply it
|
||||
if (bufferTime != tmpNum){
|
||||
DEVEL_MSG("Setting bufferTime from %" PRIu64 " to new value of %" PRIu64, bufferTime, tmpNum);
|
||||
bufferTime = tmpNum;
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
//Check if cutTime setting is correct
|
||||
tmpNum = retrieveSetting(streamCfg, "cut");
|
||||
// if the new value is different, print a message and apply it
|
||||
if (cutTime != tmpNum){
|
||||
|
@ -570,28 +571,27 @@ namespace Mist{
|
|||
cutTime = tmpNum;
|
||||
}
|
||||
|
||||
//Check if resume setting is correct
|
||||
tmpNum = retrieveSetting(streamCfg, "resume");
|
||||
// if the new value is different, print a message and apply it
|
||||
if (resumeMode != (bool)tmpNum){
|
||||
INFO_MSG("Setting resume mode from %s to new value of %s",
|
||||
resumeMode ? "enabled" : "disabled", tmpNum ? "enabled" : "disabled");
|
||||
resumeMode = tmpNum;
|
||||
}
|
||||
|
||||
if (!meta){return true;}//abort the rest if we can't write metadata
|
||||
lastReTime = Util::epoch(); /*LTS*/
|
||||
|
||||
//Check if segmentsize setting is correct
|
||||
tmpNum = retrieveSetting(streamCfg, "segmentsize");
|
||||
if (M && tmpNum < M.biggestFragment() / 2){tmpNum = M.biggestFragment() / 2;}
|
||||
// if the new value is different, print a message and apply it
|
||||
if (tmpNum < meta.biggestFragment() / 2){tmpNum = meta.biggestFragment() / 2;}
|
||||
segmentSize = meta.getMinimumFragmentDuration();
|
||||
if (segmentSize != tmpNum){
|
||||
INFO_MSG("Setting segmentSize from %" PRIu64 " to new value of %" PRIu64, segmentSize, tmpNum);
|
||||
segmentSize = tmpNum;
|
||||
if (M && M.getMinimumFragmentDuration() == 0){
|
||||
meta.setMinimumFragmentDuration(segmentSize);
|
||||
}
|
||||
}
|
||||
if (streamCfg){
|
||||
JSON::Value configuredProcesses = streamCfg.getMember("processes").asJSON();
|
||||
checkProcesses(configuredProcesses);
|
||||
meta.setMinimumFragmentDuration(segmentSize);
|
||||
}
|
||||
|
||||
/*LTS-END*/
|
||||
return true;
|
||||
}
|
||||
|
@ -643,11 +643,8 @@ namespace Mist{
|
|||
void inputBuffer::checkProcesses(const JSON::Value &procs){
|
||||
if (!M.getValidTracks().size()){return;}
|
||||
std::set<std::string> newProcs;
|
||||
std::map<std::string, std::string> wouldSelect;
|
||||
|
||||
// used for building args
|
||||
int zero = 0;
|
||||
int out = fileno(stdout);
|
||||
int err = fileno(stderr);
|
||||
char *argarr[3];
|
||||
|
||||
|
@ -660,22 +657,14 @@ namespace Mist{
|
|||
continue;
|
||||
}
|
||||
if (tmp.isMember("source_track")){
|
||||
std::string sourceTrack = tmp["source_track"].asString();
|
||||
if (sourceTrack != "null" && findTrack(sourceTrack) == INVALID_TRACK_ID){
|
||||
// No match - skip this process
|
||||
continue;
|
||||
}
|
||||
std::set<size_t> wouldSelect = Util::findTracks(M, JSON::Value(), "", tmp["source_track"].asStringRef());
|
||||
// No match - skip this process
|
||||
if (!wouldSelect.size()){continue;}
|
||||
}
|
||||
std::stringstream s;
|
||||
if (tmp.isMember("track_select")){
|
||||
std::set<size_t> wouldSelect = Util::wouldSelect(M, tmp["track_select"].asStringRef());
|
||||
if (!wouldSelect.size()){
|
||||
// No match - skip this process
|
||||
continue;
|
||||
}
|
||||
for (std::set<size_t>::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){
|
||||
s << *it << " ";
|
||||
}
|
||||
// No match - skip this process
|
||||
if (!wouldSelect.size()){continue;}
|
||||
}
|
||||
if (tmp.isMember("track_inhibit")){
|
||||
std::set<size_t> wouldSelect = Util::wouldSelect(
|
||||
|
@ -693,7 +682,6 @@ namespace Mist{
|
|||
}
|
||||
}
|
||||
newProcs.insert(tmp.toString());
|
||||
wouldSelect[tmp.toString()] = s.str();
|
||||
}
|
||||
|
||||
// shut down deleted/changed processes
|
||||
|
@ -722,8 +710,7 @@ namespace Mist{
|
|||
argarr[1] = (char *)config.c_str();
|
||||
argarr[2] = 0;
|
||||
INFO_MSG("Starting process: %s %s", argarr[0], argarr[1]);
|
||||
INFO_MSG(" WouldSelect is %s", wouldSelect.at(*newProcs.begin()).c_str());
|
||||
runningProcs[*newProcs.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err);
|
||||
runningProcs[*newProcs.begin()] = Util::Procs::StartPiped(argarr, 0, 0, &err);
|
||||
}
|
||||
newProcs.erase(newProcs.begin());
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ namespace Mist{
|
|||
size_t segmentSize; /*LTS*/
|
||||
uint64_t lastReTime; /*LTS*/
|
||||
uint64_t finalMillis;
|
||||
bool hasPush;
|
||||
bool hasPush;//Is a push currently being received?
|
||||
bool everHadPush;//Was there ever a push received?
|
||||
bool resumeMode;
|
||||
IPC::semaphore *liveMeta;
|
||||
|
||||
|
|
|
@ -361,7 +361,6 @@ namespace Mist{
|
|||
thisPacket.reInit(srcConn); // read the next packet before continuing
|
||||
continue; // parse the next packet before returning
|
||||
}
|
||||
thisPacket = DTSC::Packet(thisPacket, M.trackIDToIndex(thisPacket.getTrackId(), getpid()));
|
||||
return; // we have a packet
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,11 @@ namespace Mist{
|
|||
inputDTSC(Util::Config *cfg);
|
||||
bool needsLock();
|
||||
|
||||
virtual std::string getConnectedBinHost(){
|
||||
if (srcConn){return srcConn.getBinHost();}
|
||||
return Input::getConnectedBinHost();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Private Functions
|
||||
bool openStreamSource();
|
||||
|
|
|
@ -114,12 +114,17 @@ namespace Mist{
|
|||
while (ptr.size() < needed){
|
||||
if (!ptr.allocate(needed)){return false;}
|
||||
int64_t toRead = needed - ptr.size();
|
||||
if (!fread(ptr + ptr.size(), toRead, 1, inFile)){
|
||||
// We assume if there is no current data buffered, that we are at EOF and don't print a warning
|
||||
if (ptr.size()){
|
||||
FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed);
|
||||
int readResult = 0;
|
||||
while (!readResult){
|
||||
readResult = fread(ptr + ptr.size(), toRead, 1, inFile);
|
||||
if (!readResult){
|
||||
if (errno == EINTR){continue;}
|
||||
// At EOF we don't print a warning
|
||||
if (!feof(inFile)){
|
||||
FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
totalBytes += toRead;
|
||||
ptr.size() = needed;
|
||||
|
@ -463,7 +468,7 @@ namespace Mist{
|
|||
}break;
|
||||
}
|
||||
}
|
||||
thisPacket.genericFill(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.ptr, C.dsize,
|
||||
thisPacket.genericFill(C.time, C.offset, C.track, C.ptr, C.dsize,
|
||||
C.bpos, C.key);
|
||||
}
|
||||
|
||||
|
|
|
@ -525,8 +525,7 @@ namespace Mist{
|
|||
std::string test = root.link(entry.filename).getFilePath();
|
||||
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
|
||||
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
|
||||
entry.byteEnd = fileSource.tellg();
|
||||
totalBytes += entry.byteEnd;
|
||||
totalBytes += fileSource.tellg();
|
||||
}
|
||||
|
||||
entry.timestamp = lastTimestamp + startTime;
|
||||
|
@ -592,12 +591,9 @@ namespace Mist{
|
|||
void inputHLS::parseStreamHeader(){
|
||||
if (!initPlaylist(config->getString("input"))){
|
||||
FAIL_MSG("Failed to load HLS playlist, aborting");
|
||||
myMeta = DTSC::Meta();
|
||||
return;
|
||||
}
|
||||
myMeta = DTSC::Meta();
|
||||
myMeta.live = true;
|
||||
myMeta.vod = false;
|
||||
meta.reInit(config->getString("streamname"), false);
|
||||
INFO_MSG("Parsing live stream to create header...");
|
||||
TS::Packet packet; // to analyse and extract data
|
||||
int counter = 1;
|
||||
|
@ -612,7 +608,7 @@ namespace Mist{
|
|||
|
||||
for (std::deque<playListEntries>::iterator entryIt = pListIt->second.begin();
|
||||
entryIt != pListIt->second.end(); ++entryIt){
|
||||
nProxy.userClient.keepAlive();
|
||||
keepAlive();
|
||||
if (!segDowner.loadSegment(*entryIt)){
|
||||
WARN_MSG("Skipping segment that could not be loaded in an attempt to recover");
|
||||
tsStream.clear();
|
||||
|
@ -633,21 +629,24 @@ namespace Mist{
|
|||
tsStream.getEarliestPacket(headerPack);
|
||||
int tmpTrackId = headerPack.getTrackId();
|
||||
uint64_t packetId = pidMapping[(((uint64_t)pListIt->first) << 32) + tmpTrackId];
|
||||
|
||||
|
||||
if (packetId == 0){
|
||||
pidMapping[(((uint64_t)pListIt->first) << 32) + headerPack.getTrackId()] = counter;
|
||||
pidMappingR[counter] = (((uint64_t)pListIt->first) << 32) + headerPack.getTrackId();
|
||||
packetId = counter;
|
||||
VERYHIGH_MSG("Added file %s, trackid: %zu, mapped to: %d",
|
||||
entryIt->filename.c_str(), headerPack.getTrackId(), counter);
|
||||
VERYHIGH_MSG("Added file %s, trackid: %zu, mapped to: %d", entryIt->filename.c_str(),
|
||||
headerPack.getTrackId(), counter);
|
||||
counter++;
|
||||
}
|
||||
|
||||
if ((!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){
|
||||
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId);
|
||||
myMeta.tracks[packetId].minKeepAway = globalWaitTime * 2000;
|
||||
VERYHIGH_MSG("setting minKeepAway = %d for track: %" PRIu64,
|
||||
myMeta.tracks[packetId].minKeepAway, packetId);
|
||||
size_t idx = M.trackIDToIndex(packetId, getpid());
|
||||
if ((idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
|
||||
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
|
||||
idx = M.trackIDToIndex(packetId, getpid());
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
meta.setMinKeepAway(idx, globalWaitTime * 2000);
|
||||
VERYHIGH_MSG("setting minKeepAway = %" PRIu32 " for track: %zu", globalWaitTime * 2000, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
break; // we have all tracks discovered, next playlist!
|
||||
|
@ -655,8 +654,6 @@ namespace Mist{
|
|||
}while (!segDowner.atEnd());
|
||||
if (preCounter < counter){break;}// We're done reading this playlist!
|
||||
}
|
||||
|
||||
in.close();
|
||||
}
|
||||
tsStream.clear();
|
||||
currentPlaylist = 0;
|
||||
|
@ -673,8 +670,6 @@ namespace Mist{
|
|||
meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh");
|
||||
hasHeader = (bool)M;
|
||||
|
||||
if (M){return true;}
|
||||
|
||||
if (!hasHeader){meta.reInit(config->getString("streamname"), true);}
|
||||
|
||||
TS::Packet packet; // to analyse and extract data
|
||||
|
@ -704,7 +699,7 @@ namespace Mist{
|
|||
DTSC::Packet headerPack;
|
||||
tsStream.getEarliestPacket(headerPack);
|
||||
|
||||
int tmpTrackId = headerPack.getTrackId();
|
||||
size_t tmpTrackId = headerPack.getTrackId();
|
||||
uint64_t packetId = pidMapping[(((uint64_t)pListIt->first) << 32) + tmpTrackId];
|
||||
|
||||
if (packetId == 0){
|
||||
|
@ -717,10 +712,8 @@ namespace Mist{
|
|||
}
|
||||
|
||||
size_t idx = M.trackIDToIndex(packetId, getpid());
|
||||
INFO_MSG("PacketID: %" PRIu64 ", pid: %d, mapped to %zu", packetId, getpid(), idx);
|
||||
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
|
||||
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
|
||||
INFO_MSG("InitializingMeta for track %zu -> %zu", tmpTrackId, packetId);
|
||||
idx = M.trackIDToIndex(packetId, getpid());
|
||||
}
|
||||
|
||||
|
@ -757,6 +750,7 @@ namespace Mist{
|
|||
counter++;
|
||||
}
|
||||
|
||||
size_t idx = M.trackIDToIndex(packetId, getpid());
|
||||
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
|
||||
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
|
||||
idx = M.trackIDToIndex(packetId, getpid());
|
||||
|
@ -781,7 +775,6 @@ namespace Mist{
|
|||
|
||||
INFO_MSG("write header file...");
|
||||
M.toFile((config->getString("input") + ".dtsh").c_str());
|
||||
in.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -794,26 +787,29 @@ namespace Mist{
|
|||
INSANE_MSG("Getting next");
|
||||
uint32_t tid = 0;
|
||||
bool finished = false;
|
||||
if (userSelect.size()){tid = userSelect.begin()->first;}
|
||||
thisPacket.null();
|
||||
while (config->is_active && (needsLock() || keepAlive())){
|
||||
|
||||
// Check if we have a packet
|
||||
bool hasPacket = false;
|
||||
if (streamIsLive){
|
||||
if (idx == INVALID_TRACK_ID){
|
||||
hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket());
|
||||
}else{
|
||||
hasPacket = tsStream.hasPacket(M.getID(idx) & 0xFFFF);
|
||||
hasPacket = tsStream.hasPacket(getMappedTrackId(M.getID(idx)));
|
||||
}
|
||||
|
||||
// Yes? Excellent! Read and return it.
|
||||
if (hasPacket){
|
||||
// Read
|
||||
if (M.getLive()){
|
||||
if (idx == INVALID_TRACK_ID){
|
||||
tsStream.getEarliestPacket(thisPacket);
|
||||
tid = M.trackIDToIndex((((uint64_t)currentPlaylist) << 16) + thisPacket.getTrackId(), getpid());
|
||||
tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId());
|
||||
if (!tid){
|
||||
INFO_MSG("Track %" PRIu64 " on PLS %" PRIu64 " -> %" PRIu32, thisPacket.getTrackId(), currentPlaylist, tid);
|
||||
continue;
|
||||
}
|
||||
}else{
|
||||
tsStream.getPacket(M.getID(idx) & 0xFFFF, thisPacket);
|
||||
tsStream.getPacket(getMappedTrackId(M.getID(idx)), thisPacket);
|
||||
}
|
||||
if (!thisPacket){
|
||||
FAIL_MSG("Could not getNext TS packet!");
|
||||
|
@ -850,8 +846,8 @@ namespace Mist{
|
|||
plsTimeOffset[currentPlaylist] +=
|
||||
(int64_t)(plsLastTime[currentPlaylist] + plsInterval[currentPlaylist]) - (int64_t)newTime;
|
||||
newTime = thisPacket.getTime() + plsTimeOffset[currentPlaylist];
|
||||
INFO_MSG("[Guess; New offset: %" PRId64 " -> %" PRId64 "] Packet %" PRIu32
|
||||
"@%" PRIu64 "ms -> %" PRIu64 "ms",
|
||||
INFO_MSG("[Guess; New offset: %" PRId64 " -> %" PRId64 "] Packet %" PRIu32 "@%" PRIu64
|
||||
"ms -> %" PRIu64 "ms",
|
||||
prevOffset, plsTimeOffset[currentPlaylist], tid, thisPacket.getTime(), newTime);
|
||||
}
|
||||
}
|
||||
|
@ -891,24 +887,28 @@ namespace Mist{
|
|||
// No? Then we want to try reading the next file.
|
||||
|
||||
// No segments? Wait until next playlist reloading time.
|
||||
currentPlaylist = firstSegment();
|
||||
if (idx != INVALID_TRACK_ID){
|
||||
currentPlaylist = getMappedTrackPlaylist(M.getID(idx));
|
||||
}else{
|
||||
currentPlaylist = firstSegment();
|
||||
}
|
||||
if (currentPlaylist < 0){
|
||||
VERYHIGH_MSG("Waiting for segments...");
|
||||
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();}
|
||||
keepAlive();
|
||||
Util::wait(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now that we know our playlist is up-to-date, actually try to read the file.
|
||||
VERYHIGH_MSG("Moving on to next TS segment (variant %" PRIu32 ")", currentPlaylist);
|
||||
VERYHIGH_MSG("Moving on to next TS segment (variant %" PRIu64 ")", currentPlaylist);
|
||||
if (readNextFile()){
|
||||
MEDIUM_MSG("Next segment read successfully");
|
||||
finished = false;
|
||||
continue; // Success! Continue regular parsing.
|
||||
}else{
|
||||
if (selectedTracks.size() > 1){
|
||||
if (userSelect.size() > 1){
|
||||
// failed to read segment for playlist, dropping it
|
||||
WARN_MSG("Dropping variant %" PRIu32 " because we couldn't read anything from it", currentPlaylist);
|
||||
WARN_MSG("Dropping variant %" PRIu64 " because we couldn't read anything from it", currentPlaylist);
|
||||
tthread::lock_guard<tthread::mutex> guard(entryMutex);
|
||||
listEntries.erase(currentPlaylist);
|
||||
if (listEntries.size()){continue;}
|
||||
|
@ -946,17 +946,17 @@ namespace Mist{
|
|||
|
||||
currentIndex = plistEntry - 1;
|
||||
currentPlaylist = getMappedTrackPlaylist(trackId);
|
||||
INFO_MSG("Seeking to index %d on playlist %d", currentIndex, currentPlaylist);
|
||||
INFO_MSG("Seeking to index %zu on playlist %" PRIu64, currentIndex, currentPlaylist);
|
||||
|
||||
{// Lock mutex for listEntries
|
||||
tthread::lock_guard<tthread::mutex> guard(entryMutex);
|
||||
if (!listEntries.count(currentPlaylist)){
|
||||
WARN_MSG("Playlist %d not loaded, aborting seek", currentPlaylist);
|
||||
WARN_MSG("Playlist %" PRIu64 " not loaded, aborting seek", currentPlaylist);
|
||||
return;
|
||||
}
|
||||
std::deque<playListEntries> &curPlaylist = listEntries[currentPlaylist];
|
||||
if (curPlaylist.size() <= currentIndex){
|
||||
WARN_MSG("Playlist %d has %zu <= %d entries, aborting seek", currentPlaylist,
|
||||
WARN_MSG("Playlist %" PRIu64 " has %zu <= %zu entries, aborting seek", currentPlaylist,
|
||||
curPlaylist.size(), currentIndex);
|
||||
return;
|
||||
}
|
||||
|
@ -1179,7 +1179,7 @@ namespace Mist{
|
|||
tthread::lock_guard<tthread::mutex> guard(entryMutex);
|
||||
std::deque<playListEntries> &curList = listEntries[currentPlaylist];
|
||||
if (!curList.size()){
|
||||
WARN_MSG("no entries found in playlist: %d!", currentPlaylist);
|
||||
WARN_MSG("no entries found in playlist: %" PRIu64 "!", currentPlaylist);
|
||||
return false;
|
||||
}
|
||||
if (!streamIsLive){
|
||||
|
@ -1204,7 +1204,7 @@ namespace Mist{
|
|||
if (Util::bootSecs() < ntry.timestamp){
|
||||
VERYHIGH_MSG("Slowing down to realtime...");
|
||||
while (Util::bootSecs() < ntry.timestamp){
|
||||
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();}
|
||||
keepAlive();
|
||||
Util::wait(250);
|
||||
}
|
||||
}
|
||||
|
@ -1228,7 +1228,7 @@ namespace Mist{
|
|||
/// this will keep the playlists in sync while reading segments.
|
||||
size_t inputHLS::firstSegment(){
|
||||
// Only one selected? Immediately return the right playlist.
|
||||
if (userSelect.size() == 1){return ((M.getID(userSelect.begin()->first) >> 16) & 0xFFFF);}
|
||||
if (userSelect.size() == 1){return getMappedTrackPlaylist(M.getID(userSelect.begin()->first));}
|
||||
uint64_t firstTimeStamp = 0;
|
||||
int tmpId = -1;
|
||||
int segCount = 0;
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace Mist{
|
|||
Socket::Connection conn;
|
||||
TS::Packet tsBuf;
|
||||
|
||||
int firstSegment();
|
||||
size_t firstSegment();
|
||||
void waitForNextSegment();
|
||||
void readPMT();
|
||||
bool checkArguments();
|
||||
|
@ -130,7 +130,6 @@ namespace Mist{
|
|||
bool needHeader(){return true;}
|
||||
void getNext(size_t idx = INVALID_TRACK_ID);
|
||||
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
|
||||
|
||||
FILE *inFile;
|
||||
FILE *tsFile;
|
||||
|
||||
|
@ -141,6 +140,9 @@ namespace Mist{
|
|||
|
||||
void parseStreamHeader();
|
||||
|
||||
uint32_t getMappedTrackId(uint64_t id);
|
||||
uint32_t getMappedTrackPlaylist(uint64_t id);
|
||||
uint64_t getOriginalTrackId(uint32_t playlistId, uint32_t id);
|
||||
size_t getEntryId(uint32_t playlistId, uint64_t bytePos);
|
||||
};
|
||||
}// namespace Mist
|
||||
|
|
|
@ -39,17 +39,19 @@ namespace Mist{
|
|||
return true;
|
||||
}
|
||||
|
||||
std::string inputPlaylist::streamMainLoop(){
|
||||
void inputPlaylist::streamMainLoop(){
|
||||
bool seenValidEntry = true;
|
||||
uint64_t startTime = Util::bootMS();
|
||||
while (config->is_active && nProxy.userClient.isAlive()){
|
||||
while (config->is_active){
|
||||
struct tm *wTime;
|
||||
time_t nowTime = time(0);
|
||||
wTime = localtime(&nowTime);
|
||||
wallTime = wTime->tm_hour * 60 + wTime->tm_min;
|
||||
nProxy.userClient.keepAlive();
|
||||
reloadPlaylist();
|
||||
if (!playlist.size()){return "No entries in playlist";}
|
||||
if (!playlist.size()){
|
||||
Util::logExitReason("No entries in playlist");
|
||||
return;
|
||||
}
|
||||
++playlistIndex;
|
||||
if (playlistIndex >= playlist.size()){
|
||||
if (!seenValidEntry){
|
||||
|
@ -103,7 +105,7 @@ namespace Mist{
|
|||
continue;
|
||||
}
|
||||
seenValidEntry = true;
|
||||
while (Util::Procs::isRunning(spawn_pid) && nProxy.userClient.isAlive() && config->is_active){
|
||||
while (Util::Procs::isRunning(spawn_pid) && config->is_active){
|
||||
Util::sleep(1000);
|
||||
if (reloadOn != 0xFFFF){
|
||||
time_t nowTime = time(0);
|
||||
|
@ -117,13 +119,9 @@ namespace Mist{
|
|||
Util::Procs::Stop(spawn_pid);
|
||||
}
|
||||
}
|
||||
nProxy.userClient.keepAlive();
|
||||
}
|
||||
if (!config->is_active && Util::Procs::isRunning(spawn_pid)){Util::Procs::Stop(spawn_pid);}
|
||||
}
|
||||
if (!config->is_active){return "received deactivate signal";}
|
||||
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
void inputPlaylist::reloadPlaylist(){
|
||||
|
|
|
@ -11,9 +11,10 @@ namespace Mist{
|
|||
protected:
|
||||
bool checkArguments();
|
||||
bool readHeader(){return true;}
|
||||
virtual void parseStreamHeader(){myMeta.tracks[1].codec = "PLACEHOLDER";}
|
||||
std::string streamMainLoop();
|
||||
virtual void parseStreamHeader(){}
|
||||
void streamMainLoop();
|
||||
virtual bool needHeader(){return false;}
|
||||
virtual bool publishesTracks(){return false;}
|
||||
|
||||
private:
|
||||
void reloadPlaylist();
|
||||
|
|
|
@ -45,6 +45,8 @@ namespace Mist{
|
|||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("MPEG2");
|
||||
capa["codecs"][0u][0u].append("VP8");
|
||||
capa["codecs"][0u][0u].append("VP9");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
|
@ -194,45 +196,45 @@ namespace Mist{
|
|||
tcpCon.close();
|
||||
}
|
||||
|
||||
std::string InputRTSP::streamMainLoop(){
|
||||
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
void InputRTSP::streamMainLoop(){
|
||||
Comms::Statistics statComm;
|
||||
uint64_t startTime = Util::epoch();
|
||||
uint64_t lastPing = Util::bootSecs();
|
||||
uint64_t lastSecs = 0;
|
||||
while (keepAlive() && parsePacket()){
|
||||
uint64_t currSecs = Util::bootSecs();
|
||||
handleUDP();
|
||||
if (Util::bootSecs() - lastPing > 30){
|
||||
sendCommand("GET_PARAMETER", url.getUrl(), "");
|
||||
lastPing = Util::bootSecs();
|
||||
}
|
||||
if (lastSecs != currSecs){
|
||||
if (!statsPage.getData()){
|
||||
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
}
|
||||
if (statsPage.getData()){
|
||||
if (!statsPage.isAlive()){
|
||||
lastSecs = currSecs;
|
||||
// Connect to stats for INPUT detection
|
||||
statComm.reload();
|
||||
if (statComm){
|
||||
if (!statComm.isAlive()){
|
||||
config->is_active = false;
|
||||
statsPage.finish();
|
||||
return "received shutdown request from controller";
|
||||
Util::logExitReason("received shutdown request from controller");
|
||||
return;
|
||||
}
|
||||
uint64_t now = Util::epoch();
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
tmpEx.crc(getpid());
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector("INPUT");
|
||||
tmpEx.up(tcpCon.dataUp());
|
||||
tmpEx.down(tcpCon.dataDown());
|
||||
tmpEx.time(now - startTime);
|
||||
tmpEx.lastSecond(0);
|
||||
statsPage.keepAlive();
|
||||
uint64_t now = Util::bootSecs();
|
||||
statComm.setNow(now);
|
||||
statComm.setCRC(getpid());
|
||||
statComm.setStream(streamName);
|
||||
statComm.setConnector("INPUT:" + capa["name"].asStringRef());
|
||||
statComm.setUp(tcpCon.dataUp());
|
||||
statComm.setDown(tcpCon.dataDown());
|
||||
statComm.setTime(now - startTime);
|
||||
statComm.setLastSecond(0);
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
statComm.keepAlive();
|
||||
}
|
||||
}
|
||||
}
|
||||
statsPage.finish();
|
||||
if (!tcpCon){return "TCP connection closed";}
|
||||
if (!config->is_active){return "received deactivate signal";}
|
||||
if (!keepAlive()){return "buffer shutdown";}
|
||||
return "Unknown";
|
||||
if (!tcpCon){
|
||||
Util::logExitReason("TCP connection closed");
|
||||
}
|
||||
}
|
||||
|
||||
bool InputRTSP::parsePacket(bool mustHave){
|
||||
|
|
|
@ -17,6 +17,11 @@ namespace Mist{
|
|||
void incoming(const DTSC::Packet &pkt);
|
||||
void incomingRTP(const uint64_t track, const RTP::Packet &p);
|
||||
|
||||
virtual std::string getConnectedBinHost(){
|
||||
if (tcpCon){return tcpCon.getBinHost();}
|
||||
return Input::getConnectedBinHost();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Private Functions
|
||||
bool checkArguments();
|
||||
|
@ -29,7 +34,7 @@ namespace Mist{
|
|||
const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth = true);
|
||||
bool parsePacket(bool mustHave = false);
|
||||
bool handleUDP();
|
||||
std::string streamMainLoop();
|
||||
void streamMainLoop();
|
||||
Socket::Connection tcpCon;
|
||||
HTTP::Parser sndH, recH;
|
||||
HTTP::URL url;
|
||||
|
|
|
@ -463,7 +463,7 @@ namespace Mist{
|
|||
tmpIdx = meta.addTrack(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
std::string inputTS::streamMainLoop(){
|
||||
void inputTS::streamMainLoop(){
|
||||
meta.removeTrack(tmpIdx);
|
||||
INFO_MSG("Removed temptrack %zu", tmpIdx);
|
||||
Comms::Statistics statComm;
|
||||
|
@ -495,7 +495,8 @@ namespace Mist{
|
|||
}
|
||||
if (!tcpCon){
|
||||
config->is_active = false;
|
||||
return "end of streamed input";
|
||||
Util::logExitReason("end of streamed input");
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
std::string leftData;
|
||||
|
@ -557,17 +558,19 @@ namespace Mist{
|
|||
if (statComm){
|
||||
if (!statComm.isAlive()){
|
||||
config->is_active = false;
|
||||
return "received shutdown request from controller";
|
||||
Util::logExitReason("received shutdown request from controller");
|
||||
return;
|
||||
}
|
||||
uint64_t now = Util::bootSecs();
|
||||
statComm.setNow(now);
|
||||
statComm.setCRC(getpid());
|
||||
statComm.setStream(streamName);
|
||||
statComm.setConnector("INPUT");
|
||||
statComm.setConnector("INPUT:" + capa["name"].asStringRef());
|
||||
statComm.setUp(0);
|
||||
statComm.setDown(downCounter + tcpCon.dataDown());
|
||||
statComm.setTime(now - startTime);
|
||||
statComm.setLastSecond(0);
|
||||
statComm.setHost(getConnectedBinHost());
|
||||
statComm.keepAlive();
|
||||
}
|
||||
|
||||
|
@ -577,7 +580,8 @@ namespace Mist{
|
|||
if (hasStarted && !threadTimer.size()){
|
||||
if (!isAlwaysOn()){
|
||||
config->is_active = false;
|
||||
return "no active threads and we had input in the past";
|
||||
Util::logExitReason("no active threads and we had input in the past");
|
||||
return;
|
||||
}else{
|
||||
hasStarted = false;
|
||||
}
|
||||
|
@ -607,13 +611,13 @@ namespace Mist{
|
|||
if (Util::bootSecs() - noDataSince > 20){
|
||||
if (!isAlwaysOn()){
|
||||
config->is_active = false;
|
||||
return "No packets received for 20 seconds - terminating";
|
||||
Util::logExitReason("no packets received for 20 seconds");
|
||||
return;
|
||||
}else{
|
||||
noDataSince = Util::bootSecs();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "received shutdown request";
|
||||
}
|
||||
|
||||
void inputTS::finish(){
|
||||
|
|
|
@ -14,6 +14,11 @@ namespace Mist{
|
|||
~inputTS();
|
||||
bool needsLock();
|
||||
|
||||
virtual std::string getConnectedBinHost(){
|
||||
if (tcpCon){return tcpCon.getBinHost();}
|
||||
/// \TODO Handle UDP
|
||||
return Input::getConnectedBinHost();
|
||||
}
|
||||
protected:
|
||||
// Private Functions
|
||||
bool checkArguments();
|
||||
|
@ -25,7 +30,7 @@ namespace Mist{
|
|||
void readPMT();
|
||||
bool openStreamSource();
|
||||
void parseStreamHeader();
|
||||
std::string streamMainLoop();
|
||||
void streamMainLoop();
|
||||
void finish();
|
||||
FILE *inFile; ///< The input file with ts data
|
||||
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue