From b71765e6bc61ef6e0fc9315a62580a2aceb4f349 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 28 Sep 2016 19:15:30 +0200 Subject: [PATCH] Buffer now reports JSON blob with stream details and possible issues, improved DRY/RECOVER behaviour --- src/input/input_buffer.cpp | 86 ++++++++++++++++++++++++++++++-------- src/input/input_buffer.h | 1 + 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/input/input_buffer.cpp b/src/input/input_buffer.cpp index 4ccf7113..a503151b 100644 --- a/src/input/input_buffer.cpp +++ b/src/input/input_buffer.cpp @@ -203,11 +203,62 @@ namespace Mist { } } + /// Fills the details variable with details about the current buffer contents + void inputBuffer::fillBufferDetails(JSON::Value & details){ + //clear the reference of old data, first + details.null(); + bool hasH264 = false; + bool hasAAC = false; + std::stringstream issues; + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + JSON::Value & track = details[it->second.getWritableIdentifier()]; + track["kbits"] = (long long)((double)it->second.bps * 8 / 1024); + track["codec"] = it->second.codec; + uint32_t shrtest_key = 0xFFFFFFFFul; + uint32_t longest_key = 0; + uint32_t shrtest_prt = 0xFFFFFFFFul; + uint32_t longest_prt = 0; + uint32_t shrtest_cnt = 0xFFFFFFFFul; + uint32_t longest_cnt = 0; + for (std::deque::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){ + if (!k->getLength()){continue;} + if (k->getLength() > longest_key){longest_key = k->getLength();} + if (k->getLength() < shrtest_key){shrtest_key = k->getLength();} + if (k->getParts() > longest_cnt){longest_cnt = k->getParts();} + if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();} + if ((k->getLength()/k->getParts()) > longest_prt){longest_prt = (k->getLength()/k->getParts());} + if ((k->getLength()/k->getParts()) < shrtest_prt){shrtest_prt = (k->getLength()/k->getParts());} + } + track["keys"]["ms_min"] = (long long)shrtest_key; + track["keys"]["ms_max"] = (long long)longest_key; + track["keys"]["frame_ms_min"] = (long long)shrtest_prt; + track["keys"]["frame_ms_max"] = (long long)longest_prt; + track["keys"]["frames_min"] = (long long)shrtest_cnt; + track["keys"]["frames_max"] = (long long)longest_cnt; + if (longest_prt > 500){issues << "unstable connection (" << longest_prt << "ms " << it->second.codec << " frame)! ";} + if (shrtest_cnt < 6){issues << "unstable connection (" << shrtest_cnt << " " << it->second.codec << " frames in key)! ";} + if (it->second.codec == "AAC"){hasAAC = true;} + if (it->second.codec == "H264"){hasH264 = true;} + if (it->second.type=="video"){ + track["width"] = (long long)it->second.width; + track["height"] = (long long)it->second.height; + track["fpks"] = it->second.fpks; + } + } + if ((hasAAC || hasH264) && myMeta.tracks.size() > 1){ + if (!hasAAC){issues << "HLS no audio!";} + if (!hasH264){issues << "HLS no video!";} + } + if (issues.str().size()){details["issues"] = issues.str();} + //return is by reference + } + /// \triggers /// The `"STREAM_BUFFER"` trigger is stream-specific, and is ran whenever the buffer changes state between playable (FULL) or not (EMPTY). It cannot be cancelled. It is possible to receive multiple EMPTY calls without FULL calls in between, as EMPTY is always generated when a stream is unloaded from memory, even if this stream never reached playable state in the first place (e.g. a broadcast was cancelled before filling enough buffer to be playable). Its payload is: /// ~~~~~~~~~~~~~~~ /// streamname - /// FULL or EMPTY (depending on current state) + /// FULL, EMPTY, DRY or RECOVER (depending on current state) + /// Detected issues in string format, or empty string if no issues /// ~~~~~~~~~~~~~~~ void inputBuffer::updateMeta() { static bool wentDry = false; @@ -239,25 +290,26 @@ namespace Mist { } } /*LTS-START*/ - if (fragCount >= FRAG_BOOT && fragCount != 0xFFFFull && (lastFragCount == 0xFFFFull || lastFragCount < FRAG_BOOT)) { - if (Triggers::shouldTrigger("STREAM_BUFFER")) { - std::string payload; - if (wentDry){ - payload = config->getString("streamname") + "\nRECOVER"; - }else{ - payload = config->getString("streamname") + "\nFULL"; + if (fragCount >= FRAG_BOOT && fragCount != 0xFFFFull && Triggers::shouldTrigger("STREAM_BUFFER")){ + JSON::Value stream_details; + fillBufferDetails(stream_details); + if (lastFragCount == 0xFFFFull) { + std::string payload = config->getString("streamname") + "\nFULL\n" + stream_details.toString(); + Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); + }else{ + if (stream_details.isMember("issues") != wentDry){ + if (stream_details.isMember("issues")){ + std::string payload = config->getString("streamname") + "\nDRY\n" + stream_details.toString(); + Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); + }else{ + std::string payload = config->getString("streamname") + "\nRECOVER\n" + stream_details.toString(); + Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); + } } - Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); } + wentDry = stream_details.isMember("issues"); + lastFragCount = fragCount; } - if ((fragCount < FRAG_BOOT || fragCount == 0xFFFFull) && (lastFragCount >= FRAG_BOOT && lastFragCount != 0xFFFFull)) { - if (Triggers::shouldTrigger("STREAM_BUFFER")) { - std::string payload = config->getString("streamname") + "\nDRY"; - Triggers::doTrigger("STREAM_BUFFER", payload, config->getString("streamname")); - wentDry = true; - } - } - lastFragCount = fragCount; /*LTS-END*/ myMeta.bufferWindow = lastms - firstms; myMeta.vod = false; diff --git a/src/input/input_buffer.h b/src/input/input_buffer.h index c8d18d0a..d58cad34 100644 --- a/src/input/input_buffer.h +++ b/src/input/input_buffer.h @@ -11,6 +11,7 @@ namespace Mist { ~inputBuffer(); void onCrash(); private: + void fillBufferDetails(JSON::Value & details); unsigned int bufferTime; unsigned int cutTime; unsigned int segmentSize; /*LTS*/