Several fixes:

- Fixed bug in stream health function causing loop if track not active
- Fixed DTSC pulls ignoring data before the live point
- Improved async buffers (deque mode) to spread the tracks more fairly
- DTSC pull now implements "ping" and "error" commands
- DTSC pulls report suspicious keyframe intervals to the origin and ask for confirmation
- DTSC output now accepts these reports and disconnects if there is no match in keyframe intervals
- Outputs in async mode now keep the seek point in all tracks when reselecting
- Outputs in async mode now default to a starting position in each track that is at a keyframe roughly halfway in the buffer
- Outputs in async mode now ignore playback rate (always fastest possible)
- Removed code duplication in prepareNext function
- Reordered the prepareNext function somewhat to be easier to follow for humans
- DTSC output no longer overrides initialSeek function, now uses default implementation
- Sanitycheck output now supports both sync and async modes, supports printing multiple timestamps for multiple tracks
This commit is contained in:
Thulinma 2022-01-06 12:52:47 +01:00
parent b89875ea37
commit f560b88bfe
9 changed files with 257 additions and 222 deletions

View file

@ -1384,8 +1384,13 @@ namespace DTSC{
setType(newIdx, M.getType(*it));
setCodec(newIdx, M.getCodec(*it));
setLang(newIdx, M.getLang(*it));
setFirstms(newIdx, M.getFirstms(*it));
setLastms(newIdx, M.getLastms(*it));
if (copyData){
setFirstms(newIdx, M.getFirstms(*it));
setLastms(newIdx, M.getLastms(*it));
}else{
setFirstms(newIdx, 0);
setLastms(newIdx, 0);
}
setBps(newIdx, M.getBps(*it));
setMaxBps(newIdx, M.getMaxBps(*it));
setFpks(newIdx, M.getFpks(*it));
@ -3241,8 +3246,8 @@ namespace DTSC{
uint32_t longest_cnt = 0;
DTSC::Keys Mkeys(keys(i));
uint32_t firstKey = Mkeys.getFirstValid();
uint32_t endKey = Mkeys.getEndValid() - 1;
for (int k = firstKey; k < endKey; k++){
uint32_t endKey = Mkeys.getEndValid();
for (uint32_t k = firstKey; k+1 < endKey; k++){
uint64_t kDur = Mkeys.getDuration(k);
uint64_t kParts = Mkeys.getParts(k);
if (!kDur){continue;}

View file

@ -280,12 +280,10 @@ void Util::packetSorter::dropTrack(size_t tid){
/// Removes the first packet from the sorter and inserts the given packet.
void Util::packetSorter::replaceFirst(const sortedPageInfo &pInfo){
if (dequeMode){
//in deque mode, insertion of the new packet is at the back
//this works, as a failure to retrieve a packet will swap the front entry to the back as well
dequeBuffer.pop_front();
if (dequeBuffer.size() && dequeBuffer.front().time > pInfo.time){
dequeBuffer.push_front(pInfo);
}else{
dequeBuffer.push_back(pInfo);
}
dequeBuffer.push_back(pInfo);
}else{
setBuffer.erase(setBuffer.begin());
setBuffer.insert(pInfo);
@ -328,6 +326,20 @@ void Util::packetSorter::getTrackList(std::set<size_t> &toFill) const{
}
}
/// Fills toFill with track IDs and current playback position of tracks that are in the sorter.
void Util::packetSorter::getTrackList(std::map<size_t, uint64_t> &toFill) const{
toFill.clear();
if (dequeMode){
for (std::deque<Util::sortedPageInfo>::const_iterator it = dequeBuffer.begin(); it != dequeBuffer.end(); ++it){
toFill[it->tid] = it->time;
}
}else{
for (std::set<Util::sortedPageInfo>::const_iterator it = setBuffer.begin(); it != setBuffer.end(); ++it){
toFill[it->tid] = it->time;
}
}
}
JSON::Value Util::getStreamConfig(const std::string &streamname){
JSON::Value result;
if (streamname.size() > 100){

View file

@ -75,6 +75,7 @@ namespace Util{
void moveFirstToEnd();
bool hasEntry(size_t tid) const;
void getTrackList(std::set<size_t> &toFill) const;
void getTrackList(std::map<size_t, uint64_t> &toFill) const;
void setSyncMode(bool synced);
bool getSyncMode() const;
private:

View file

@ -165,7 +165,7 @@ namespace Mist{
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
DTSC::Meta nM("", metaPack.getScan());
meta.reInit(streamName, false);
meta.merge(nM);
meta.merge(nM, true, false);
std::set<size_t> validTracks = M.getMySourceTracks(getpid());
userSelect.clear();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
@ -342,20 +342,39 @@ namespace Mist{
// userClient.keepAlive();
std::string cmd;
thisPacket.getString("cmd", cmd);
if (cmd != "reset"){
if (cmd == "reset"){
// Read next packet
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() != DTSC::DTSC_HEAD){
meta.clear();
continue;
}
DTSC::Meta nM("", thisPacket.getScan());
meta.merge(nM, true, false);
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
if (cmd == "error"){
thisPacket.getString("msg", cmd);
Util::logExitReason("%s", cmd.c_str());
thisPacket.null();
return;
}
if (cmd == "ping"){
thisPacket.reInit(srcConn);
JSON::Value prep;
prep["cmd"] = "ok";
prep["msg"] = "Pong!";
srcConn.SendNow("DTCM");
char sSize[4] ={0, 0, 0, 0};
Bit::htobl(sSize, prep.packedSize());
srcConn.SendNow(sSize, 4);
prep.sendTo(srcConn);
continue;
}
// Read next packet
INFO_MSG("Unhandled command: %s", cmd.c_str());
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() != DTSC::DTSC_HEAD){
meta.clear();
continue;
}
DTSC::Meta nM("", thisPacket.getScan());
meta.merge(nM, true, false);
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
continue;
}
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta nM("", thisPacket.getScan());
@ -364,7 +383,33 @@ namespace Mist{
continue; // parse the next packet before returning
}
thisTime = thisPacket.getTime();
thisIdx = thisPacket.getTrackId();
thisIdx = M.trackIDToIndex(thisPacket.getTrackId());
if (thisPacket.getFlag("keyframe") && M.trackValid(thisIdx)){
uint32_t shrtest_key = 0xFFFFFFFFul;
uint32_t longest_key = 0;
DTSC::Keys Mkeys(M.keys(thisIdx));
uint32_t firstKey = Mkeys.getFirstValid();
uint32_t endKey = Mkeys.getEndValid();
uint32_t checkKey = (endKey-firstKey <= 3)?firstKey:endKey-3;
for (uint32_t k = firstKey; k+1 < endKey; k++){
uint64_t kDur = Mkeys.getDuration(k);
if (!kDur){continue;}
if (kDur > longest_key && k >= checkKey){longest_key = kDur;}
if (kDur < shrtest_key){shrtest_key = kDur;}
}
if (longest_key > shrtest_key*2){
JSON::Value prep;
prep["cmd"] = "check_key_duration";
prep["id"] = thisPacket.getTrackId();
prep["duration"] = longest_key;
srcConn.SendNow("DTCM");
char sSize[4] ={0, 0, 0, 0};
Bit::htobl(sSize, prep.packedSize());
srcConn.SendNow(sSize, 4);
prep.sendTo(srcConn);
INFO_MSG("Key duration %" PRIu32 " is quite long - confirming with upstream source", longest_key);
}
}
return; // we have a packet
}
}

View file

@ -463,7 +463,7 @@ namespace Mist{
meta.reloadReplacedPagesIfNeeded();
bool autoSeek = buffer.size();
uint64_t seekTarget = currentTime();
uint64_t seekTarget = buffer.getSyncMode()?currentTime():0;
std::set<size_t> newSelects =
Util::wouldSelect(M, targetParams, capa, UA, autoSeek ? seekTarget : 0);
@ -482,9 +482,10 @@ namespace Mist{
}
std::set<size_t> oldSelects;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){
oldSelects.insert(it->first);
}
buffer.getTrackList(oldSelects);
std::map<size_t, uint64_t> seekTargets;
buffer.getTrackList(seekTargets);
//No changes? Abort and return false;
if (oldSelects == newSelects){return false;}
@ -510,6 +511,7 @@ namespace Mist{
WARN_MSG("Could not select track %zu, dropping track", *it);
newSelects.erase(*it);
userSelect.erase(*it);
continue;
}
}
@ -521,10 +523,16 @@ namespace Mist{
//After attempting to add/remove tracks, now no changes? Abort and return false;
if (oldSelects == newSelects){return false;}
if (autoSeek){
INFO_MSG("Automatically seeking to position %" PRIu64 " to resume playback", seekTarget);
seek(seekTarget);
buffer.clear();
INFO_MSG("Automatically seeking to resume playback");
for (std::set<size_t>::iterator it = newSelects.begin(); it != newSelects.end(); it++){
if (seekTargets.count(*it)){
seek(*it, seekTargets[*it], false);
}else{
seek(*it, 0, false);
}
}
}
return true;
}
@ -793,6 +801,11 @@ namespace Mist{
return false;
}
DTSC::Keys keys(M.keys(tid));
if (M.getLive() && !pos && !buffer.getSyncMode()){
uint64_t tmpTime = (M.getFirstms(tid) + M.getLastms(tid))/2;
uint32_t tmpKey = M.getKeyNumForTime(tid, tmpTime);
pos = keys.getTime(tmpKey);
}
uint32_t keyNum = M.getKeyNumForTime(tid, pos);
if (keyNum == INVALID_KEY_NUM){
FAIL_MSG("Attempted seek on empty track %zu", tid);
@ -827,8 +840,7 @@ namespace Mist{
tmp.time = tmpPack.getTime();
}
if (tmpPack){
HIGH_MSG("Sought to time %" PRIu64 " (yields a packet at %" PRIu64 "ms) in %s@%zu", tmp.time,
tmpPack.getTime(), streamName.c_str(), tid);
HIGH_MSG("Sought to time %" PRIu64 " in %s", tmp.time, curPage[tid].name.c_str());
tmp.partIndex = M.getPartIndex(tmpPack.getTime(), tmp.tid);
buffer.insert(tmp);
return true;
@ -862,7 +874,7 @@ namespace Mist{
void Output::initialSeek(){
if (!meta){return;}
uint64_t seekPos = 0;
if (meta.getLive()){
if (meta.getLive() && buffer.getSyncMode()){
size_t mainTrack = getMainSelectedTrack();
if (mainTrack == INVALID_TRACK_ID){return;}
DTSC::Keys keys(M.keys(mainTrack));
@ -1208,7 +1220,7 @@ namespace Mist{
/// Waits for the given amount of millis, increasing the realtime playback
/// related times as needed to keep smooth playback intact.
void Output::playbackSleep(uint64_t millis){
if (realTime && M.getLive()){
if (realTime && M.getLive() && buffer.getSyncMode()){
firstTime += millis;
extraKeepAway += millis;
}
@ -1321,7 +1333,7 @@ namespace Mist{
if (firstPacketTime == 0xFFFFFFFFFFFFFFFFull){firstPacketTime = lastPacketTime;}
// slow down processing, if real time speed is wanted
if (realTime){
if (realTime && buffer.getSyncMode()){
uint8_t i = 6;
while (--i && thisPacket.getTime() > (((Util::bootMS() - firstTime) * 1000) / realTime + maxSkipAhead) &&
keepGoing()){
@ -1476,9 +1488,14 @@ namespace Mist{
// depending on whether this is probably bad and the current debug level, print a message
size_t printLevel = (probablyBad ? DLVL_WARN : DLVL_INFO);
const Comms::Users &usr = userSelect.at(trackId);
DEBUG_MSG(printLevel, "Dropping %s track %zu@k%zu (nextP=%" PRIu64 ", lastP=%" PRIu64 "): %s",
meta.getCodec(trackId).c_str(), trackId, usr.getKeyNum() + 1,
pageNumForKey(trackId, usr.getKeyNum() + 1), pageNumMax(trackId), reason.c_str());
if (!usr){
DEBUG_MSG(printLevel, "Dropping %s track %zu (lastP=%" PRIu64 "): %s",
meta.getCodec(trackId).c_str(), trackId, pageNumMax(trackId), reason.c_str());
}else{
DEBUG_MSG(printLevel, "Dropping %s track %zu@k%zu (nextP=%" PRIu64 ", lastP=%" PRIu64 "): %s",
meta.getCodec(trackId).c_str(), trackId, usr.getKeyNum() + 1,
pageNumForKey(trackId, usr.getKeyNum() + 1), pageNumMax(trackId), reason.c_str());
}
// now actually drop the track from the buffer
buffer.dropTrack(trackId);
userSelect.erase(trackId);
@ -1576,42 +1593,12 @@ namespace Mist{
return false;
}
Util::sortedPageInfo nxt = *(buffer.begin());
Util::sortedPageInfo nxt;
if (meta.reloadReplacedPagesIfNeeded()){return false;}
if (!M.getValidTracks().count(nxt.tid)){
dropTrack(nxt.tid, "disappeared from metadata");
return false;
}
// if we're going to read past the end of the data page, load the next page
// this only happens for VoD
if (nxt.offset >= curPage[nxt.tid].len ||
(!memcmp(curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4))){
if (!M.getLive() && nxt.time >= M.getLastms(nxt.tid)){
dropTrack(nxt.tid, "end of non-live track reached", false);
return false;
}
if (M.getPageNumberForTime(nxt.tid, nxt.time) != currentPage[nxt.tid]){
loadPageForKey(nxt.tid, M.getPageNumberForTime(nxt.tid, nxt.time));
nxt.offset = 0;
//Only read the next time if the page load succeeded and there is a packet to read from
if (curPage[nxt.tid].mapped && curPage[nxt.tid].mapped[0] == 'D'){
nxt.time = getDTSCTime(curPage[nxt.tid].mapped, 0);
}
buffer.replaceFirst(nxt);
return false;
}
dropTrack(nxt.tid, "VoD page load failure");
return false;
}
// We know this packet will be valid, pre-load it so we know its length
DTSC::Packet preLoad(curPage[nxt.tid].mapped + nxt.offset, 0, true);
uint64_t nextTime = 0;
uint64_t nextTime;
size_t trackTries = 0;
//In case we're not in sync mode, we might have to retry a few times
for (size_t trackTries = 0; trackTries < buffer.size(); ++trackTries){
for (; trackTries < buffer.size(); ++trackTries){
nxt = *(buffer.begin());
@ -1639,8 +1626,13 @@ namespace Mist{
buffer.replaceFirst(nxt);
return false;
}
INFO_MSG("Invalid packet: no data @%" PRIu64 " for time %" PRIu64 " on track %zu", nxt.offset, nxt.time, nxt.tid);
dropTrack(nxt.tid, "VoD page load failure");
if (nxt.offset >= curPage[nxt.tid].len){
INFO_MSG("Reading past end of page %s: %" PRIu64 " > %" PRIu64 " for time %" PRIu64 " on track %zu", curPage[nxt.tid].name.c_str(), nxt.offset, curPage[nxt.tid].len, nxt.time, nxt.tid);
dropTrack(nxt.tid, "reading past end of page");
}else{
INFO_MSG("Invalid packet: no data @%" PRIu64 " in %s for time %" PRIu64 " on track %zu", nxt.offset, curPage[nxt.tid].name.c_str(), nxt.time, nxt.tid);
dropTrack(nxt.tid, "zero packet");
}
return false;
}
// We know this packet will be valid, pre-load it so we know its length
@ -1662,88 +1654,101 @@ namespace Mist{
dropTrack(nxt.tid, errMsg.str().c_str());
return false;
}
}else{
//no next packet yet!
//Check if this is the last packet of a VoD stream. Return success and drop the track.
if (!M.getLive() && nxt.time >= M.getLastms(nxt.tid)){
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
thisIdx = nxt.tid;
dropTrack(nxt.tid, "end of non-live track reached", false);
return true;
}
uint32_t thisKey = M.getKeyNumForTime(nxt.tid, nxt.time);
//Check if there exists a different page for the next key
uint32_t nextKeyPage = INVALID_KEY_NUM;
//Make sure we only try to read the page for the next key if it actually should be available
DTSC::Keys keys(M.keys(nxt.tid));
if (keys.getEndValid() >= thisKey+1){nextKeyPage = M.getPageNumberForKey(nxt.tid, thisKey + 1);}
if (nextKeyPage != INVALID_KEY_NUM && nextKeyPage != currentPage[nxt.tid]){
// If so, the next key is our next packet
nextTime = keys.getTime(thisKey + 1);
break;//Packet valid!
}
//If the next packet should've been before the current packet, something is wrong. Abort, abort!
if (nextTime < nxt.time){
std::stringstream errMsg;
errMsg << "next key (" << (thisKey+1) << ") time " << nextTime << " but current time " << nxt.time;
errMsg << "; currPage=" << currentPage[nxt.tid] << ", nxtPage=" << nextKeyPage;
errMsg << ", firstKey=" << keys.getFirstValid() << ", endKey=" << keys.getEndValid();
dropTrack(nxt.tid, errMsg.str().c_str());
return false;
}
}else{
if (!buffer.getSyncMode() && trackTries < buffer.size()-1){
//We shuffle the just-tried packet back to the end of the queue, then retry up to buffer.size() times
buffer.moveFirstToEnd();
continue;
}
//Okay, there's no next page yet, and no next packet on this page either.
//That means we're waiting for data to show up, somewhere.
// after ~25 seconds, give up and drop the track.
if (++emptyCount >= dataWaitTimeout){
dropTrack(nxt.tid, "EOP: data wait timeout");
return false;
}
//every ~1 second, check if the stream is not offline
if (emptyCount % 100 == 0 && M.getLive() && Util::getStreamStatus(streamName) == STRMSTAT_OFF){
Util::logExitReason("Stream source shut down");
thisPacket.null();
return true;
}
//every ~16 seconds, reconnect to metadata
if (emptyCount % 1600 == 0){
INFO_MSG("Reconnecting to input; track %zu key %" PRIu32 " is on page %" PRIu32 " and we're currently serving %" PRIu32 " from %" PRIu32, nxt.tid, thisKey+1, nextKeyPage, thisKey, currentPage[nxt.tid]);
reconnect();
if (!meta){
onFail("Could not connect to stream data", true);
thisPacket.null();
return true;
}
// if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile.
if (!meta){
Util::logExitReason("Attempted reconnect to source failed");
thisPacket.null();
return true;
}
return false;//no sleep after reconnect
}
//Fine! We didn't want a packet, anyway. Let's try again later.
playbackSleep(10);
//no next packet on the current page
//Check if this is the last packet of a VoD stream. Return success and drop the track.
if (!M.getLive() && nxt.time >= M.getLastms(nxt.tid)){
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
thisIdx = nxt.tid;
dropTrack(nxt.tid, "end of non-live track reached", false);
return true;
}
//Check if there exists a different page for the next key
uint32_t thisKey = M.getKeyNumForTime(nxt.tid, nxt.time);
uint32_t nextKeyPage = INVALID_KEY_NUM;
//Make sure we only try to read the page for the next key if it actually should be available
DTSC::Keys keys(M.keys(nxt.tid));
if (keys.getEndValid() >= thisKey+1){nextKeyPage = M.getPageNumberForKey(nxt.tid, thisKey + 1);}
if (nextKeyPage != INVALID_KEY_NUM && nextKeyPage != currentPage[nxt.tid]){
// If so, the next key is our next packet
nextTime = keys.getTime(thisKey + 1);
//If the next packet should've been before the current packet, something is wrong. Abort, abort!
if (nextTime < nxt.time){
std::stringstream errMsg;
errMsg << "next key (" << (thisKey+1) << ") time " << nextTime << " but current time " << nxt.time;
errMsg << "; currPage=" << currentPage[nxt.tid] << ", nxtPage=" << nextKeyPage;
errMsg << ", firstKey=" << keys.getFirstValid() << ", endKey=" << keys.getEndValid();
dropTrack(nxt.tid, errMsg.str().c_str());
return false;
}
break;//Valid packet!
}
//Okay, there's no next page yet, and no next packet on this page either.
//That means we're waiting for data to show up, somewhere.
//In non-sync mode, shuffle the just-tried packet to the end of queue and retry
if (!buffer.getSyncMode()){
buffer.moveFirstToEnd();
continue;
}
// in sync mode, after ~25 seconds, give up and drop the track.
if (++emptyCount >= dataWaitTimeout){
dropTrack(nxt.tid, "EOP: data wait timeout");
return false;
}
//every ~1 second, check if the stream is not offline
if (emptyCount % 100 == 0 && M.getLive() && Util::getStreamStatus(streamName) == STRMSTAT_OFF){
Util::logExitReason("Stream source shut down");
thisPacket.null();
return true;
}
//every ~16 seconds, reconnect to metadata
if (emptyCount % 1600 == 0){
INFO_MSG("Reconnecting to input; track %zu key %" PRIu32 " is on page %" PRIu32 " and we're currently serving %" PRIu32 " from %" PRIu32, nxt.tid, thisKey+1, nextKeyPage, thisKey, currentPage[nxt.tid]);
reconnect();
if (!meta){
onFail("Could not connect to stream data", true);
thisPacket.null();
return true;
}
// if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile.
if (!meta){
Util::logExitReason("Attempted reconnect to source failed");
thisPacket.null();
return true;
}
return false;//no sleep after reconnect
}
//Fine! We didn't want a packet, anyway. Let's try again later.
playbackSleep(10);
return false;
}
if (trackTries == buffer.size()){
//Fine! We didn't want a packet, anyway. Let's try again later.
playbackSleep(10);
return false;
}
// we've handled all special cases - at this point the packet should exist
// let's load it
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
thisIdx = nxt.tid;
thisTime = thisPacket.getTime();
// if it failed, drop the track and continue
if (!thisPacket){
dropTrack(nxt.tid, "packet load failure");
return false;
}
emptyCount = 0; // valid packet - reset empty counter
thisIdx = nxt.tid;
thisTime = thisPacket.getTime();
if (!userSelect[nxt.tid]){
dropTrack(nxt.tid, "track is not alive!");

View file

@ -115,47 +115,6 @@ namespace Mist{
std::string OutDTSC::getStatsName(){return (pushing ? "INPUT:DTSC" : "OUTPUT:DTSC");}
/// Seeks to the first sync'ed keyframe of the main track.
/// Aborts if there is no main track or it has no keyframes.
void OutDTSC::initialSeek(){
uint64_t seekPos = 0;
if (M.getLive()){
size_t mainTrack = getMainSelectedTrack();
// cancel if there are no keys in the main track
if (mainTrack == INVALID_TRACK_ID){return;}
DTSC::Keys keys(M.keys(mainTrack));
if (!keys.getValidCount()){return;}
// seek to the oldest keyframe
std::set<size_t> validTracks = M.getValidTracks();
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){
seekPos = keys.getTime(i);
bool good = true;
// check if all tracks have data for this point in time
for (std::map<size_t, Comms::Users>::iterator ti = userSelect.begin(); ti != userSelect.end(); ++ti){
if (mainTrack == ti->first){continue;}// skip self
if (!validTracks.count(ti->first)){
HIGH_MSG("Skipping track %zu, not in tracks", ti->first);
continue;
}// ignore missing tracks
if (M.getLastms(ti->first) == M.getFirstms(ti->first)){
HIGH_MSG("Skipping track %zu, last equals first", ti->first);
continue;
}// ignore point-tracks
if (M.getFirstms(ti->first) > seekPos){
good = false;
break;
}
HIGH_MSG("Track %zu is good", ti->first);
}
// if yes, seek here
if (good){break;}
}
}
MEDIUM_MSG("Initial seek to %" PRIu64 "ms", seekPos);
seek(seekPos);
}
void OutDTSC::sendNext(){
// If selectable tracks changed, set sentHeader to false to force it to send init data
static uint64_t lastMeta = 0;
@ -167,7 +126,7 @@ namespace Mist{
return;
}
}
DTSC::Packet p(thisPacket, thisIdx + 1);
DTSC::Packet p(thisPacket, thisIdx+1);
myConn.SendNow(p.getData(), p.getDataLen());
lastActive = Util::epoch();
}
@ -201,6 +160,10 @@ namespace Mist{
std::string dataPacket = myConn.Received().remove(rSize);
DTSC::Scan dScan((char *)dataPacket.data(), rSize);
HIGH_MSG("Received DTCM: %s", dScan.asJSON().toString().c_str());
if (dScan.getMember("cmd").asString() == "ok"){
INFO_MSG("Remote OK: %s", dScan.getMember("msg").asString().c_str());
continue;
}
if (dScan.getMember("cmd").asString() == "push"){
handlePush(dScan);
continue;
@ -230,6 +193,29 @@ namespace Mist{
sendOk("Internal state reset");
continue;
}
if (dScan.getMember("cmd").asString() == "check_key_duration"){
size_t idx = dScan.getMember("id").asInt() - 1;
size_t dur = dScan.getMember("duration").asInt();
if (!M.trackValid(idx)){
ERROR_MSG("Cannot check key duration %zu for track %zu: not valid", dur, idx);
return;
}
uint32_t longest_key = 0;
DTSC::Keys Mkeys(M.keys(idx));
uint32_t firstKey = Mkeys.getFirstValid();
uint32_t endKey = Mkeys.getEndValid();
for (uint32_t k = firstKey; k+1 < endKey; k++){
uint64_t kDur = Mkeys.getDuration(k);
if (kDur > longest_key){longest_key = kDur;}
}
if (dur > longest_key*1.2){
onFail("Key duration mismatch; disconnecting "+myConn.getHost()+" to recover ("+JSON::Value(longest_key).asString()+" -> "+JSON::Value(dur).asString()+")", true);
return;
}else{
sendOk("Key duration matches upstream");
}
continue;
}
WARN_MSG("Unhandled DTCM command: '%s'", dScan.getMember("cmd").asString().c_str());
}else if (myConn.Received().copy(4) == "DTSC"){
// Header packet

View file

@ -11,7 +11,6 @@ namespace Mist{
void onRequest();
void sendNext();
void sendHeader();
void initialSeek();
static bool listenMode(){return !(config->getString("target").size());}
void onFail(const std::string &msg, bool critical = false);
void stats(bool force = false);

View file

@ -14,6 +14,11 @@ namespace Mist{
//}
parseData = true;
wantRequest = false;
if (config->getBool("sync")){
setSyncMode(true);
}else{
setSyncMode(false);
}
initialize();
initialSeek();
sortSet.clear();
@ -50,19 +55,7 @@ namespace Mist{
seek(seekPoint);
}
}
}
void OutSanityCheck::initialSeek(){
if (M.getLive()){
liveSeek();
if (getKeyFrame() && thisPacket){
sendNext();
INFO_MSG("Initial sent!");
}
firstTime = Util::getMS() - currentTime();
}else{
Output::initialSeek();
}
}
void OutSanityCheck::init(Util::Config *cfg){
@ -74,8 +67,11 @@ namespace Mist{
"\"stream\",\"help\":\"The name of the stream "
"that this connector will transmit.\"}"));
cfg->addOption(
"seek", JSON::fromString("{\"arg\":\"string\",\"short\":\"S\",\"long\":\"seek\",\"help\":"
"seek", JSON::fromString("{\"arg\":\"string\",\"short\":\"k\",\"long\":\"seek\",\"help\":"
"\"Time in ms to check from - by default start of stream\"}"));
cfg->addOption(
"sync", JSON::fromString("{\"short\":\"y\",\"long\":\"sync\",\"help\":"
"\"Retrieve tracks in sync (default async)\"}"));
cfg->addBasicConnectorOptions(capa);
config = cfg;
}
@ -89,36 +85,23 @@ namespace Mist{
}
*/
#define printTime(t) std::setfill('0') << std::setw(2) << (t / 3600000) << ":" << std::setw(2) << ((t % 3600000) / 60000) << ":" << std::setw(2) << ((t % 60000) / 1000) << "." << std::setw(3) << (t % 1000)
void OutSanityCheck::sendNext(){
static std::map<size_t, uint64_t> trkTime;
if (M.getLive()){
static uint64_t prevTime = 0;
static size_t prevTrack = 0;
uint64_t t = thisPacket.getTime();
if (t < prevTime){
std::cout << "Time error: ";
std::cout << std::setfill('0') << std::setw(2) << (t / 3600000) << ":" << std::setw(2)
<< ((t % 3600000) / 60000) << ":" << std::setw(2) << ((t % 60000) / 1000) << "."
<< std::setw(3) << (t % 1000);
std::cout << " (" << thisIdx << ")";
std::cout << " < ";
std::cout << std::setfill('0') << std::setw(2) << (prevTime / 3600000) << ":"
<< std::setw(2) << ((prevTime % 3600000) / 60000) << ":" << std::setw(2)
<< ((prevTime % 60000) / 1000) << "." << std::setw(3) << (prevTime % 1000);
std::cout << " (" << prevTrack << ")";
std::cout << std::endl << std::endl;
if (thisTime < trkTime[thisIdx]){
std::cout << "Time error in track " << thisIdx << ": ";
std::cout << printTime(thisTime) << " < " << printTime(trkTime[thisIdx]) << std::endl << std::endl;
}else{
prevTime = t;
prevTrack = thisIdx;
trkTime[thisIdx] = thisTime;
}
std::cout << "\033[A" << std::setfill('0') << std::setw(2) << (t / 3600000) << ":"
<< std::setw(2) << ((t % 3600000) / 60000) << ":" << std::setw(2)
<< ((t % 60000) / 1000) << "." << std::setw(3) << (t % 1000) << " ";
uint32_t mainTrack = M.mainTrack();
if (mainTrack == INVALID_TRACK_ID){return;}
t = M.getLastms(mainTrack);
std::cout << std::setfill('0') << std::setw(2) << (t / 3600000) << ":" << std::setw(2)
<< ((t % 3600000) / 60000) << ":" << std::setw(2) << ((t % 60000) / 1000) << "."
<< std::setw(3) << (t % 1000) << " " << std::endl;
std::cout << "\033[A";
for (std::map<size_t, uint64_t>::iterator it = trkTime.begin(); it != trkTime.end(); ++it){
uint64_t t = M.getLastms(it->first);
std::cout << it->first << ":" << printTime(it->second) << "/" << printTime(t) << ", ";
}
std::cout << std::endl;
return;
}

View file

@ -27,7 +27,6 @@ namespace Mist{
OutSanityCheck(Socket::Connection &conn);
static void init(Util::Config *cfg);
void sendNext();
void initialSeek();
static bool listenMode(){return false;}
protected: