From a8e04e178704286ceba50c7df0e54ca26ff9c804 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 17 Feb 2020 17:02:05 +0100 Subject: [PATCH] Fixes to CMAF output --- lib/cmaf.cpp | 84 +++++++++++++++----------------------- lib/cmaf.h | 6 +-- lib/dtsc.cpp | 12 +++--- lib/dtsc.h | 2 +- src/input/input.cpp | 4 +- src/output/output.cpp | 2 +- src/output/output_cmaf.cpp | 43 +++++++++---------- 7 files changed, 69 insertions(+), 84 deletions(-) diff --git a/lib/cmaf.cpp b/lib/cmaf.cpp index 37776f66..e2aa8f11 100644 --- a/lib/cmaf.cpp +++ b/lib/cmaf.cpp @@ -1,24 +1,13 @@ #include "cmaf.h" +static uint64_t unixBootDiff = (Util::unixMS() - Util::bootMS()); + namespace CMAF{ /// Function to determine the payload size of a CMAF fragment. - /// \parm isKeyIndex indicates whether we are sending DTSC Fragment or DTSC Key based CMAF fragments. - size_t payloadSize(const DTSC::Meta &M, size_t track, size_t index, bool isKeyIndex){ - DTSC::Fragments fragments(M.fragments(track)); - DTSC::Keys keys(M.keys(track)); + size_t payloadSize(const DTSC::Meta &M, size_t track, uint64_t startTime, uint64_t endTime){ DTSC::Parts parts(M.parts(track)); - - size_t firstKey = (isKeyIndex ? index : fragments.getFirstKey(index)); - size_t endKey = keys.getEndValid(); - if (isKeyIndex) { - if (index + 1 < keys.getEndValid()){endKey = index + 1;} - } else { - if (index + 1 < fragments.getEndValid()){endKey = fragments.getFirstKey(index + 1);} - } - - size_t firstPart = keys.getFirstPart(firstKey); - size_t endPart = parts.getEndValid(); - if (endKey != keys.getEndValid()){endPart = keys.getFirstPart(endKey);} + size_t firstPart = M.getPartIndex(startTime, track); + size_t endPart = M.getPartIndex(endTime, track); size_t payloadSize = 0; for (size_t i = firstPart; i < endPart; i++){payloadSize += parts.getSize(i);} return payloadSize; @@ -186,7 +175,7 @@ namespace CMAF{ ((i + 1 < fragments.getEndValid()) ? fragments.getFirstKey(i + 1) : keys.getEndValid()); MP4::sidxReference refItem; - refItem.referencedSize = payloadSize(M, track, i) + fragmentHeaderSize(M, track, i) + 8; + refItem.referencedSize = payloadSize(M, track, keys.getTime(firstKey), keys.getTime(endKey)) + fragmentHeaderSize(M, track, i) + 8; refItem.subSegmentDuration = (endKey == keys.getEndValid() ? M.getLastms(track) : keys.getTime(endKey)) - keys.getTime(firstKey); refItem.sapStart = true; @@ -304,7 +293,7 @@ namespace CMAF{ if (M.getVod()){ tfdtBox.setBaseMediaDecodeTime(M.getTimeForFragmentIndex(track, fragment) - M.getFirstms(track)); }else{ - tfdtBox.setBaseMediaDecodeTime((UTCTime ? Util::epoch()*1000 : M.getTimeForFragmentIndex(track, fragment))); + tfdtBox.setBaseMediaDecodeTime((UTCTime ? M.getTimeForFragmentIndex(track, fragment) + M.getBootMsOffset() + unixBootDiff : M.getTimeForFragmentIndex(track, fragment))); } trafBox.setContent(tfdtBox, 1); @@ -337,50 +326,36 @@ namespace CMAF{ /// Calculates the full size of a 'moof' box for a DTSC::Key based fragment. /// Used when building the 'moof' box to calculate the relative data offsets. - size_t keyHeaderSize(const DTSC::Meta &M, size_t track, size_t key){ + size_t keyHeaderSize(const DTSC::Meta &M, size_t track, uint64_t startTime, uint64_t endTime){ uint64_t tmpRes = 8 + 16 + 32 + 20; - - DTSC::Keys keys(M.keys(track)); - DTSC::Parts parts(M.parts(track)); - - size_t firstPart = keys.getFirstPart(key); - size_t endPart = parts.getEndValid(); - if (key + 1 < keys.getEndValid()){ - endPart = keys.getFirstPart(key + 1); - } - + size_t firstPart = M.getPartIndex(startTime, track); + size_t endPart = M.getPartIndex(endTime, track); tmpRes += 24 + ((endPart - firstPart) * 12); return tmpRes; } /// Generates the 'moof' box for a DTSC::Key based CMAF fragment. - std::string keyHeader(const DTSC::Meta &M, size_t track, size_t key, bool simplifyTrackIds, bool UTCTime){ - DTSC::Keys keys(M.keys(track)); - DTSC::Parts parts(M.parts(track)); - - size_t firstPart = keys.getFirstPart(key); - size_t endPart = parts.getEndValid(); - if (key + 1 < keys.getEndValid()){ - endPart = keys.getFirstPart(key + 1); - } + std::string keyHeader(const DTSC::Meta &M, size_t track, uint64_t startTime, uint64_t endTime, uint64_t segmentNum, bool simplifyTrackIds, bool UTCTime){ + size_t firstPart = M.getPartIndex(startTime, track); + size_t endPart = M.getPartIndex(endTime, track); std::stringstream header; - MP4::MOOF moofBox; - MP4::MFHD mfhdBox(key + 1); + MP4::MFHD mfhdBox(segmentNum); moofBox.setContent(mfhdBox, 0); std::set trunOrder; //We use keyHeaderSize here to determine the relative offsets of the data in the 'mdat' box. - uint64_t relativeOffset = keyHeaderSize(M, track, key) + 8; + uint64_t relativeOffset = keyHeaderSize(M, track, startTime, endTime) + 8; sortPart temp; - temp.time = keys.getTime(key); + temp.time = startTime; temp.partIndex = firstPart; temp.bytePos = relativeOffset; + DTSC::Parts parts(M.parts(track)); for (size_t p = firstPart; p < endPart; p++){ trunOrder.insert(temp); temp.time += parts.getDuration(p); @@ -405,9 +380,9 @@ namespace CMAF{ MP4::TFDT tfdtBox; if (M.getVod()){ - tfdtBox.setBaseMediaDecodeTime(keys.getTime(key) - M.getFirstms(track)); + tfdtBox.setBaseMediaDecodeTime(startTime - M.getFirstms(track)); }else{ - tfdtBox.setBaseMediaDecodeTime((UTCTime ? Util::epoch()*1000 : keys.getTime(key) )); + tfdtBox.setBaseMediaDecodeTime((UTCTime ? startTime + M.getBootMsOffset() + unixBootDiff : startTime)); } trafBox.setContent(tfdtBox, 1); @@ -421,12 +396,21 @@ namespace CMAF{ size_t trunOffset = 0; - for (std::set::iterator it = trunOrder.begin(); it != trunOrder.end(); it++){ - MP4::trunSampleInformation sampleInfo; - sampleInfo.sampleSize = parts.getSize(it->partIndex); - sampleInfo.sampleDuration = parts.getDuration(it->partIndex); - sampleInfo.sampleOffset = parts.getOffset(it->partIndex); - trunBox.setSampleInformation(sampleInfo, trunOffset++); + if (trunOrder.size()){ + std::set::iterator lastOne = trunOrder.end(); + lastOne--; + for (std::set::iterator it = trunOrder.begin(); it != trunOrder.end(); it++){ + MP4::trunSampleInformation sampleInfo; + sampleInfo.sampleSize = parts.getSize(it->partIndex); + sampleInfo.sampleDuration = parts.getDuration(it->partIndex); + if (it == lastOne){ + sampleInfo.sampleDuration = endTime - it->time; + } + sampleInfo.sampleOffset = parts.getOffset(it->partIndex); + trunBox.setSampleInformation(sampleInfo, trunOffset++); + } + }else{ + WARN_MSG("Empty CMAF header for track %zu: %zu-%zu contains no packets (first: %" PRIu64 ", last: %" PRIu64 "), firstPart=%zu, lastPart=%zu", track, startTime, endTime, M.getFirstms(track), M.getLastms(track), firstPart, endPart); } trafBox.setContent(trunBox, 2); diff --git a/lib/cmaf.h b/lib/cmaf.h index 793f1b34..87eff0bc 100644 --- a/lib/cmaf.h +++ b/lib/cmaf.h @@ -4,11 +4,11 @@ #include namespace CMAF{ - size_t payloadSize(const DTSC::Meta &M, size_t track, size_t index, bool isKeyIndex = false); + size_t payloadSize(const DTSC::Meta &M, size_t track, uint64_t startTime, uint64_t endTime); size_t trackHeaderSize(const DTSC::Meta &M, size_t track); std::string trackHeader(const DTSC::Meta &M, size_t track, bool simplifyTrackIds = false); size_t fragmentHeaderSize(const DTSC::Meta &M, size_t track, size_t fragment); std::string fragmentHeader(const DTSC::Meta &M, size_t track, size_t fragment, bool simplifyTrackIds = false, bool UTCTime = false); - size_t keyHeaderSize(const DTSC::Meta &M, size_t track, size_t key); - std::string keyHeader(const DTSC::Meta &M, size_t track, size_t key, bool simplifyTrackIds = false, bool UTCTime = false); + size_t keyHeaderSize(const DTSC::Meta &M, size_t track, uint64_t startTime, uint64_t endTime); + std::string keyHeader(const DTSC::Meta &M, size_t track, uint64_t startTime, uint64_t endTime, uint64_t segmentNum, bool simplifyTrackIds = false, bool UTCTime = false); }// namespace CMAF diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp index 63f36474..5d090727 100644 --- a/lib/dtsc.cpp +++ b/lib/dtsc.cpp @@ -2890,27 +2890,27 @@ namespace DTSC{ return keys.getTime(fragments.getFirstKey(fragmentIdx)); } - /// Returns the part index for the given DTSC::Packet by timestamp. + /// Returns the part index for the given timestamp. /// Assumes the Packet is for the given track, and assumes the metadata and track data are not out /// of sync. Works by looking up the key for the Packet's timestamp, then walking through the /// parts until the time matches or exceeds the time of the Packet. Returns zero if the track /// index is invalid or if the timestamp cannot be found. - uint32_t Meta::getPartIndex(const DTSC::Packet &pack, size_t idx) const{ + uint32_t Meta::getPartIndex(uint64_t timestamp, size_t idx) const{ if (idx == INVALID_TRACK_ID){return 0;} uint32_t res = 0; - uint32_t keyIdx = getKeyIndexForTime(idx, pack.getTime()); + uint32_t keyIdx = getKeyIndexForTime(idx, timestamp); DTSC::Keys Keys(keys(idx)); DTSC::Parts Parts(parts(idx)); uint64_t currentTime = Keys.getTime(keyIdx); res = Keys.getFirstPart(keyIdx); - size_t endPart = res + Keys.getParts(keyIdx); + size_t endPart = Parts.getEndValid(); for (size_t i = res; i < endPart; i++){ - if (currentTime >= pack.getTime()){return res;} + if (currentTime >= timestamp){return res;} currentTime += Parts.getDuration(i); res++; } - return 0; + return res; } /// Given the current page, check if the next page is available. Returns true if it is. diff --git a/lib/dtsc.h b/lib/dtsc.h index 78ae5528..47d3a168 100644 --- a/lib/dtsc.h +++ b/lib/dtsc.h @@ -426,7 +426,7 @@ namespace DTSC{ uint64_t getTimeForKeyIndex(uint32_t idx, uint32_t keyIdx) const; uint32_t getKeyIndexForTime(uint32_t idx, uint64_t timestamp) const; - uint32_t getPartIndex(const DTSC::Packet &pack, size_t idx) const; + uint32_t getPartIndex(uint64_t timestamp, size_t idx) const; bool nextPageAvailable(uint32_t idx, size_t currentPage) const; size_t getPageNumberForTime(uint32_t idx, uint64_t time) const; diff --git a/src/input/input.cpp b/src/input/input.cpp index 28839fbe..189824e7 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -1262,11 +1262,11 @@ namespace Mist{ } if (encryption.substr(0, encryption.find('/')) == "CTR128"){ DTSC::Packet encPacket = aesCipher.encryptPacketCTR( - M, thisPacket, M.getIvec(idx) + M.getPartIndex(thisPacket, idx), idx); + M, thisPacket, M.getIvec(idx) + M.getPartIndex(thisPacket.getTime(), idx), idx); thisPacket = encPacket; }else if (encryption.substr(0, encryption.find('/')) == "CBC128"){ char ivec[] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - Bit::htobll(ivec + 8, M.getIvec(idx) + M.getPartIndex(thisPacket, idx)); + Bit::htobll(ivec + 8, M.getIvec(idx) + M.getPartIndex(thisPacket.getTime(), idx)); DTSC::Packet encPacket = aesCipher.encryptPacketCBC(M, thisPacket, ivec, idx); thisPacket = encPacket; } diff --git a/src/output/output.cpp b/src/output/output.cpp index 7cb37b68..724464cb 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -725,7 +725,7 @@ namespace Mist{ 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); - tmp.partIndex = M.getPartIndex(tmpPack, tmp.tid); + tmp.partIndex = M.getPartIndex(tmpPack.getTime(), tmp.tid); buffer.insert(tmp); return true; } diff --git a/src/output/output_cmaf.cpp b/src/output/output_cmaf.cpp index 528035d2..d4408a2b 100644 --- a/src/output/output_cmaf.cpp +++ b/src/output/output_cmaf.cpp @@ -237,7 +237,7 @@ namespace Mist{ std::string headerData = CMAF::fragmentHeader(M, idx, fragmentIndex, false, false); H.Chunkify(headerData.c_str(), headerData.size(), myConn); - uint64_t mdatSize = 8 + CMAF::payloadSize(M, idx, fragmentIndex); + uint64_t mdatSize = 8 + CMAF::payloadSize(M, idx, targetTime, M.getTimeForFragmentIndex(idx, fragmentIndex+1)); char mdatHeader[] ={0x00, 0x00, 0x00, 0x00, 'm', 'd', 'a', 't'}; Bit::htobl(mdatHeader, mdatSize); @@ -776,8 +776,7 @@ namespace Mist{ if (!isRecording()){return;} if (!pushTracks.count(idx) || !pushTracks.at(idx).D.getSocket()){return;} INFO_MSG("Disconnecting track %zu", idx); - pushTracks[idx].disconnect(); - + pushTracks[idx].disconnect(); pushTracks.erase(idx); } @@ -803,11 +802,13 @@ namespace Mist{ /// Function that waits at most `maxWait` ms (in steps of 100ms) for the next keyframe to become available. /// Uses thisIdx and thisPacket to determine track and current timestamp respectively. bool OutCMAF::waitForNextKey(uint64_t maxWait){ - size_t currentKey = M.getKeyIndexForTime(thisIdx, thisPacket.getTime()); - DTSC::Keys keys(M.keys(thisIdx)); + size_t currentKey = M.getKeyIndexForTime(getMainSelectedTrack(), thisPacket.getTime()); + DTSC::Keys keys(M.keys(getMainSelectedTrack())); size_t waitTimes = maxWait / 100; for (size_t i = 0; i < waitTimes; ++i){ - if (keys.getEndValid() > currentKey + 1){return true;} + if (keys.getEndValid() > currentKey + 1 && M.getLastms(thisIdx) > M.getTimeForKeyIndex(getMainSelectedTrack(), currentKey+1)){ + return true; + } Util::wait(100); //Make sure we don't accidentally timeout while waiting - runs approximately every second. if (i % 10 == 0){ @@ -817,7 +818,7 @@ namespace Mist{ } } } - return (keys.getEndValid() > currentKey + 1); + return (keys.getEndValid() > currentKey + 1 && M.getLastms(thisIdx) > M.getTimeForKeyIndex(getMainSelectedTrack(), currentKey+1)); } //Set up an empty connection to the target to make sure we can push data towards it. @@ -833,14 +834,15 @@ namespace Mist{ void OutCMAF::pushNext() { //Set up a new connection if this is a new track, or if we have been disconnected. if (!pushTracks.count(thisIdx) || !pushTracks.at(thisIdx).D.getSocket()){ + if (pushTracks.count(thisIdx)){INFO_MSG("Reconnecting existing track: socket was disconnected");} CMAFPushTrack & track = pushTracks[thisIdx]; - size_t keyIndex = M.getKeyIndexForTime(thisIdx, thisPacket.getTime()); - track.headerFrom = M.getTimeForKeyIndex(thisIdx, keyIndex); + size_t keyIndex = M.getKeyIndexForTime(getMainSelectedTrack(), thisPacket.getTime()); + track.headerFrom = M.getTimeForKeyIndex(getMainSelectedTrack(), keyIndex); if (track.headerFrom < thisPacket.getTime()){ - track.headerFrom = M.getTimeForKeyIndex(thisIdx, keyIndex + 1); + track.headerFrom = M.getTimeForKeyIndex(getMainSelectedTrack(), keyIndex + 1); } - HIGH_MSG("Starting track %zu at %" PRIu64 "ms into the stream, current packet at %" PRIu64 "ms", thisIdx, track.headerFrom, thisPacket.getTime()); + INFO_MSG("Starting track %zu at %" PRIu64 "ms into the stream, current packet at %" PRIu64 "ms", thisIdx, track.headerFrom, thisPacket.getTime()); setupTrackObject(thisIdx); track.headerUntil = 0; @@ -848,13 +850,13 @@ namespace Mist{ } CMAFPushTrack & track = pushTracks[thisIdx]; if (thisPacket.getTime() < track.headerFrom){return;} - if (thisPacket.getTime() > track.headerUntil || !track.headerUntil){ - size_t keyIndex = M.getKeyIndexForTime(thisIdx, thisPacket.getTime()); - uint64_t keyTime = M.getTimeForKeyIndex(thisIdx, keyIndex); - if (keyTime != thisPacket.getTime()){ - WARN_MSG("Corruption probably occured, initiating reconnect %" PRIu64 " != %" PRIu64, keyTime, thisPacket.getTime()); + if (thisPacket.getTime() >= track.headerUntil){ + size_t keyIndex = M.getKeyIndexForTime(getMainSelectedTrack(), thisPacket.getTime()); + uint64_t keyTime = M.getTimeForKeyIndex(getMainSelectedTrack(), keyIndex); + if (keyTime > thisPacket.getTime()){ + WARN_MSG("Corruption probably occurred, initiating reconnect %" PRIu64 " != %" PRIu64, keyTime, thisPacket.getTime()); onTrackEnd(thisIdx); - track.headerFrom = M.getTimeForKeyIndex(thisIdx, keyIndex + 1); + track.headerFrom = M.getTimeForKeyIndex(getMainSelectedTrack(), keyIndex + 1); track.headerUntil = 0; pushNext(); return; @@ -865,10 +867,9 @@ namespace Mist{ dropTrack(thisIdx, "No next keyframe available"); return; } - track.headerUntil = M.getTimeForKeyIndex(thisIdx, keyIndex + 1) - 1; - std::string keyHeader = CMAF::keyHeader(M, thisIdx, keyIndex, true, true); - - uint64_t mdatSize = 8 + CMAF::payloadSize(M, thisIdx, keyIndex, true); + track.headerUntil = M.getTimeForKeyIndex(getMainSelectedTrack(), keyIndex + 1); + std::string keyHeader = CMAF::keyHeader(M, thisIdx, track.headerFrom, track.headerUntil, keyIndex+1, true, true); + uint64_t mdatSize = 8 + CMAF::payloadSize(M, thisIdx, track.headerFrom, track.headerUntil); char mdatHeader[] ={0x00, 0x00, 0x00, 0x00, 'm', 'd', 'a', 't'}; Bit::htobl(mdatHeader, mdatSize);