From 7df7c04afff82e46e658e241b0fd68edc338e269 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 23 Nov 2016 12:51:24 +0100 Subject: [PATCH] Backported various Pro MP4 updates/fixes to free version --- src/output/output_progressive_mp4.cpp | 486 +++++++++++++------------- src/output/output_progressive_mp4.h | 7 +- 2 files changed, 256 insertions(+), 237 deletions(-) diff --git a/src/output/output_progressive_mp4.cpp b/src/output/output_progressive_mp4.cpp index 42dbcc64..1bb9a5ca 100644 --- a/src/output/output_progressive_mp4.cpp +++ b/src/output/output_progressive_mp4.cpp @@ -1,8 +1,9 @@ -#include "output_progressive_mp4.h" #include #include #include #include +#include +#include "output_progressive_mp4.h" namespace Mist { OutProgressiveMP4::OutProgressiveMP4(Socket::Connection & conn) : HTTPOutput(conn){} @@ -33,214 +34,207 @@ namespace Mist { return retVal * 1.1; } + ///\todo This function does not indicate errors anywhere... maybe fix this... std::string OutProgressiveMP4::DTSCMeta2MP4Header(long long & size){ std::stringstream header; - //ftyp box - MP4::FTYP ftypBox; - header.write(ftypBox.asBox(),ftypBox.boxedSize()); - bool biggerThan4G = (estimateFileSize() > 0xFFFFFFFFull); + //Determines whether the outputfile is larger than 4GB, in which case we need to use 64-bit boxes for offsets + bool useLargeBoxes = (estimateFileSize() > 0xFFFFFFFFull); + //Keeps track of the total size of the mdat box uint64_t mdatSize = 0; - //moov box + + + //Start actually creating the header + + //MP4 Files always start with an FTYP box. Constructor sets default values + MP4::FTYP ftypBox; + header.write(ftypBox.asBox(), ftypBox.boxedSize()); + + //Start building the moov box. This is the metadata box for an mp4 file, and will contain all metadata. MP4::MOOV moovBox; + //Keep track of the current index within the moovBox unsigned int moovOffset = 0; - { - //calculating longest duration - long long int firstms = -1; - long long int lastms = -1; - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { - if (lastms == -1 || lastms < (long long)myMeta.tracks[*it].lastms){ - lastms = myMeta.tracks[*it].lastms; - } - if (firstms == -1 || firstms > (long long)myMeta.tracks[*it].firstms){ - firstms = myMeta.tracks[*it].firstms; - } - } - MP4::MVHD mvhdBox(lastms - firstms); - moovBox.setContent(mvhdBox, moovOffset++); + //calculating longest duration + long long unsigned firstms = 0xFFFFFFFFFFFFFFull; + long long unsigned lastms = 0; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { + lastms = std::max(lastms, myMeta.tracks[*it].lastms); + firstms = std::min(firstms, myMeta.tracks[*it].firstms); } - for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { + MP4::MVHD mvhdBox(lastms - firstms); + //Set the trackid for the first "empty" track within the file. + mvhdBox.setTrackID(selectedTracks.size() + 1); + moovBox.setContent(mvhdBox, moovOffset++); + + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { DTSC::Track & thisTrack = myMeta.tracks[*it]; MP4::TRAK trakBox; - { - { - MP4::TKHD tkhdBox(*it, thisTrack.lastms - thisTrack.firstms, thisTrack.width, thisTrack.height); - trakBox.setContent(tkhdBox, 0); - }{ - MP4::MDIA mdiaBox; - unsigned int mdiaOffset = 0; - { - MP4::MDHD mdhdBox(thisTrack.lastms - thisTrack.firstms); - mdhdBox.setLanguage(thisTrack.lang); - mdiaBox.setContent(mdhdBox, mdiaOffset++); - }//MDHD box - { - MP4::HDLR hdlrBox(thisTrack.type, thisTrack.getIdentifier()); - mdiaBox.setContent(hdlrBox, mdiaOffset++); - }//hdlr box - { - MP4::MINF minfBox; - unsigned int minfOffset = 0; - if (thisTrack.type== "video"){ - MP4::VMHD vmhdBox; - vmhdBox.setFlags(1); - minfBox.setContent(vmhdBox,minfOffset++); - }else if (thisTrack.type == "audio"){ - MP4::SMHD smhdBox; - minfBox.setContent(smhdBox,minfOffset++); - }//type box - { - MP4::DINF dinfBox; - MP4::DREF drefBox; - dinfBox.setContent(drefBox,0); - minfBox.setContent(dinfBox,minfOffset++); - }//dinf box - { - MP4::STBL stblBox; - unsigned int offset = 0; - { - MP4::STSD stsdBox; - stsdBox.setVersion(0); - if (thisTrack.type == "video"){//boxname = codec - MP4::VisualSampleEntry vse; - if (thisTrack.codec == "H264"){ - vse.setCodec("avc1"); - } - if (thisTrack.codec == "HEVC"){ - vse.setCodec("hev1"); - } - vse.setDataReferenceIndex(1); - vse.setWidth(thisTrack.width); - vse.setHeight(thisTrack.height); - if (thisTrack.codec == "H264"){ - MP4::AVCC avccBox; - avccBox.setPayload(thisTrack.init); - vse.setCLAP(avccBox); - } - stsdBox.setEntry(vse,0); - }else if(thisTrack.type == "audio"){//boxname = codec - MP4::AudioSampleEntry ase; - if (thisTrack.codec == "AAC"){ - ase.setCodec("mp4a"); - ase.setDataReferenceIndex(1); - }else if (thisTrack.codec == "MP3"){ - ase.setCodec("mp4a"); - ase.setDataReferenceIndex(1); - } - ase.setSampleRate(thisTrack.rate); - ase.setChannelCount(thisTrack.channels); - ase.setSampleSize(thisTrack.size); - MP4::ESDS esdsBox(thisTrack.init); - ase.setCodecBox(esdsBox); - stsdBox.setEntry(ase,0); - } - stblBox.setContent(stsdBox,offset++); - }//stsd box - { - MP4::STTS sttsBox; - sttsBox.setVersion(0); - if (thisTrack.parts.size()){ - for (unsigned int part = thisTrack.parts.size(); part > 0; --part){ - MP4::STTSEntry newEntry; - newEntry.sampleCount = 1; - newEntry.sampleDelta = thisTrack.parts[part-1].getDuration(); - sttsBox.setSTTSEntry(newEntry, part-1); - } - } - stblBox.setContent(sttsBox,offset++); - }//stts box - if (thisTrack.type == "video"){ - //STSS Box here - MP4::STSS stssBox; - stssBox.setVersion(0); - int tmpCount = 0; - int tmpItCount = 0; - for ( std::deque< DTSC::Key>::iterator tmpIt = thisTrack.keys.begin(); tmpIt != thisTrack.keys.end(); tmpIt ++) { - stssBox.setSampleNumber(tmpCount,tmpItCount); - tmpCount += tmpIt->getParts(); - tmpItCount ++; - } - stblBox.setContent(stssBox,offset++); - }//stss box - { - MP4::STSC stscBox; - stscBox.setVersion(0); - MP4::STSCEntry stscEntry; - stscEntry.firstChunk = 1; - stscEntry.samplesPerChunk = 1; - stscEntry.sampleDescriptionIndex = 1; - stscBox.setSTSCEntry(stscEntry, 0); - stblBox.setContent(stscBox,offset++); - }//stsc box - { - bool makeCTTS = false; - MP4::STSZ stszBox; - stszBox.setVersion(0); - if (thisTrack.parts.size()){ - std::deque::reverse_iterator tmpIt = thisTrack.parts.rbegin(); - for (unsigned int part = thisTrack.parts.size(); part > 0; --part){ - unsigned int partSize = tmpIt->getSize(); - stszBox.setEntrySize(partSize, part-1);//in bytes in file - size += partSize; - makeCTTS |= tmpIt->getOffset(); - tmpIt++; - } - } - if (makeCTTS){ - MP4::CTTS cttsBox; - cttsBox.setVersion(0); - if (thisTrack.parts.size()){ - std::deque::iterator tmpIt = thisTrack.parts.begin(); - MP4::CTTSEntry tmpEntry; - tmpEntry.sampleCount = 1; - tmpEntry.sampleOffset = tmpIt->getOffset(); - unsigned int totalEntries = 0; - tmpIt++; - while (tmpIt != thisTrack.parts.end()){ - unsigned int timeOffset = tmpIt->getOffset(); - if (timeOffset == tmpEntry.sampleOffset){ - tmpEntry.sampleCount++; - }else{ - cttsBox.setCTTSEntry(tmpEntry, totalEntries++); - tmpEntry.sampleCount = 1; - tmpEntry.sampleOffset = timeOffset; - } - tmpIt++; - } - cttsBox.setCTTSEntry(tmpEntry, totalEntries++); - //cttsBox.setEntryCount(totalEntries); - } - stblBox.setContent(cttsBox,offset++); - }//ctts - stblBox.setContent(stszBox,offset++); - }//stsz box - { - if (biggerThan4G){ - MP4::CO64 CO64Box; - //Inserting empty values on purpose here, will be fixed later. - if (thisTrack.parts.size() != 0){ - CO64Box.setChunkOffset(0, thisTrack.parts.size() - 1);//this inserts all empty entries at once - } - stblBox.setContent(CO64Box,offset++); - }else{ - MP4::STCO stcoBox; - //Inserting empty values on purpose here, will be fixed later. - if (thisTrack.parts.size() != 0){ - stcoBox.setChunkOffset(0, thisTrack.parts.size() - 1);//this inserts all empty entries at once - } - stblBox.setContent(stcoBox,offset++); - } - }//stco box - minfBox.setContent(stblBox,minfOffset++); - }//stbl box - mdiaBox.setContent(minfBox, mdiaOffset++); - }//minf box - trakBox.setContent(mdiaBox, 1); + //Keep track of the current index within the moovBox + unsigned int trakOffset = 0; + + MP4::TKHD tkhdBox(thisTrack, false); + trakBox.setContent(tkhdBox, trakOffset++); + + //Create an EDTS box, containing an ELST box with default values; + ///\todo Figure out if this box is really needed for anything. + MP4::EDTS edtsBox; + MP4::ELST elstBox; + elstBox.setVersion(0); + elstBox.setFlags(0); + elstBox.setCount(1); + elstBox.setSegmentDuration(thisTrack.lastms - thisTrack.firstms); + elstBox.setMediaTime(0); + elstBox.setMediaRateInteger(1); + elstBox.setMediaRateFraction(0); + edtsBox.setContent(elstBox, 0); + trakBox.setContent(edtsBox, trakOffset++); + + MP4::MDIA mdiaBox; + unsigned int mdiaOffset = 0; + + //Add the mandatory MDHD and HDLR boxes to the MDIA + MP4::MDHD mdhdBox(thisTrack.lastms - thisTrack.firstms); + mdhdBox.setLanguage(thisTrack.lang); + mdiaBox.setContent(mdhdBox, mdiaOffset++); + MP4::HDLR hdlrBox(thisTrack.type, thisTrack.getIdentifier()); + mdiaBox.setContent(hdlrBox, mdiaOffset++); + + MP4::MINF minfBox; + unsigned int minfOffset = 0; + + //Add a track-type specific box to the MINF box + if (thisTrack.type == "video") { + MP4::VMHD vmhdBox; + vmhdBox.setFlags(1); + minfBox.setContent(vmhdBox, minfOffset++); + } else if (thisTrack.type == "audio") { + MP4::SMHD smhdBox; + minfBox.setContent(smhdBox, minfOffset++); + } + + //Add the mandatory DREF (dataReference) box + MP4::DINF dinfBox; + MP4::DREF drefBox; + dinfBox.setContent(drefBox, 0); + minfBox.setContent(dinfBox, minfOffset++); + + + + MP4::STBL stblBox; + unsigned int stblOffset = 0; + + //Add STSD box + MP4::STSD stsdBox(0); + if (thisTrack.type == "video") { + MP4::VisualSampleEntry sampleEntry(thisTrack); + stsdBox.setEntry(sampleEntry, 0); + } else if (thisTrack.type == "audio") { + MP4::AudioSampleEntry sampleEntry(thisTrack); + stsdBox.setEntry(sampleEntry, 0); + } + stblBox.setContent(stsdBox, stblOffset++); + + //Add STTS Box + MP4::STTS sttsBox(0); + std::deque > sttsCounter; + for (unsigned int part = 0; part < thisTrack.parts.size(); ++part) { + //Create a new entry with current duration if EITHER there is no entry yet, or this parts duration differs from the previous + if (!sttsCounter.size() || sttsCounter.rbegin()->second != thisTrack.parts[part].getDuration()){ + //Set the counter to 0, so we don't have to handle this situation diffent when updating + sttsCounter.push_back(std::pair(0, thisTrack.parts[part].getDuration())); } - }//trak Box + //Then update the counter + sttsCounter.rbegin()->first++; + } + + //Write all entries in reverse + for (unsigned int entry = sttsCounter.size(); entry > 0; --entry){ + MP4::STTSEntry newEntry; + newEntry.sampleCount = sttsCounter[entry - 1].first;; + newEntry.sampleDelta = sttsCounter[entry - 1].second; + sttsBox.setSTTSEntry(newEntry, entry - 1);///\todo rewrite for sanity + } + stblBox.setContent(sttsBox, stblOffset++); + + //Add STSS Box IF type is video and we are not fragmented + if (thisTrack.type == "video") { + MP4::STSS stssBox(0); + int tmpCount = 0; + for (int i = 0; i < thisTrack.keys.size(); i++){ + stssBox.setSampleNumber(tmpCount + 1, i);///\todo PLEASE rewrite this for sanity.... SHOULD be: index FIRST, value SECOND + tmpCount += thisTrack.keys[i].getParts(); + } + stblBox.setContent(stssBox, stblOffset++); + } + + //Add STSC Box + MP4::STSC stscBox(0); + MP4::STSCEntry stscEntry(1,1,1); + stscBox.setSTSCEntry(stscEntry, 0); + stblBox.setContent(stscBox, stblOffset++); + + bool containsOffsets = false; + + //Add STSZ Box + MP4::STSZ stszBox(0); + if (thisTrack.parts.size()) { + std::deque::reverse_iterator tmpIt = thisTrack.parts.rbegin(); + for (unsigned int part = thisTrack.parts.size(); part > 0; --part) { + ///\todo rewrite for sanity + stszBox.setEntrySize(tmpIt->getSize(), part - 1); //in bytes in file + size += tmpIt->getSize(); + containsOffsets |= tmpIt->getOffset(); + tmpIt++; + } + } + stblBox.setContent(stszBox, stblOffset++); + + //Add CTTS Box only if the track contains time offsets + if (containsOffsets) { + MP4::CTTS cttsBox; + cttsBox.setVersion(0); + + MP4::CTTSEntry tmpEntry; + tmpEntry.sampleCount = 0; + tmpEntry.sampleOffset = thisTrack.parts[0].getOffset(); + unsigned int totalEntries = 0; + for (std::deque::iterator tmpIt = thisTrack.parts.begin(); tmpIt != thisTrack.parts.end(); tmpIt++){ + if (tmpIt->getOffset() != tmpEntry.sampleOffset) { + //If the offset of this and previous part differ, write current values and reset + cttsBox.setCTTSEntry(tmpEntry, totalEntries++);///\todo Again, rewrite for sanity. index FIRST, value SECOND + tmpEntry.sampleCount = 0; + tmpEntry.sampleOffset = tmpIt->getOffset(); + } + tmpEntry.sampleCount++; + } + //set the last entry + cttsBox.setCTTSEntry(tmpEntry, totalEntries++); + stblBox.setContent(cttsBox, stblOffset++); + } + + + + //Create STCO Box (either stco or co64) + //note: Inserting empty values on purpose here, will be fixed later. + if (useLargeBoxes) { + MP4::CO64 CO64Box; + CO64Box.setChunkOffset(0, thisTrack.parts.size() - 1); + stblBox.setContent(CO64Box, stblOffset++); + } else { + MP4::STCO stcoBox(0); + stcoBox.setChunkOffset(0, thisTrack.parts.size() - 1); + stblBox.setContent(stcoBox, stblOffset++); + } + + minfBox.setContent(stblBox, minfOffset++); + + mdiaBox.setContent(minfBox, mdiaOffset++); + + trakBox.setContent(mdiaBox, 2); + moovBox.setContent(trakBox, moovOffset++); - }//for each selected track + } //initial offset length ftyp, length moov + 8 - unsigned long long int byteOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8; + unsigned long long int dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8; //update all STCO or CO64 from the following maps; std::map checkStcoBoxes; std::map checkCO64Boxes; @@ -288,7 +282,8 @@ namespace Mist { } //inserting right values in the STCO box header //total = 0; - long long unsigned int totalByteOffset = 0; + //Keep track of the current size of the data within the mdat + long long unsigned int dataSize = 0; //Current values are actual byte offset without header-sized offset std::set sortSet;//filling sortset for interleaving parts for (std::set::iterator subIt = selectedTracks.begin(); subIt != selectedTracks.end(); subIt++) { @@ -298,38 +293,41 @@ namespace Mist { temp.endTime = myMeta.tracks[*subIt].firstms + myMeta.tracks[*subIt].parts[0].getDuration(); temp.size = myMeta.tracks[*subIt].parts[0].getSize();//bytesize of frame (alle parts all together) temp.index = 0; + INFO_MSG("adding to sortSet: tid %lu time %llu", temp.trackID, temp.time); sortSet.insert(temp); } while (!sortSet.empty()){ std::set::iterator keyBegin = sortSet.begin(); //setting the right STCO size in the STCO box - if (checkCO64Boxes.count(keyBegin->trackID)){ - checkCO64Boxes[keyBegin->trackID].setChunkOffset(totalByteOffset + byteOffset, keyBegin->index); - }else{ - checkStcoBoxes[keyBegin->trackID].setChunkOffset(totalByteOffset + byteOffset, keyBegin->index); + if (useLargeBoxes){//Re-using the previously defined boolean for speedup + checkCO64Boxes[keyBegin->trackID].setChunkOffset(dataOffset + dataSize, keyBegin->index); + } else { + checkStcoBoxes[keyBegin->trackID].setChunkOffset(dataOffset + dataSize, keyBegin->index); } - totalByteOffset += keyBegin->size; - //add keyPart to sortSet - keyPart temp; - temp.index = keyBegin->index + 1; - temp.trackID = keyBegin->trackID; - DTSC::Track & thisTrack = myMeta.tracks[temp.trackID]; - if(temp.index < thisTrack.parts.size() ){//only insert when there are parts left - temp.time = keyBegin->endTime;//timeplace of frame - temp.endTime = keyBegin->endTime + thisTrack.parts[temp.index].getDuration(); - temp.size = thisTrack.parts[temp.index].getSize();//bytesize of frame + dataSize += keyBegin->size; + + //add next keyPart to sortSet + DTSC::Track & thisTrack = myMeta.tracks[keyBegin->trackID]; + if (keyBegin->index < thisTrack.parts.size() - 1) {//Only create new element, when there are new elements to be added + keyPart temp = *keyBegin; + temp.index ++; + temp.time = temp.endTime; + temp.endTime += thisTrack.parts[temp.index].getDuration(); + temp.size = thisTrack.parts[temp.index].getSize();//bytesize of frame sortSet.insert(temp); } //remove highest keyPart sortSet.erase(keyBegin); } - mdatSize = totalByteOffset+8; + ///\todo Update this thing for boxes >4G? + mdatSize = dataSize + 8;//+8 for mp4 header - header.write(moovBox.asBox(),moovBox.boxedSize()); + header << std::string(moovBox.asBox(), moovBox.boxedSize()); - header << (char)((mdatSize>>24) & 0xFF) << (char)((mdatSize>>16) & 0xFF) << (char)((mdatSize>>8) & 0xFF) << (char)(mdatSize & 0xFF) << "mdat"; - //end of header + char mdatHeader[8] = {0x00,0x00,0x00,0x00,'m','d','a','t'}; + Bit::htobl(mdatHeader, mdatSize); + header.write(mdatHeader, 8); size += header.str().size(); return header.str(); @@ -340,7 +338,9 @@ namespace Mist { void OutProgressiveMP4::findSeekPoint(long long byteStart, long long & seekPoint, unsigned int headerSize){ seekPoint = 0; //if we're starting in the header, seekPoint is always zero. - if (byteStart <= headerSize){return;} + if (byteStart <= headerSize) { + return; + } //okay, we're past the header. Substract the headersize from the starting postion. byteStart -= headerSize; //forward through the file by headers, until we reach the point where we need to be @@ -350,7 +350,10 @@ namespace Mist { //substract the size of this fragment from byteStart byteStart -= sortSet.begin()->size; //if that put us past the point where we wanted to be, return right now - if (byteStart < 0){return;} + if (byteStart < 0) { + INFO_MSG("We're starting at time %lld, skipping %lld bytes", seekPoint, byteStart+sortSet.begin()->size); + return; + } //otherwise, set currPos to where we are now and continue currPos += sortSet.begin()->size; //find the next part @@ -431,7 +434,9 @@ namespace Mist { } break; } - if (byteEnd > size - 1){byteEnd = size - 1;} + if (byteEnd > size - 1) { + byteEnd = size - 1; + } }else{ byteEnd = size; } @@ -450,15 +455,22 @@ namespace Mist { return; } + //Always initialize before anything else initialize(); + + //Make sure we start receiving data after this function + ///\todo Should this happen here? parseData = true; wantRequest = false; sentHeader = false; - fileSize = 0; + + //For storing the header. + ///\todo Do we really need this though? std::string headerData = DTSCMeta2MP4Header(fileSize); + + seekPoint = 0; byteStart = 0; byteEnd = fileSize - 1; - seekPoint = 0; char rangeType = ' '; currPos = 0; sortSet.clear(); @@ -514,7 +526,6 @@ namespace Mist { leftOver = byteEnd - byteStart + 1;//add one byte, because range "0-0" = 1 byte of data if (byteStart < (long long)headerData.size()){ /// \todo Switch to chunked? - //H.Chunkify(headerData.data()+byteStart, std::min((long long)headerData.size(), byteEnd) - byteStart, conn);//send MP4 header myConn.SendNow(headerData.data()+byteStart, std::min((long long)headerData.size(), byteEnd) - byteStart);//send MP4 header leftOver -= std::min((long long)headerData.size(), byteEnd) - byteStart; } @@ -523,11 +534,13 @@ namespace Mist { void OutProgressiveMP4::sendNext(){ static bool perfect = true; + + //Obtain a pointer to the data of this packet char * dataPointer = 0; unsigned int len = 0; thisPacket.getString("data", dataPointer, len); if ((unsigned long)thisPacket.getTrackId() != sortSet.begin()->trackID || thisPacket.getTime() != sortSet.begin()->time){ - if (thisPacket.getTime() >= sortSet.begin()->time || (unsigned long)thisPacket.getTrackId() >= sortSet.begin()->trackID){ + if (thisPacket.getTime() > sortSet.begin()->time || (unsigned long)thisPacket.getTrackId() > sortSet.begin()->trackID) { if (perfect){ DEBUG_MSG(DLVL_WARN, "Warning: input is inconsistent. Expected %lu:%llu but got %ld:%llu - cancelling playback", sortSet.begin()->trackID, sortSet.begin()->time, thisPacket.getTrackId(), thisPacket.getTime()); perfect = false; @@ -538,6 +551,17 @@ namespace Mist { } return; } + + if (currPos >= byteStart) { + myConn.SendNow(dataPointer, std::min(leftOver, (long long)len)); + leftOver -= len; + } else { + if (currPos + (long long)len > byteStart) { + myConn.SendNow(dataPointer + (byteStart - currPos), std::min(leftOver, (long long)(len - (byteStart - currPos)))); + leftOver -= len - (byteStart - currPos); + } + } + //keep track of where we are if (!sortSet.empty()){ keyPart temp; @@ -555,19 +579,9 @@ namespace Mist { } - if (currPos >= byteStart){ - myConn.SendNow(dataPointer, std::min(leftOver, (long long)len)); - //H.Chunkify(Strm.lastData().data(), Strm.lastData().size(), conn); - leftOver -= len; - }else{ - if (currPos + (long long)len > byteStart){ - myConn.SendNow(dataPointer+(byteStart-currPos), len-(byteStart-currPos)); - leftOver -= len-(byteStart-currPos); - currPos = byteStart; - } - } - //sortSet.clear();//we don't need you anymore! + if (leftOver < 1){ + //stop playback, wait for new request stop(); wantRequest = true; diff --git a/src/output/output_progressive_mp4.h b/src/output/output_progressive_mp4.h index 367dbe15..eb69c2a7 100644 --- a/src/output/output_progressive_mp4.h +++ b/src/output/output_progressive_mp4.h @@ -12,6 +12,9 @@ namespace Mist { if (trackID < rhs.trackID){ return true; } + if (trackID == rhs.trackID){ + return index < rhs.index; + } } return false; } @@ -40,8 +43,10 @@ namespace Mist { long long leftOver; long long currPos; long long seekPoint; - std::set sortSet;//filling sortset for interleaving parts + //variables for standard MP4 + std::set sortSet;//needed for unfragmented MP4, remembers the order of keyparts + long long unsigned estimateFileSize(); }; }