Fixes to better support Safari/MacOS/iOS playback of MP4 output

Co-authored-by: Marco van Dijk <marco@stronk.rocks>
Co-authored-by: Thulinma <jaron@vietors.com>
This commit is contained in:
Balder 2023-07-05 17:07:41 +02:00 committed by Thulinma
parent 963d7846a8
commit d63190387a
3 changed files with 42 additions and 18 deletions

View file

@ -2007,8 +2007,8 @@ namespace MP4{
TKHD::TKHD(const DTSC::Meta &M, size_t idx){ TKHD::TKHD(const DTSC::Meta &M, size_t idx){
initialize(); initialize();
setTrackID(idx + 1); setTrackID(idx + 1);
setDuration(-1); setDuration(0);
if (M.getVod()){setDuration(M.getLastms(idx) - M.getFirstms(idx));} if (!M.getLive()){setDuration(M.getLastms(idx) - M.getFirstms(idx));}
if (M.getType(idx) == "video"){ if (M.getType(idx) == "video"){
setWidth(M.getWidth(idx)); setWidth(M.getWidth(idx));
setHeight(M.getHeight(idx)); setHeight(M.getHeight(idx));
@ -2757,7 +2757,7 @@ namespace MP4{
setCLAP(avccBox); setCLAP(avccBox);
} }
if (tCodec == "HEVC"){ if (tCodec == "HEVC"){
setCodec("hev1"); setCodec("hvc1");
MP4::HVCC hvccBox; MP4::HVCC hvccBox;
hvccBox.setPayload(M.getInit(idx)); hvccBox.setPayload(M.getInit(idx));
setCLAP(hvccBox); setCLAP(hvccBox);

View file

@ -111,6 +111,7 @@ namespace Mist{
startTime = 0; startTime = 0;
endTime = 0xffffffffffffffffull; endTime = 0xffffffffffffffffull;
realBaseOffset = 1; realBaseOffset = 1;
timeOffset = 0;
} }
OutMP4::~OutMP4(){} OutMP4::~OutMP4(){}
@ -154,7 +155,7 @@ namespace Mist{
for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){
DTSC::Keys keys = M.getKeys(it->first); DTSC::Keys keys = M.getKeys(it->first);
size_t endKey = keys.getEndValid(); size_t endKey = keys.getEndValid();
for (size_t i = 0; i < endKey; i++){ for (size_t i = keys.getFirstValid(); i < endKey; i++){
retVal += keys.getSize(i); // Handle number as index, faster for VoD retVal += keys.getSize(i); // Handle number as index, faster for VoD
} }
} }
@ -176,7 +177,7 @@ namespace Mist{
for (std::map<size_t, Comms::Users>::const_iterator subIt = userSelect.begin(); for (std::map<size_t, Comms::Users>::const_iterator subIt = userSelect.begin();
subIt != userSelect.end(); subIt++){ subIt != userSelect.end(); subIt++){
tmpRes += 8 + 20; // TRAF + TFHD Box tmpRes += 8 + 20 + 20; // TRAF + TFHD + TFDT Box
DTSC::Keys keys = M.getKeys(subIt->first); DTSC::Keys keys = M.getKeys(subIt->first);
DTSC::Parts parts(M.parts(subIt->first)); DTSC::Parts parts(M.parts(subIt->first));
@ -377,7 +378,7 @@ namespace Mist{
uint64_t firstms = 0xFFFFFFFFFFFFFFull; uint64_t firstms = 0xFFFFFFFFFFFFFFull;
// Construct with duration of -1, as this is the default for fragmented // Construct with duration of -1, as this is the default for fragmented
MP4::MVHD mvhdBox(-1); MP4::MVHD mvhdBox(0);
// Then override it when we are not sending a VoD asset // Then override it when we are not sending a VoD asset
if (!M.getLive()){ if (!M.getLive()){
// calculating longest duration // calculating longest duration
@ -428,7 +429,7 @@ namespace Mist{
elstBox.setMediaRateFraction(1, 0); elstBox.setMediaRateFraction(1, 0);
}else{ }else{
elstBox.setCount(1); elstBox.setCount(1);
elstBox.setSegmentDuration(0, fragmented ? -1 : tDuration); elstBox.setSegmentDuration(0, fragmented ? 0 : tDuration);
elstBox.setMediaTime(0, 0); elstBox.setMediaTime(0, 0);
elstBox.setMediaRateInteger(0, 1); elstBox.setMediaRateInteger(0, 1);
elstBox.setMediaRateFraction(0, 0); elstBox.setMediaRateFraction(0, 0);
@ -441,7 +442,7 @@ namespace Mist{
// Add the mandatory MDHD and HDLR boxes to the MDIA // Add the mandatory MDHD and HDLR boxes to the MDIA
MP4::MDHD mdhdBox(tDuration); MP4::MDHD mdhdBox(tDuration);
if (fragmented){mdhdBox.setDuration(-1);} if (fragmented){mdhdBox.setDuration(0);}
mdhdBox.setLanguage(M.getLang(it->first)); mdhdBox.setLanguage(M.getLang(it->first));
mdiaBox.setContent(mdhdBox, mdiaOffset++); mdiaBox.setContent(mdhdBox, mdiaOffset++);
MP4::HDLR hdlrBox(tType, M.getTrackIdentifier(it->first)); MP4::HDLR hdlrBox(tType, M.getTrackIdentifier(it->first));
@ -661,7 +662,7 @@ namespace Mist{
MP4::MVEX mvexBox; MP4::MVEX mvexBox;
size_t curBox = 0; size_t curBox = 0;
MP4::MEHD mehdBox; MP4::MEHD mehdBox;
mehdBox.setFragmentDuration(M.getDuration(mainTrack)); mehdBox.setFragmentDuration(0);
mvexBox.setContent(mehdBox, curBox++); mvexBox.setContent(mehdBox, curBox++);
for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin();
@ -868,7 +869,7 @@ namespace Mist{
trafBox.setContent(tfdtBox, 1); trafBox.setContent(tfdtBox, 1);
MP4::TRUN trunBox; MP4::TRUN trunBox;
trunBox.setFirstSampleFlags(MP4::isIPicture | MP4::isKeySample); trunBox.setFirstSampleFlags(thisPacket.getFlag("keyframe") ? (MP4::isIPicture | MP4::isKeySample) : (MP4::noIPicture | MP4::noKeySample));
trunBox.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleSize | trunBox.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleSize |
MP4::trunsampleDuration | MP4::trunsampleOffsets); MP4::trunsampleDuration | MP4::trunsampleOffsets);
@ -916,6 +917,7 @@ namespace Mist{
sortSet.clear(); sortSet.clear();
std::set<keyPart> trunOrder; std::set<keyPart> trunOrder;
std::set<size_t> keyParts;
std::deque<size_t> sortedTracks; std::deque<size_t> sortedTracks;
if (endFragmentTime == 0){ if (endFragmentTime == 0){
@ -951,6 +953,11 @@ namespace Mist{
temp.time = timeStamp; temp.time = timeStamp;
temp.index = p; temp.index = p;
trunOrder.insert(temp); trunOrder.insert(temp);
uint64_t keyTime = M.getTimeForKeyIndex(subIt->first, M.getKeyIndexForTime(subIt->first, timeStamp));
if (keyTime == timeStamp){
keyParts.insert(p);
}
} }
timeStamp += parts.getDuration(p); timeStamp += parts.getDuration(p);
@ -987,8 +994,6 @@ namespace Mist{
} }
trunOrder.clear(); // erase the trunOrder set, to keep memory usage down trunOrder.clear(); // erase the trunOrder set, to keep memory usage down
bool firstSample = true;
for (std::deque<size_t>::iterator it = sortedTracks.begin(); it != sortedTracks.end(); ++it){ for (std::deque<size_t>::iterator it = sortedTracks.begin(); it != sortedTracks.end(); ++it){
size_t tid = *it; size_t tid = *it;
DTSC::Parts parts(M.parts(*it)); DTSC::Parts parts(M.parts(*it));
@ -1004,9 +1009,14 @@ namespace Mist{
tfhdBox.setDefaultSampleSize(444); tfhdBox.setDefaultSampleSize(444);
tfhdBox.setDefaultSampleFlags(tid == vidTrack ? (MP4::noIPicture | MP4::noKeySample) tfhdBox.setDefaultSampleFlags(tid == vidTrack ? (MP4::noIPicture | MP4::noKeySample)
: (MP4::isIPicture | MP4::isKeySample)); : (MP4::isIPicture | MP4::isKeySample));
trafBox.setContent(tfhdBox, 0); unsigned int trafOffset = 0;
trafBox.setContent(tfhdBox, trafOffset++);
MP4::TFDT tfdtBox;
tfdtBox.setBaseMediaDecodeTime(startFragmentTime - timeOffset);
trafBox.setContent(tfdtBox, trafOffset++);
unsigned int trafOffset = 1;
for (std::set<keyPart>::iterator trunIt = sortSet.begin(); trunIt != sortSet.end(); trunIt++){ for (std::set<keyPart>::iterator trunIt = sortSet.begin(); trunIt != sortSet.end(); trunIt++){
if (trunIt->trackID == tid){ if (trunIt->trackID == tid){
DTSC::Parts parts(M.parts(trunIt->trackID)); DTSC::Parts parts(M.parts(trunIt->trackID));
@ -1021,8 +1031,11 @@ namespace Mist{
// The value set here, will be updated afterwards to the correct value // The value set here, will be updated afterwards to the correct value
trunBox.setDataOffset(trunIt->byteOffset); trunBox.setDataOffset(trunIt->byteOffset);
trunBox.setFirstSampleFlags(MP4::isIPicture | (firstSample ? MP4::isKeySample : MP4::noKeySample)); bool isKeyFrame = keyParts.count(trunIt->index);
firstSample = false; if (M.getType(trunIt->trackID) != "video"){
isKeyFrame = true;
}
trunBox.setFirstSampleFlags(isKeyFrame ? (MP4::isKeySample | MP4::isIPicture) : (MP4::noKeySample | MP4::noIPicture));
MP4::trunSampleInformation sampleInfo; MP4::trunSampleInformation sampleInfo;
sampleInfo.sampleSize = partSize; sampleInfo.sampleSize = partSize;
@ -1197,8 +1210,17 @@ namespace Mist{
return; return;
} }
} }
sendFragmentHeaderTime(thisPacket.getTime(), thisPacket.getTime() + needsLookAhead);
nextHeaderTime = thisPacket.getTime() + needsLookAhead; nextHeaderTime = thisTime + needsLookAhead;
if (targetParams.count("recstop")){
uint64_t planStop = atoll(targetParams["recstop"].c_str());
if (planStop < nextHeaderTime){nextHeaderTime = planStop;}
}
if (targetParams.count("stop")){
uint64_t planStop = atoll(targetParams["stop"].c_str());
if (planStop < nextHeaderTime){nextHeaderTime = planStop;}
}
if (thisTime < nextHeaderTime){sendFragmentHeaderTime(thisTime, nextHeaderTime);}
}else{ }else{
if (startTime || endTime != 0xffffffffffffffffull){ if (startTime || endTime != 0xffffffffffffffffull){
sendFragmentHeaderTime(startTime, endTime); sendFragmentHeaderTime(startTime, endTime);
@ -1336,6 +1358,7 @@ namespace Mist{
INFO_MSG("Increased initial lookAhead of %" PRIu64 "ms", needsLookAhead); INFO_MSG("Increased initial lookAhead of %" PRIu64 "ms", needsLookAhead);
initialSeek(); initialSeek();
} }
timeOffset = currentTime();
}else{ }else{
seek(seekPoint); seek(seekPoint);
} }

View file

@ -118,6 +118,7 @@ namespace Mist{
int64_t leftOver; int64_t leftOver;
uint64_t currPos; uint64_t currPos;
uint64_t seekPoint; uint64_t seekPoint;
int64_t timeOffset;
uint64_t nextHeaderTime; uint64_t nextHeaderTime;
uint64_t headerSize; uint64_t headerSize;