Improved EBML B-frame detection/handling

This commit is contained in:
Thulinma 2020-07-18 19:31:04 +02:00
parent 5edf06ab68
commit 0a51e95d1a
2 changed files with 86 additions and 92 deletions

View file

@ -170,6 +170,10 @@ namespace Mist{
if (M.inputLocalVars.isMember("timescale")){
timeScale = ((double)M.inputLocalVars["timescale"].asInt()) / 1000000.0;
}
if (!M.inputLocalVars.isMember("version") || M.inputLocalVars["version"].asInt() < 2){
INFO_MSG("Header needs update, regenerating");
return false;
}
return true;
}
@ -177,9 +181,15 @@ namespace Mist{
if (!inFile){return false;}
// Create header file from file
uint64_t bench = Util::getMicros();
if (!meta){meta.reInit(streamName);}
if (!meta || (needsLock() && isSingular())){
meta.reInit(streamName);
}
while (readElement()){
if (!config->is_active){
WARN_MSG("Aborting header generation due to shutdown: %s", Util::exitReason);
return false;
}
EBML::Element E(ptr, readingMinimal);
if (E.getID() == EBML::EID_TRACKENTRY){
EBML::Element tmpElem = E.findChild(EBML::EID_TRACKNUMBER);
@ -364,7 +374,7 @@ namespace Mist{
if (isVideo && B.isKeyframe()){
while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(true);
meta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
meta.update(C.time, C.offset, idx, C.dsize, C.bpos, C.key);
TP.remove();
}
TP.flush();
@ -394,12 +404,12 @@ namespace Mist{
frameSize = assStr.size();
}
if (frameSize){
TP.add(newTime * timeScale, 0, tNum, frameSize, lastClusterBPos, B.isKeyframe() && !isAudio, isVideo);
TP.add(newTime * timeScale, tNum, frameSize, lastClusterBPos, B.isKeyframe() && !isAudio, isVideo);
}
}
while (TP.hasPackets()){
packetData &C = TP.getPacketData(isVideo);
meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
meta.update(C.time, C.offset, idx, C.dsize, C.bpos, C.key);
TP.remove();
}
}
@ -417,6 +427,7 @@ namespace Mist{
}
}
meta.inputLocalVars["version"] = 2;
bench = Util::getMicros(bench);
INFO_MSG("Header generated in %" PRIu64 " ms", bench / 1000);
clearPredictors();
@ -473,6 +484,8 @@ namespace Mist{
}
void InputEBML::getNext(size_t idx){
bool singleTrack = (idx != INVALID_TRACK_ID);
size_t wantedID = singleTrack?M.getID(idx):0;
// Make sure we empty our buffer first
if (bufferedPacks && packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
@ -483,6 +496,7 @@ namespace Mist{
fillPacket(C);
TP.remove();
--bufferedPacks;
if (singleTrack && it->first != wantedID){getNext(idx);}
return;
}
}
@ -500,6 +514,7 @@ namespace Mist{
fillPacket(C);
TP.remove();
--bufferedPacks;
if (singleTrack && it->first != wantedID){getNext(idx);}
return;
}
}
@ -510,7 +525,7 @@ namespace Mist{
}
B = EBML::Block(ptr);
}while (!B || B.getType() != EBML::ELEM_BLOCK ||
(idx != INVALID_TRACK_ID && M.getID(idx) != B.getTrackNum()));
(singleTrack && wantedID != B.getTrackNum()));
}else{
B = EBML::Block(ptr);
}
@ -531,6 +546,7 @@ namespace Mist{
fillPacket(C);
TP.remove();
--bufferedPacks;
if (singleTrack && trackIdx != idx){getNext(idx);}
return;
}
}
@ -563,7 +579,7 @@ namespace Mist{
memcpy(ptr, assStr.data(), frameSize);
}
if (frameSize){
TP.add(newTime * timeScale, 0, tNum, frameSize, lastClusterBPos,
TP.add(newTime * timeScale, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
++bufferedPacks;
}
@ -574,6 +590,7 @@ namespace Mist{
fillPacket(C);
TP.remove();
--bufferedPacks;
if (singleTrack && trackIdx != idx){getNext(idx);}
}else{
// We didn't set thisPacket yet. Read another.
// Recursing is fine, this can only happen a few times in a row.

View file

@ -4,7 +4,7 @@
namespace Mist{
#define PKT_COUNT 64
#define PKT_COUNT 24
class packetData{
public:
@ -29,119 +29,96 @@ namespace Mist{
};
class trackPredictor{
public:
packetData pkts[PKT_COUNT];
uint64_t frameOffset; /// The static average offset between transmit time and display time
bool frameOffsetKnown; /// Whether the average frame offset is known
uint16_t smallestFrame; /// low-ball estimate of time per frame
uint64_t lastTime; /// last send transmit timestamp
uint64_t ctr; /// ingested frame count
uint64_t rem; /// removed frame count
uint64_t maxOffset; /// maximum offset for this track
uint64_t lowestTime; /// First timestamp to enter the buffer
packetData pkts[PKT_COUNT]; /// Buffer for packet data
uint64_t times[PKT_COUNT]; /// Sorted timestamps of buffered packets
size_t maxDelay; /// Maximum amount of bframes we expect
uint32_t timeOffset; /// Milliseconds we need to subtract from times so that offsets are always > 0
uint64_t ctr; /// ingested frame count
uint64_t rem; /// removed frame count
bool initialized;
trackPredictor(){
smallestFrame = 0xFFFF;
frameOffsetKnown = false;
frameOffset = 0;
maxOffset = 0;
initialized = false;
maxDelay = 0;
timeOffset = 0;
flush();
}
bool hasPackets(bool finished = false){
if (finished || frameOffsetKnown){
if (finished){
return (ctr - rem > 0);
}else{
return (ctr - rem > 12);
return ((initialized || ctr > 16) && ctr - rem > maxDelay);
}
}
/// Clears all internal values, for reuse as-new.
void flush(){
lastTime = 0;
ctr = 0;
rem = 0;
lowestTime = 0;
}
packetData &getPacketData(bool mustCalcOffsets){
// grab the next packet to output
packetData &p = pkts[rem % PKT_COUNT];
if (!mustCalcOffsets){
frameOffsetKnown = true;
if (!mustCalcOffsets || !maxDelay){
return p;
}
if (rem && !p.key){
uint64_t dispTime = p.time;
if (p.time + frameOffset < lastTime + smallestFrame){
uint32_t shift =
(uint32_t)((((lastTime + smallestFrame) - (p.time + frameOffset)) + (smallestFrame - 1)) / smallestFrame) *
smallestFrame;
if (shift < smallestFrame){shift = smallestFrame;}
VERYHIGH_MSG("Offset negative, shifting original time forward by %" PRIu32, shift);
p.time += shift;
}
p.offset = p.time - (lastTime + smallestFrame) + frameOffset;
if (p.offset > maxOffset){
uint64_t diff = p.offset - maxOffset;
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (maxOffset reached: %" PRIu64 " > %" PRIu64 ")",
diff, p.offset, maxOffset);
p.offset -= diff;
lastTime += diff;
}
p.time = (lastTime + smallestFrame);
// If we calculate an offset less than a frame away,
// we assume it's just time stamp drift due to lack of precision.
p.offset = ((uint32_t)((p.offset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
// Shift the time forward if needed, but never backward
if (p.offset + p.time < dispTime){
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (time drift)", dispTime - (p.offset + p.time));
p.time += dispTime - (p.offset + p.time);
}
}else{
if (!frameOffsetKnown){
// Check the first few timestamps against each other, find the smallest distance.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t t1 = pkts[i % PKT_COUNT].time;
for (uint64_t j = 0; j < ctr; ++j){
if (i == j){continue;}
uint64_t t2 = pkts[j % PKT_COUNT].time;
uint64_t tDiff = (t1 < t2) ? (t2 - t1) : (t1 - t2);
if (tDiff < smallestFrame){smallestFrame = tDiff;}
//Calculate the timeOffset when extracting the first frame
if (!initialized){
size_t buffLen = (ctr-rem-1) % PKT_COUNT;
for (size_t i = 0; i <= buffLen; ++i){
if (pkts[i].time < times[i]){
if (times[i] - pkts[i].time > timeOffset){
timeOffset = times[i] - pkts[i].time;
}
}
// Cool, now we're pretty sure we know the frame rate. Let's calculate some offsets.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t timeDiff = pkts[i % PKT_COUNT].time - lowestTime;
uint64_t timeExpt = smallestFrame * i;
if (timeDiff > timeExpt && maxOffset < timeDiff - timeExpt){
maxOffset = timeDiff - timeExpt;
}
if (timeDiff < timeExpt && frameOffset < timeExpt - timeDiff){
frameOffset = timeExpt - timeDiff;
}
}
maxOffset += frameOffset;
// Print for debugging purposes, and consider them gospel from here on forward. Yay!
HIGH_MSG("smallestFrame=%" PRIu16 ", frameOffset=%" PRIu64 ", maxOffset=%" PRIu64,
smallestFrame, frameOffset, maxOffset);
frameOffsetKnown = true;
DONTEVEN_MSG("Checking time offset against entry %zu/%zu: %" PRIu64 "-%" PRIu64 " = %" PRIu32, i, buffLen, times[i], pkts[i].time, timeOffset);
}
p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
MEDIUM_MSG("timeOffset calculated to be %" PRIu32 ", max frame delay %zu", timeOffset, maxDelay);
initialized = true;
}
lastTime = p.time;
INSANE_MSG("Outputting%s %" PRIu64 "+%" PRIu64 " (#%" PRIu64 ", Max=%" PRIu64
"), display at %" PRIu64,
(p.key ? "KEY" : ""), p.time, p.offset, rem, maxOffset, p.time + p.offset);
uint64_t origTime = p.time;
//Set new timestamp to first time in sorted array
p.time = times[0];
//Subtract timeOffset if possible
if (p.time >= timeOffset){p.time -= timeOffset;}
//If possible, calculate offset based on original timestamp difference with new timestamp
if (origTime > p.time){p.offset = origTime-p.time;}
//Less than 3 milliseconds off? Assume we needed 0 and it's a rounding error in timestamps.
if (p.offset < 3){p.offset = 0;}
DONTEVEN_MSG("Outputting%s %" PRIu64 "+%" PRIu64 " (#%" PRIu64 "), display at %" PRIu64,
(p.key ? " KEY" : ""), p.time, p.offset, rem, p.time + p.offset);
return p;
}
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
void add(uint64_t packTime, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, bool isVideo, void *dataPtr = 0){
if (!ctr){lowestTime = packTime;}
if (packTime > lowestTime && packTime - lowestTime < smallestFrame){
smallestFrame = packTime - lowestTime;
}
pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
pkts[ctr % PKT_COUNT].set(packTime, 0, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
++ctr;
if (ctr == PKT_COUNT - 1){frameOffsetKnown = true;}
if (!isVideo){return;}
size_t buffLen = ctr-rem-1;
//Just in case somebody messed up, ensure we don't go out of our PKT_COUNT sized array
if (buffLen >= PKT_COUNT){buffLen = PKT_COUNT - 1;}
times[buffLen] = packTime;
if (buffLen){
//Swap the times while the previous is higher than the current
size_t i = buffLen;
while (i && times[i] < times[i-1]){
uint64_t tmp = times[i-1];
times[i-1] = times[i];
times[i] = tmp;
--i;
//Keep track of maximum delay
if (!initialized && buffLen - i + 1 > maxDelay){
maxDelay = buffLen - i + 1;
}
}
}
}
void remove(){
++rem;
size_t buffLen = ctr-rem;
if (buffLen >= PKT_COUNT){buffLen = PKT_COUNT-1;}
for (size_t i = 0; i < buffLen; ++i){times[i] = times[i+1];}
}
void remove(){++rem;}
};
class InputEBML : public Input{