From 4be57ab043adf333f93442a08b39957a3260b873 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 6 Jan 2017 02:34:47 +0100 Subject: [PATCH 1/6] Updated/fixed MP4 elst box implementation --- lib/mp4_generic.cpp | 62 ++++++++++++++++++++++++--------------------- lib/mp4_generic.h | 16 ++++++------ 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/lib/mp4_generic.cpp b/lib/mp4_generic.cpp index 8c67fe61..e37c6748 100644 --- a/lib/mp4_generic.cpp +++ b/lib/mp4_generic.cpp @@ -3161,67 +3161,67 @@ namespace MP4 { return getInt32(4); } - void ELST::setSegmentDuration(uint64_t newVal) { + void ELST::setSegmentDuration(uint32_t cnt, uint64_t newVal) { if (getVersion() == 1) { - setInt64(newVal, 8); + setInt64(newVal, 28*cnt+8); } else { - setInt32(newVal, 8); + setInt32(newVal, 20*cnt+8); } } - uint64_t ELST::getSegmentDuration() { + uint64_t ELST::getSegmentDuration(uint32_t cnt) { if (getVersion() == 1) { - return getInt64(8); + return getInt64(28*cnt+8); } else { - return getInt32(8); + return getInt32(20*cnt+8); } } - void ELST::setMediaTime(uint64_t newVal) { + void ELST::setMediaTime(uint32_t cnt, uint64_t newVal) { if (getVersion() == 1) { - setInt64(newVal, 16); + setInt64(newVal, 28*cnt+16); } else { - setInt32(newVal, 12); + setInt32(newVal, 20*cnt+12); } } - uint64_t ELST::getMediaTime() { + uint64_t ELST::getMediaTime(uint32_t cnt) { if (getVersion() == 1) { - return getInt64(16); + return getInt64(28*cnt+16); } else { - return getInt32(12); + return getInt32(20*cnt+12); } } - void ELST::setMediaRateInteger(uint16_t newVal) { + void ELST::setMediaRateInteger(uint32_t cnt, uint16_t newVal) { if (getVersion() == 1) { - setInt16(newVal, 24); + setInt16(newVal, 28*cnt+24); } else { - setInt16(newVal, 16); + setInt16(newVal, 20*cnt+16); } } - uint16_t ELST::getMediaRateInteger() { + uint16_t ELST::getMediaRateInteger(uint32_t cnt) { if (getVersion() == 1) { - return getInt16(24); + return getInt16(28*cnt+24); } else { - return getInt16(16); + return getInt16(20*cnt+16); } } - void ELST::setMediaRateFraction(uint16_t newVal) { + void ELST::setMediaRateFraction(uint32_t cnt, uint16_t newVal) { if (getVersion() == 1) { - setInt16(newVal, 26); + setInt16(newVal, 28*cnt+26); } else { - setInt16(newVal, 18); + setInt16(newVal, 20*cnt+18); } } - uint16_t ELST::getMediaRateFraction() { + uint16_t ELST::getMediaRateFraction(uint32_t cnt) { if (getVersion() == 1) { - return getInt16(26); + return getInt16(28*cnt+26); } else { - return getInt16(18); + return getInt16(20*cnt+18); } } @@ -3229,11 +3229,15 @@ namespace MP4 { std::stringstream r; r << std::string(indent, ' ') << "[elst] Edit List Box (" << boxedSize() << ")" << std::endl; r << fullBox::toPrettyString(indent); - r << std::string(indent + 1, ' ') << "Count: " << getCount() << std::endl; - r << std::string(indent + 1, ' ') << "SegmentDuration: " << getSegmentDuration() << std::endl; - r << std::string(indent + 1, ' ') << "MediaTime: " << getMediaTime() << std::endl; - r << std::string(indent + 1, ' ') << "MediaRateInteger: " << getMediaRateInteger() << std::endl; - r << std::string(indent + 1, ' ') << "MediaRateFraction: " << getMediaRateFraction() << std::endl; + uint32_t cnt = getCount(); + r << std::string(indent + 1, ' ') << "Count: " << cnt << std::endl; + for (uint32_t i = 0; i < cnt; ++i){ + r << std::string(indent + 1, ' ') << "[Entry " << i << "] " << std::endl; + r << std::string(indent + 2, ' ') << "SegmentDuration: " << getSegmentDuration(i) << std::endl; + r << std::string(indent + 2, ' ') << "MediaTime: " << getMediaTime(i) << std::endl; + r << std::string(indent + 2, ' ') << "MediaRateInteger: " << getMediaRateInteger(i) << std::endl; + r << std::string(indent + 2, ' ') << "MediaRateFraction: " << getMediaRateFraction(i) << std::endl; + } return r.str(); } } diff --git a/lib/mp4_generic.h b/lib/mp4_generic.h index 82c798b7..a31e749e 100644 --- a/lib/mp4_generic.h +++ b/lib/mp4_generic.h @@ -704,14 +704,14 @@ namespace MP4 { ELST(); void setCount(uint32_t newVal); uint32_t getCount(); - void setSegmentDuration(uint64_t newVal); - uint64_t getSegmentDuration(); - void setMediaTime(uint64_t newVal); - uint64_t getMediaTime(); - void setMediaRateInteger(uint16_t newVal); - uint16_t getMediaRateInteger(); - void setMediaRateFraction(uint16_t newVal); - uint16_t getMediaRateFraction(); + void setSegmentDuration(uint32_t cnt, uint64_t newVal); + uint64_t getSegmentDuration(uint32_t cnt); + void setMediaTime(uint32_t cnt, uint64_t newVal); + uint64_t getMediaTime(uint32_t cnt); + void setMediaRateInteger(uint32_t cnt, uint16_t newVal); + uint16_t getMediaRateInteger(uint32_t cnt); + void setMediaRateFraction(uint32_t cnt, uint16_t newVal); + uint16_t getMediaRateFraction(uint32_t cnt); std::string toPrettyString(uint32_t indent = 0); }; } From ba2ef09a7e91fb132514d9c7647b3d18aabb6fa3 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 10 Jan 2017 12:33:13 +0100 Subject: [PATCH 2/6] Replaced completeKeysOnly variable by more versatile needsLookAhead variable --- src/output/output.cpp | 63 +++++++++++++++++-------------------------- src/output/output.h | 2 +- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/output/output.cpp b/src/output/output.cpp index a65dd1da..f67ca2a4 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -13,10 +13,6 @@ #include #include "output.h" -#ifndef MIN_DELAY -#define MIN_DELAY 2500 -#endif - namespace Mist{ JSON::Value Output::capa = JSON::Value(); @@ -43,7 +39,7 @@ namespace Mist{ sought = false; isInitialized = false; isBlocking = false; - completeKeysOnly = false; + needsLookAhead = 0; lastStats = 0; maxSkipAhead = 7500; minSkipAhead = 5000; @@ -621,7 +617,7 @@ namespace Mist{ /// This function decides where in the stream initial playback starts. /// The default implementation calls seek(0) for VoD. - /// For live, it seeks to the last sync'ed keyframe of the main track, no closer than MIN_DELAY ms from the end. + /// For live, it seeks to the last sync'ed keyframe of the main track, no closer than needsLookAhead ms from the end. /// Unless lastms < 5000, then it seeks to the first keyframe of the main track. /// Aborts if there is no main track or it has no keyframes. void Output::initialSeek(){ @@ -637,7 +633,7 @@ namespace Mist{ bool good = true; //check if all tracks have data for this point in time for (std::set::iterator ti = selectedTracks.begin(); ti != selectedTracks.end(); ++ti){ - if (myMeta.tracks[*ti].lastms < seekPos+MIN_DELAY){good = false; break;} + if (myMeta.tracks[*ti].lastms < seekPos+needsLookAhead){good = false; break;} if (mainTrack == *ti){continue;}//skip self if (!myMeta.tracks.count(*ti)){ HIGH_MSG("Skipping track %lu, not in tracks", *ti); @@ -701,41 +697,32 @@ namespace Mist{ } } - //delay the stream until its current keyframe is complete, if only complete keys wanted - if (completeKeysOnly){ - bool completeKeyReady = false; - int timeoutTries = 40;//wait default 250ms*40=10 seconds - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.keys.size() >1){ - int thisTimeoutTries = ((it->second.lastms - it->second.firstms) / (it->second.keys.size()-1)) / 125; - if (thisTimeoutTries > timeoutTries) timeoutTries = thisTimeoutTries; - } - } - unsigned int mTrack = getMainSelectedTrack(); - while(!completeKeyReady && timeoutTries>0){ - if (!myMeta.tracks[mTrack].keys.size() || myMeta.tracks[mTrack].keys.rbegin()->getTime() <= thisPacket.getTime()){ - completeKeyReady = false; - }else{ - DTSC::Key & mustHaveKey = myMeta.tracks[mTrack].getKey(getKeyForTime(mTrack, thisPacket.getTime())); - unsigned long long mustHaveTime = mustHaveKey.getTime() + mustHaveKey.getLength(); - completeKeyReady = true; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (myMeta.tracks[*it].lastms < mustHaveTime){ - completeKeyReady = false; - break; + //delay the stream until metadata has caught up, if needed + if (needsLookAhead){ + //we sleep in 250ms increments, or less if the lookahead time itself is less + uint32_t sleepTime = std::min((uint32_t)250, needsLookAhead); + //wait at most double the look ahead time, plus ten seconds + uint32_t timeoutTries = (needsLookAhead / sleepTime) * 2 + (10000/sleepTime); + uint64_t needsTime = thisPacket.getTime() + needsLookAhead; + while(--timeoutTries){ + bool lookReady = true; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (myMeta.tracks[*it].lastms <= needsTime){ + if (timeoutTries == 1){ + WARN_MSG("Track %lu: %llu <= %llu", *it, myMeta.tracks[*it].lastms, needsTime); } + lookReady = false; + break; } } - if (!completeKeyReady){ - timeoutTries--;//we count down - stats(); - Util::wait(250); - updateMeta(); - } + if (lookReady){break;} + Util::wait(sleepTime); + stats(); + updateMeta(); } - if (timeoutTries<=0){ - WARN_MSG("Waiting for key frame timed out"); - completeKeysOnly = false; + if (!timeoutTries){ + WARN_MSG("Waiting for lookahead timed out - resetting lookahead!"); + needsLookAhead = 0; } } diff --git a/src/output/output.h b/src/output/output.h index 6e8fd266..5580542d 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -99,7 +99,7 @@ namespace Mist { unsigned int maxSkipAhead;///< Maximum ms that we will go ahead of the intended timestamps. unsigned int minSkipAhead;///< Minimum ms that we will go ahead of the intended timestamps. unsigned int realTime;///< Playback speed in ms of data per second. eg: 0 is infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed. - bool completeKeysOnly;///< Bool if we send whole keys only, so the metadata is complete and the output knows in advance what will be sent. + uint32_t needsLookAhead;///< Amount of millis we need to be able to look ahead in the metadata //Read/write status variables Socket::Connection & myConn;///< Connection to the client. From 8f38872ffc20b30c858e12c05039bab0796961a3 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 6 Jan 2017 19:18:03 +0100 Subject: [PATCH 3/6] Fixed DTSC input bug with ping commands --- src/input/input_dtsc.cpp | 115 +++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/src/input/input_dtsc.cpp b/src/input/input_dtsc.cpp index 236304d1..6a5dee61 100644 --- a/src/input/input_dtsc.cpp +++ b/src/input/input_dtsc.cpp @@ -233,68 +233,79 @@ namespace Mist { void inputDTSC::getNext(bool smart) { if (!needsLock()){ thisPacket.reInit(srcConn); - if (thisPacket.getVersion() == DTSC::DTCM){ - nProxy.userClient.keepAlive(); - std::string cmd; - thisPacket.getString("cmd", cmd); - if (cmd == "reset"){ - //Read next packet - thisPacket.reInit(srcConn); - if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ - DTSC::Meta newMeta; - newMeta.reinit(thisPacket); - //Detect new tracks - std::set newTracks; - for (std::map::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){ - if (!myMeta.tracks.count(it->first)){ - newTracks.insert(it->first); + while (config->is_active){ + if (thisPacket.getVersion() == DTSC::DTCM){ + nProxy.userClient.keepAlive(); + std::string cmd; + thisPacket.getString("cmd", cmd); + if (cmd == "reset"){ + //Read next packet + thisPacket.reInit(srcConn); + if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ + DTSC::Meta newMeta; + newMeta.reinit(thisPacket); + //Detect new tracks + std::set newTracks; + for (std::map::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){ + if (!myMeta.tracks.count(it->first)){ + newTracks.insert(it->first); + } } - } - for (std::set::iterator it = newTracks.begin(); it != newTracks.end(); it++){ - INFO_MSG("Reset: adding track %d", *it); - myMeta.tracks[*it] = newMeta.tracks[*it]; - continueNegotiate(*it, true); - } - - //Detect removed tracks - std::set deletedTracks; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (!newMeta.tracks.count(it->first)){ - deletedTracks.insert(it->first); + for (std::set::iterator it = newTracks.begin(); it != newTracks.end(); it++){ + INFO_MSG("Reset: adding track %d", *it); + myMeta.tracks[*it] = newMeta.tracks[*it]; + continueNegotiate(*it, true); } - } - for(std::set::iterator it = deletedTracks.begin(); it != deletedTracks.end(); it++){ - INFO_MSG("Reset: deleting track %d", *it); - myMeta.tracks.erase(*it); - } + //Detect removed tracks + std::set deletedTracks; + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + if (!newMeta.tracks.count(it->first)){ + deletedTracks.insert(it->first); + } + } - //Read next packet before returning - return getNext(smart); + for(std::set::iterator it = deletedTracks.begin(); it != deletedTracks.end(); it++){ + INFO_MSG("Reset: deleting track %d", *it); + myMeta.tracks.erase(*it); + } + thisPacket.reInit(srcConn);//read the next packet before continuing + }else{ + myMeta = DTSC::Meta(); + } }else{ - myMeta = DTSC::Meta(); + thisPacket.reInit(srcConn);//read the next packet before continuing } - }else{ - //Read next packet before returning - thisPacket.reInit(srcConn); - } - }else if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ - DTSC::Meta newMeta; - newMeta.reinit(thisPacket); - std::set newTracks; - for (std::map::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){ - if (!myMeta.tracks.count(it->first)){ - newTracks.insert(it->first); + continue;//parse the next packet before returning + }else if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ + DTSC::Meta newMeta; + newMeta.reinit(thisPacket); + std::set newTracks; + for (std::map::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){ + if (!myMeta.tracks.count(it->first)){ + newTracks.insert(it->first); + } } - } - for (std::set::iterator it = newTracks.begin(); it != newTracks.end(); it++){ - INFO_MSG("New header: adding track %d (%s)", *it, newMeta.tracks[*it].type.c_str()); - myMeta.tracks[*it] = newMeta.tracks[*it]; - continueNegotiate(*it, true); + for (std::set::iterator it = newTracks.begin(); it != newTracks.end(); it++){ + INFO_MSG("New header: adding track %d (%s)", *it, newMeta.tracks[*it].type.c_str()); + myMeta.tracks[*it] = newMeta.tracks[*it]; + continueNegotiate(*it, true); + } + thisPacket.reInit(srcConn);//read the next packet before continuing + continue;//parse the next packet before returning } - return getNext(smart); + //We now know we have either a data packet, or an error. + if (!thisPacket.getTrackId()){ + if (thisPacket.getVersion() == DTSC::DTSC_V2){ + WARN_MSG("Received bad packet for stream %s: %llu@%llu", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime()); + }else{ + //All types except data packets are handled above, so if it's not a V2 data packet, we assume corruption + WARN_MSG("Invalid packet header for stream %s", streamName.c_str()); + } + } + return;//we have a packet } }else{ if (smart) { From c6377085f0f79bac0a7488cb39c2ef0b2cbcf208 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 6 Jan 2017 19:18:47 +0100 Subject: [PATCH 4/6] Added shutdown reason message in stream-mode inputs --- src/input/input.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/input/input.cpp b/src/input/input.cpp index 786354a1..f5ed8b72 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -281,6 +281,10 @@ namespace Mist { getNext(); nProxy.userClient.keepAlive(); } + std::string reason = "Unknown"; + if (!thisPacket){reason = "Invalid packet";} + if (!config->is_active){reason = "received deactivate signal";} + if (!nProxy.userClient.isAlive()){reason = "buffer shutdown";} closeStreamSource(); @@ -289,7 +293,7 @@ namespace Mist { pullLock.post(); pullLock.close(); pullLock.unlink(); - INFO_MSG("Stream input %s closing clean", streamName.c_str()); + INFO_MSG("Stream input %s closing clean; reason: %s", streamName.c_str(), reason.c_str()); return; } From 88834e53baf0b199ae746c60f115649cadb24fd1 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 7 Jan 2017 12:12:24 +0100 Subject: [PATCH 5/6] Fixed statistics recovery after controller reload/restart --- lib/shared_memory.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/shared_memory.cpp b/lib/shared_memory.cpp index 9180895d..fecc7973 100644 --- a/lib/shared_memory.cpp +++ b/lib/shared_memory.cpp @@ -807,11 +807,15 @@ namespace IPC { ///\brief Creates the next page with the correct size void sharedServer::newPage() { - sharedPage tmp(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')), std::min(((8192 * 2) << myPages.size()), (32 * 1024 * 1024)), true); + sharedPage tmp(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')), std::min(((8192 * 2) << myPages.size()), (32 * 1024 * 1024)), false); + if (!tmp.mapped){ + tmp.init(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')), std::min(((8192 * 2) << myPages.size()), (32 * 1024 * 1024)), true); + tmp.master = false; + } myPages.push_back(tmp); - tmp.master = false; - DEBUG_MSG(DLVL_VERYHIGH, "Created a new page: %s", tmp.name.c_str()); - amount += tmp.len / (payLen + (hasCounter ? 1 : 0)) - 1; + myPages.back().master = true; + VERYHIGH_MSG("Created a new page: %s", tmp.name.c_str()); + amount += (32 * 1024 * 1024)*myPages.size();//assume maximum load - we don't want to miss any entries } ///\brief Deletes the highest allocated page @@ -982,12 +986,12 @@ namespace IPC { if (countNum == 127 || countNum == 126){ semGuard tmpGuard(&mySemaphore); if (disconCallback){ - disconCallback(it->mapped + offset + 1, payLen, id); + disconCallback(counter + 1, payLen, id); } - memset(it->mapped + offset + 1, 0, payLen); - it->mapped[offset] = 0; + memset(counter + 1, 0, payLen); + *counter = 0; } else { - it->mapped[offset] ++; + ++(*counter); } } else { //stop if we're past the amount counted and we're empty From fd5b9805deb5a12f1cd5f7571ee9b7728f3ddf8b Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 10 Jan 2017 12:34:43 +0100 Subject: [PATCH 6/6] Fixed favicon.ico --- src/output/output_http_internal.cpp | 40 +++++++++++++++++++---------- src/output/output_http_internal.h | 1 + 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index fd7ce399..0f6fcb7d 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -32,6 +32,11 @@ namespace Mist { } void OutHTTP::onFail(){ + // send logo icon + if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){ + sendIcon(); + return; + } INFO_MSG("Failing: %s", H.url.c_str()); if (H.url.size() >= 3 && H.url.substr(H.url.size() - 3) == ".js"){ if (H.url.size() >= 5 && H.url.substr(0, 5) == "/json"){ @@ -250,20 +255,7 @@ namespace Mist { } // send logo icon if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){ - H.Clean(); - #include "../icon.h" - H.SetHeader("Content-Type", "image/x-icon"); - H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); - H.SetHeader("Content-Length", icon_len); - H.setCORSHeaders(); - if(method == "OPTIONS" || method == "HEAD"){ - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - H.SendResponse("200", "OK", myConn); - myConn.SendNow((const char*)icon_data, icon_len); - H.Clean(); + sendIcon(); return; } @@ -618,4 +610,24 @@ namespace Mist { return; } } + + void OutHTTP::sendIcon(){ + std::string method = H.method; + H.Clean(); + #include "../icon.h" + H.SetHeader("Content-Type", "image/x-icon"); + H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); + H.SetHeader("Content-Length", icon_len); + H.setCORSHeaders(); + if(method == "OPTIONS" || method == "HEAD"){ + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + H.SendResponse("200", "OK", myConn); + myConn.SendNow((const char*)icon_data, icon_len); + H.Clean(); + } + } + diff --git a/src/output/output_http_internal.h b/src/output/output_http_internal.h index 479d37a4..5cda9bb3 100644 --- a/src/output/output_http_internal.h +++ b/src/output/output_http_internal.h @@ -11,6 +11,7 @@ namespace Mist { virtual void onFail(); void onHTTP(); bool isReadyForPlay(); + void sendIcon(); }; }