diff --git a/lib/defines.h b/lib/defines.h index efcbd0b4..f0a59cee 100644 --- a/lib/defines.h +++ b/lib/defines.h @@ -37,6 +37,9 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", " #endif #endif +#if defined(_WIN32) || defined(__CYGWIN__) +static inline void show_stackframe(){} +#else #include static inline void show_stackframe() { void *trace[16]; @@ -52,6 +55,7 @@ static inline void show_stackframe() { DEBUG_MSG(0, "Backtrace[%d]: %s", i, messages[i]+p); } } +#endif #else diff --git a/lib/mp4.cpp b/lib/mp4.cpp index edf7e3ec..fcb0c340 100644 --- a/lib/mp4.cpp +++ b/lib/mp4.cpp @@ -67,13 +67,25 @@ namespace MP4 { } } + void Box::copyFrom(const Box & rs){ + clear(); + if (data) { + free(data); + data = 0; + } + data = (char*)malloc(rs.data_size); + memcpy(data, rs.data, rs.data_size); + data_size = rs.data_size; + managed = true; + payloadOffset = rs.payloadOffset; + } /// Returns the values at byte positions 4 through 7. std::string Box::getType() { return std::string(data + 4, 4); } /// Returns true if the given 4-byte boxtype is equal to the values at byte positions 4 through 7. - bool Box::isType(const char * boxType) { + bool Box::isType(const char * boxType) const { return !memcmp(boxType, data + 4, 4); } @@ -790,6 +802,29 @@ namespace MP4 { return getBox(tempLoc); } + Box containerBox::getChild(const char * boxName){ + uint32_t count = getContentCount(); + for (uint32_t i = 0; i < count; i++){ + Box & thisChild = getContent(i); + if (thisChild.isType(boxName)){ + return Box(thisChild.asBox(), false); + } + } + return Box((char*)"\000\000\000\010erro", false); + } + + std::deque containerBox::getChildren(const char * boxName){ + std::deque res; + uint32_t count = getContentCount(); + for (uint32_t i = 0; i < count; i++){ + Box & thisChild = getContent(i); + if (thisChild.isType(boxName)){ + res.push_back(Box(thisChild.asBox(), false)); + } + } + return res; + } + std::string containerBox::toPrettyString(uint32_t indent) { std::stringstream r; r << std::string(indent, ' ') << "[" << getType() << "] Container Box (" << boxedSize() << ")" << std::endl; diff --git a/lib/mp4.h b/lib/mp4.h index 4b18fc57..f817a171 100644 --- a/lib/mp4.h +++ b/lib/mp4.h @@ -26,10 +26,16 @@ namespace MP4 { Box(const Box & rs); Box & operator = (const Box & rs); ~Box(); + operator bool() const { + return data && data_size >= 8 && !isType("erro"); + } + void copyFrom(const Box & rs); + std::string getType(); - bool isType(const char * boxType); + bool isType(const char * boxType) const; bool read(FILE * newData); bool read(std::string & newData); + uint64_t boxedSize(); uint64_t payloadSize(); char * asBox(); @@ -84,6 +90,24 @@ namespace MP4 { void setContent(Box & newContent, uint32_t no); Box & getContent(uint32_t no, bool unsafe = false); std::string toPrettyString(uint32_t indent = 0); + Box getChild(const char * boxName); + template + T getChild(){ + T a; + MP4::Box r = getChild(a.getType().c_str()); + return (T&)r; + } + std::deque getChildren(const char * boxName); + template + std::deque getChildren(){ + T a; + std::deque tmpRes = getChildren(a.getType().c_str()); + std::deque res; + for (std::deque::iterator it = tmpRes.begin(); it != tmpRes.end(); it++){ + res.push_back((T&)*it); + } + return res; + } }; class containerFullBox: public fullBox { diff --git a/lib/mp4_generic.cpp b/lib/mp4_generic.cpp index f6808851..5475eee6 100644 --- a/lib/mp4_generic.cpp +++ b/lib/mp4_generic.cpp @@ -581,6 +581,26 @@ namespace MP4 { return payload() + 8; } + std::string AVCC::hexSPS(){ + std::stringstream res; + char * data = getSPS(); + uint32_t len = getSPSLen(); + for (int i = 0; i < len; i++){ + res << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; + } + return res.str(); + } + + std::string AVCC::hexPPS(){ + std::stringstream res; + char * data = getPPS(); + uint32_t len = getPPSLen(); + for (int i = 0; i < len; i++){ + res << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; + } + return res.str(); + } + void AVCC::setPPSNumber(uint32_t newPPSNumber) { int offset = 8 + getSPSLen(); setInt8(newPPSNumber, offset); @@ -617,9 +637,9 @@ namespace MP4 { r << std::string(indent + 1, ' ') << "Compatible Profiles: " << getCompatibleProfiles() << std::endl; r << std::string(indent + 1, ' ') << "Level: " << getLevel() << std::endl; r << std::string(indent + 1, ' ') << "SPS Number: " << getSPSNumber() << std::endl; - r << std::string(indent + 2, ' ') << getSPSLen() << " of SPS data" << std::endl; + r << std::string(indent + 2, ' ') << getSPSLen() << " of SPS data: " << hexSPS() << std::endl; r << std::string(indent + 1, ' ') << "PPS Number: " << getPPSNumber() << std::endl; - r << std::string(indent + 2, ' ') << getPPSLen() << " of PPS data" << std::endl; + r << std::string(indent + 2, ' ') << getPPSLen() << " of PPS data: " << hexPPS() << std::endl; return r.str(); } @@ -634,7 +654,7 @@ namespace MP4 { void AVCC::setPayload(std::string newPayload) { if (!reserve(0, payloadSize(), newPayload.size())) { - DEBUG_MSG(DLVL_ERROR, "Cannot allocate enough memory for payload"); + ERROR_MSG("Cannot allocate enough memory for payload"); return; } memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); @@ -842,7 +862,7 @@ namespace MP4 { void HVCC::setPayload(std::string newPayload) { if (!reserve(0, payloadSize(), newPayload.size())) { - DEBUG_MSG(DLVL_ERROR, "Cannot allocate enough memory for payload"); + ERROR_MSG("Cannot allocate enough memory for payload"); return; } memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); @@ -1478,7 +1498,7 @@ namespace MP4 { return r.str(); } - HDLR::HDLR(std::string & type, std::string name) { + HDLR::HDLR(const std::string & type, const std::string & name) { memcpy(data + 4, "hdlr", 4); //reserve an entire box, except for the string part at the end if (!reserve(0, 8, 32)) { @@ -2571,9 +2591,7 @@ namespace MP4 { for (unsigned int i = 0; i < getEntryCount(); i++) { static STTSEntry temp; temp = getSTTSEntry(i); - r << std::string(indent + 1, ' ') << "Entry[" << i << "]:" << std::endl; - r << std::string(indent + 2, ' ') << "SampleCount: " << temp.sampleCount << std::endl; - r << std::string(indent + 2, ' ') << "SampleDelta: " << temp.sampleDelta << std::endl; + r << std::string(indent + 1, ' ') << "Entry[" << i << "]: " << temp.sampleCount << " sample(s) of " << temp.sampleDelta << "ms each" << std::endl; } return r.str(); diff --git a/lib/mp4_generic.h b/lib/mp4_generic.h index 51764f3f..959bb9b1 100644 --- a/lib/mp4_generic.h +++ b/lib/mp4_generic.h @@ -113,11 +113,13 @@ namespace MP4 { void setSPS(std::string newSPS); uint32_t getSPSLen(); char * getSPS(); + std::string hexSPS(); void setPPSNumber(uint32_t newPPSNumber); uint32_t getPPSNumber(); void setPPS(std::string newPPS); uint32_t getPPSLen(); char * getPPS(); + std::string hexPPS(); std::string asAnnexB(); void setPayload(std::string newPayload); std::string toPrettyString(uint32_t indent = 0); @@ -333,7 +335,7 @@ namespace MP4 { class HDLR: public Box { public: - HDLR(std::string & type, std::string name); + HDLR(const std::string & type = "", const std::string & name = ""); void setHandlerType(const char * newHandlerType); std::string getHandlerType(); void setName(std::string newName); @@ -472,7 +474,7 @@ namespace MP4 { class TKHD: public fullBox { public: - TKHD(uint32_t trackId, uint64_t duration, uint32_t width, uint32_t height); + TKHD(uint32_t trackId = 0, uint64_t duration = 0, uint32_t width = 0, uint32_t height = 0); TKHD(DTSC::Track & track, bool fragmented); void setCreationTime(uint64_t newCreationTime); @@ -506,7 +508,7 @@ namespace MP4 { class MDHD: public fullBox { public: - MDHD(uint64_t duration); + MDHD(uint64_t duration = 0); void setCreationTime(uint64_t newCreationTime); uint64_t getCreationTime(); void setModificationTime(uint64_t newModificationTime); diff --git a/src/input/input_mp4.cpp b/src/input/input_mp4.cpp index 8f640e93..6472c789 100644 --- a/src/input/input_mp4.cpp +++ b/src/input/input_mp4.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -29,14 +30,12 @@ namespace Mist { cttsBox.clear(); stszBox.clear(); stcoBox.clear(); + co64Box.clear(); + stco64 = false; } - long unsigned int mp4TrackHeader::size(){ - if (!stszBox.asBox()){ - return 0; - }else{ - return stszBox.getSampleCount(); - } + uint64_t mp4TrackHeader::size(){ + return (stszBox.asBox() ? stszBox.getSampleCount() : 0); } void mp4TrackHeader::read(MP4::TRAK & trakBox){ @@ -44,80 +43,40 @@ namespace Mist { std::string tmp;//temporary string for copying box data MP4::Box trakLoopPeek; timeScale = 1; - //for all in trak - for (uint32_t j = 0; j < trakBox.getContentCount(); j++){ - trakLoopPeek = MP4::Box(trakBox.getContent(j).asBox(),false); - std::string trakBoxType = trakLoopPeek.getType(); - if (trakBoxType == "mdia"){//fi tkhd & if mdia - MP4::Box mdiaLoopPeek; - //for all in mdia - for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){ - mdiaLoopPeek = MP4::Box(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),false); - std::string mdiaBoxType = mdiaLoopPeek.getType(); - if (mdiaBoxType == "mdhd"){ - timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale(); - }else if (mdiaBoxType == "minf"){//fi hdlr - //for all in minf - //get all boxes: stco stsz,stss - MP4::Box minfLoopPeek; - for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){ - minfLoopPeek = MP4::Box(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),false); - std::string minfBoxType = minfLoopPeek.getType(); - ///\todo more stuff here - if (minfBoxType == "stbl"){ - MP4::Box stblLoopPeek; - for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){ - stblLoopPeek = MP4::Box(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),false); - std::string stblBoxType = stblLoopPeek.getType(); - if (stblBoxType == "stts"){ - tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); - sttsBox.clear(); - sttsBox.read(tmp); - }else if (stblBoxType == "ctts"){ - ///\todo this box should not have to be read, since its information is taken from the DTSH - tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); - cttsBox.clear(); - cttsBox.read(tmp); - hasCTTS = true; - }else if (stblBoxType == "stsz"){ - tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); - stszBox.clear(); - stszBox.read(tmp); - }else if (stblBoxType == "stco" || stblBoxType == "co64"){ - tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); - stcoBox.clear(); - stcoBox.read(tmp); - }else if (stblBoxType == "stsc"){ - tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize()); - stscBox.clear(); - stscBox.read(tmp); - } - }//rof stbl - }//fi stbl - }//rof minf - }//fi minf - }//rof mdia - }//fi mdia - }//rof trak + + MP4::MDIA mdiaBox = trakBox.getChild(); + + timeScale = mdiaBox.getChild().getTimeScale(); + + MP4::STBL stblBox = mdiaBox.getChild().getChild(); + + sttsBox.copyFrom(stblBox.getChild()); + cttsBox.copyFrom(stblBox.getChild()); + stszBox.copyFrom(stblBox.getChild()); + stcoBox.copyFrom(stblBox.getChild()); + co64Box.copyFrom(stblBox.getChild()); + stscBox.copyFrom(stblBox.getChild()); + stco64 = co64Box.isType("co64"); + hasCTTS = cttsBox.isType("ctts"); } - void mp4TrackHeader::getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, int32_t & timeOffset){ - - + void mp4TrackHeader::getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset){ if (index < sampleIndex){ sampleIndex = 0; stscStart = 0; } - while (stscStart < stscBox.getEntryCount()){ + uint64_t stscCount = stscBox.getEntryCount(); + MP4::STSCEntry stscEntry; + while (stscStart < stscCount){ + stscEntry = stscBox.getSTSCEntry(stscStart); //check where the next index starts - unsigned long long nextSampleIndex; - if (stscStart + 1 < stscBox.getEntryCount()){ - nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscBox.getSTSCEntry(stscStart).firstChunk) * stscBox.getSTSCEntry(stscStart).samplesPerChunk; + uint64_t nextSampleIndex; + if (stscStart + 1 < stscCount){ + nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscEntry.firstChunk) * stscEntry.samplesPerChunk; }else{ nextSampleIndex = stszBox.getSampleCount(); } - //if the next one is too far, we're in the right spot if (nextSampleIndex > index){ break; } @@ -126,19 +85,13 @@ namespace Mist { } if (sampleIndex > index){ - FAIL_MSG("Could not complete seek - not in file (%llu > %lu)", sampleIndex, index); + FAIL_MSG("Could not complete seek - not in file (%" PRIu64 " > %" PRIu64 ")", sampleIndex, index); } - long long unsigned stcoPlace = (stscBox.getSTSCEntry(stscStart).firstChunk - 1 ) + ((index - sampleIndex) / stscBox.getSTSCEntry(stscStart).samplesPerChunk); - long long unsigned stszStart = sampleIndex + (stcoPlace - (stscBox.getSTSCEntry(stscStart).firstChunk - 1)) * stscBox.getSTSCEntry(stscStart).samplesPerChunk; - - //set the offset to the correct value - if (stcoBox.getType() == "co64"){ - offset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoPlace); - }else{ - offset = stcoBox.getChunkOffset(stcoPlace); - } + uint64_t stcoPlace = (stscEntry.firstChunk - 1 ) + ((index - sampleIndex) / stscEntry.samplesPerChunk); + uint64_t stszStart = sampleIndex + (stcoPlace - (stscEntry.firstChunk - 1)) * stscEntry.samplesPerChunk; + offset = (stco64 ? co64Box.getChunkOffset(stcoPlace) : stcoBox.getChunkOffset(stcoPlace)); for (int j = stszStart; j < index; j++){ offset += stszBox.getEntrySize(j); } @@ -150,14 +103,14 @@ namespace Mist { } MP4::STTSEntry tmpSTTS; - while (deltaIndex < sttsBox.getEntryCount()){ + uint64_t sttsCount = sttsBox.getEntryCount(); + while (deltaIndex < sttsCount){ tmpSTTS = sttsBox.getSTTSEntry(deltaIndex); if ((index - deltaPos) < tmpSTTS.sampleCount){ break; - }else{ - deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta; - deltaPos += tmpSTTS.sampleCount; } + deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta; + deltaPos += tmpSTTS.sampleCount; ++deltaIndex; } timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale; @@ -169,20 +122,18 @@ namespace Mist { } if (hasCTTS){ MP4::CTTSEntry tmpCTTS; - while (offsetIndex < cttsBox.getEntryCount()){ + uint32_t cttsCount = cttsBox.getEntryCount(); + while (offsetIndex < cttsCount){ tmpCTTS = cttsBox.getCTTSEntry(offsetIndex); if ((index - offsetPos) < tmpCTTS.sampleCount){ - timeOffset = (tmpCTTS.sampleOffset*1000)/(int32_t)timeScale; + timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale; break; } offsetPos += tmpCTTS.sampleCount; ++offsetIndex; } } - - //next lines are common for next-getting and seeking size = stszBox.getEntrySize(index); - } inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) { @@ -220,6 +171,7 @@ namespace Mist { std::cerr << "File output in player mode not supported" << std::endl; return false; } + streamName = config->getString("streamname"); } //open File @@ -236,324 +188,230 @@ namespace Mist { INFO_MSG("inFile failed!"); return false; } - //make trackmap here from inFile - long long unsigned int trackNo = 0; - std::string tmp;//temp string for reading boxes. - + uint32_t trackNo = 0; //first we get the necessary header parts while(!feof(inFile)){ std::string boxType = MP4::readBoxType(inFile); + if (boxType == "erro"){ + break; + } if (boxType=="moov"){ MP4::MOOV moovBox; moovBox.read(inFile); //for all box in moov - - MP4::Box moovLoopPeek; - for (uint32_t i = 0; i < moovBox.getContentCount(); i++){ - tmp = std::string(moovBox.getContent(i).asBox() ,moovBox.getContent(i).boxedSize()); - moovLoopPeek.read(tmp); - //if trak - if (moovLoopPeek.getType() == "trak"){ - //create new track entry here - trackNo ++; - - headerData[trackNo].read((MP4::TRAK&)moovLoopPeek); - } + + std::deque trak = moovBox.getChildren(); + for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ + headerData[++trackNo].read(*trakIt); } - }else if (boxType == "erro"){ - break; - }else{ - if (!MP4::skipBox(inFile)){//moving on to next box - DEBUG_MSG(DLVL_FAIL,"Error in skipping box, exiting"); - return false; - } - }//fi moov - }//when at the end of the file - //seek file to 0; + continue; + } + if (!MP4::skipBox(inFile)){//moving on to next box + FAIL_MSG("Error in skipping box, exiting"); + return false; + } + } fseeko(inFile,0,SEEK_SET); //See whether a separate header file exists. if (readExistingHeader()){return true;} + HIGH_MSG("Not read existing header"); trackNo = 0; //Create header file from MP4 data while(!feof(inFile)){ std::string boxType = MP4::readBoxType(inFile); + if (boxType=="erro"){ + break; + } if (boxType=="moov"){ MP4::MOOV moovBox; moovBox.read(inFile); - //for all box in moov - MP4::Box moovLoopPeek; - for (uint32_t i = 0; i < moovBox.getContentCount(); i++){ - tmp = std::string(moovBox.getContent(i).asBox(), moovBox.getContent(i).boxedSize()); - moovLoopPeek.read(tmp); - //if trak - if (moovLoopPeek.getType() == "trak"){ - //create new track entry here - long long unsigned int trackNo = myMeta.tracks.size()+1; - myMeta.tracks[trackNo].trackID = trackNo; - MP4::Box trakLoopPeek; - unsigned long int timeScale = 1; - //for all in trak - for (uint32_t j = 0; j < ((MP4::TRAK&)moovLoopPeek).getContentCount(); j++){ - tmp = std::string(((MP4::MOOV&)moovLoopPeek).getContent(j).asBox(),((MP4::MOOV&)moovLoopPeek).getContent(j).boxedSize()); - trakLoopPeek.read(tmp); - std::string trakBoxType = trakLoopPeek.getType(); - //note: per track: trackID codec, type (vid/aud), init - //if tkhd - if (trakBoxType == "tkhd"){ - MP4::TKHD tkhdBox(0,0,0,0);///\todo: this can be done with casting - tmp = std::string(trakLoopPeek.asBox(), trakLoopPeek.boxedSize()); - tkhdBox.read(tmp); - //remember stuff for decoding stuff later - if (tkhdBox.getWidth() > 0){ - myMeta.tracks[trackNo].width = tkhdBox.getWidth(); - myMeta.tracks[trackNo].height = tkhdBox.getHeight(); - } - }else if (trakBoxType == "mdia"){//fi tkhd & if mdia - MP4::Box mdiaLoopPeek; - //for all in mdia - for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){ - tmp = std::string(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),((MP4::MDIA&)trakLoopPeek).getContent(k).boxedSize()); - mdiaLoopPeek.read(tmp); - std::string mdiaBoxType = mdiaLoopPeek.getType(); - if (mdiaBoxType == "mdhd"){ - timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale(); - myMeta.tracks[trackNo].lang = ((MP4::MDHD&)mdiaLoopPeek).getLanguage(); - }else if (mdiaBoxType == "hdlr"){//fi mdhd - std::string handlerType = ((MP4::HDLR&)mdiaLoopPeek).getHandlerType(); - if (handlerType != "vide" && handlerType !="soun" && handlerType != "sbtl"){ - myMeta.tracks.erase(trackNo); - //skip meta boxes for now - break; - } - }else if (mdiaBoxType == "minf"){//fi hdlr - //for all in minf - //get all boxes: stco stsz,stss - MP4::Box minfLoopPeek; - for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){ - tmp = std::string(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),((MP4::MINF&)mdiaLoopPeek).getContent(l).boxedSize()); - minfLoopPeek.read(tmp); - std::string minfBoxType = minfLoopPeek.getType(); - ///\todo more stuff here - if (minfBoxType == "stbl"){ - MP4::Box stblLoopPeek; - MP4::STSS stssBox; - MP4::STTS sttsBox; - MP4::STSZ stszBox; - MP4::STCO stcoBox; - MP4::STSC stscBox; - MP4::CTTS cttsBox;//optional ctts box - bool hasCTTS = false; - for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){ - tmp = std::string(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),((MP4::STBL&)minfLoopPeek).getContent(m).boxedSize()); - std::string stboxRead = tmp; - stblLoopPeek.read(tmp); - std::string stblBoxType = stblLoopPeek.getType(); - if (stblBoxType == "stss"){ - stssBox.read(stboxRead); - }else if (stblBoxType == "stts"){ - sttsBox.read(stboxRead); - }else if (stblBoxType == "stsz"){ - stszBox.read(stboxRead); - }else if (stblBoxType == "stco" || stblBoxType == "co64"){ - stcoBox.read(stboxRead); - }else if (stblBoxType == "stsc"){ - stscBox.read(stboxRead); - }else if (stblBoxType == "ctts"){ - cttsBox.read(stboxRead); - hasCTTS = true; - }else if (stblBoxType == "stsd"){ - //check for codec in here - MP4::Box & tmpBox = ((MP4::STSD&)stblLoopPeek).getEntry(0); - std::string tmpType = tmpBox.getType(); - INFO_MSG("Found track of type %s", tmpType.c_str()); - if (tmpType == "avc1" || tmpType == "h264" || tmpType == "mp4v"){ - myMeta.tracks[trackNo].type = "video"; - myMeta.tracks[trackNo].codec = "H264"; - if (!myMeta.tracks[trackNo].width){ - myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth(); - myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight(); - } - MP4::Box tmpBox2 = tmpBox; - MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP(); - if (tmpContent.getType() == "avcC"){ - myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize()); - } - tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP(); - if (tmpContent.getType() == "avcC"){ - myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize()); - } - ///this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data... - if (!myMeta.tracks[trackNo].width || !myMeta.tracks[trackNo].height || !myMeta.tracks[trackNo].fpks){ - h264::sequenceParameterSet sps; - sps.fromDTSCInit(myMeta.tracks[trackNo].init); - h264::SPSMeta spsChar = sps.getCharacteristics(); - myMeta.tracks[trackNo].width = spsChar.width; - myMeta.tracks[trackNo].height = spsChar.height; - } - }else if (tmpType == "hev1" || tmpType == "hvc1"){ - myMeta.tracks[trackNo].type = "video"; - myMeta.tracks[trackNo].codec = "HEVC"; - if (!myMeta.tracks[trackNo].width){ - myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth(); - myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight(); - } - MP4::Box tmpBox2 = tmpBox; - MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP(); - if (tmpContent.getType() == "hvcC"){ - myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize()); - } - tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP(); - if (tmpContent.getType() == "hvcC"){ - myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize()); - } - }else if (tmpType == "mp4a" || tmpType == "aac " || tmpType == "ac-3"){ - myMeta.tracks[trackNo].type = "audio"; - myMeta.tracks[trackNo].channels = ((MP4::AudioSampleEntry&)tmpBox).getChannelCount(); - myMeta.tracks[trackNo].rate = (long long int)(((MP4::AudioSampleEntry&)tmpBox).getSampleRate()); - if (tmpType == "ac-3"){ - myMeta.tracks[trackNo].codec = "AC3"; - }else{ - MP4::Box esds = ((MP4::AudioSampleEntry&)tmpBox).getCodecBox(); - myMeta.tracks[trackNo].codec = ((MP4::ESDS&)esds).getCodec(); - myMeta.tracks[trackNo].init = ((MP4::ESDS&)esds).getInitData(); - } - myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file; - //get Visual sample entry -> esds -> startcodes - }else if (tmpType == "tx3g"){//plain text subtitles - myMeta.tracks[trackNo].type = "subtitle"; - myMeta.tracks[trackNo].codec = "TTXT"; - }else{ - myMeta.tracks.erase(trackNo); - } - - } - }//rof stbl - uint64_t totaldur = 0;///\todo note: set this to begin time - mp4PartBpos BsetPart; - long long unsigned int entryNo = 0; - long long unsigned int sampleNo = 0; - MP4::STTSEntry tempSTTS; - tempSTTS = sttsBox.getSTTSEntry(entryNo); - long long unsigned int curSTSS = 0; - bool vidTrack = (myMeta.tracks[trackNo].type == "video"); - //change to for over all samples - unsigned int stcoIndex = 0; - unsigned int stscIndex = 0; - unsigned int cttsIndex = 0;//current ctts Index we are reading - unsigned int cttsEntryRead = 0;//current part of ctts we are reading - MP4::CTTSEntry cttsEntry; - - unsigned int fromSTCOinSTSC = 0; - long long unsigned int tempOffset; - bool stcoIs64 = (stcoBox.getType() == "co64"); - if (stcoIs64){ - tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(0); - }else{ - tempOffset = stcoBox.getChunkOffset(0); - } - long long unsigned int nextFirstChunk; - if (stscBox.getEntryCount() > 1){ - nextFirstChunk = stscBox.getSTSCEntry(1).firstChunk - 1; - }else{ - if (stcoIs64){ - nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount(); - }else{ - nextFirstChunk = stcoBox.getEntryCount(); - } - } - for(long long unsigned int sampleIndex = 0; sampleIndex < stszBox.getSampleCount(); sampleIndex ++){ - if (stcoIndex >= nextFirstChunk){//note - stscIndex ++; - if (stscIndex + 1 < stscBox.getEntryCount()){ - nextFirstChunk = stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1; - }else{ - if (stcoIs64){ - nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount(); - }else{ - nextFirstChunk = stcoBox.getEntryCount(); - } - } - } - if (vidTrack && curSTSS < stssBox.getEntryCount() && sampleIndex + 1 == stssBox.getSampleNumber(curSTSS)){ - BsetPart.keyframe = true; - curSTSS ++; - }else{ - BsetPart.keyframe = false; - } - //in bpos set - BsetPart.stcoNr=stcoIndex; - //bpos = chunkoffset[samplenr] in stco - BsetPart.bpos = tempOffset; - fromSTCOinSTSC ++; - if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk - tempOffset += stszBox.getEntrySize(sampleIndex); - }else{ - stcoIndex ++; - fromSTCOinSTSC = 0; - if (stcoIs64){ - tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoIndex); - }else{ - tempOffset = stcoBox.getChunkOffset(stcoIndex); - } - } - //time = totaldur + stts[entry][sample] - BsetPart.time = (totaldur*1000)/timeScale; - totaldur += tempSTTS.sampleDelta; - sampleNo++; - if (sampleNo >= tempSTTS.sampleCount){ - entryNo++; - sampleNo = 0; - if (entryNo < sttsBox.getEntryCount()){ - tempSTTS = sttsBox.getSTTSEntry(entryNo); - } - } - //set time offset - if (hasCTTS){ - - cttsEntry = cttsBox.getCTTSEntry(cttsIndex); - cttsEntryRead++; - if (cttsEntryRead >= cttsEntry.sampleCount){ - cttsIndex++; - cttsEntryRead = 0; - } - BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000)/(int32_t)timeScale; - }else{ - BsetPart.timeOffset = 0; - } - myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(sampleIndex), BsetPart.bpos, BsetPart.keyframe); - }//while over stsc - if (vidTrack){ - //something wrong with the time formula, but the answer is right for some reason - /// \todo Fix this. This makes no sense whatsoever. This isn't frame per kilosecond, but milli-STCO-entries per second. - // (A single STCO entry may be more than 1 part, and 1 part may be a partial frame or multiple frames) - if (stcoIs64){ - myMeta.tracks[trackNo].fpks = (((double)(((MP4::CO64*)&stcoBox)->getEntryCount()*1000))/((totaldur*1000)))*1000; - }else{ - myMeta.tracks[trackNo].fpks = (((double)(stcoBox.getEntryCount()*1000))/((totaldur*1000)))*1000; - } - } - }//fi stbl - }//rof minf - }//fi minf - }//rof mdia - }//fi mdia - }//rof trak - }//endif trak - }//rof moov - }else if (boxType == "erro"){ - break; - }else{ - if (!MP4::skipBox(inFile)){//moving on to next box - DEBUG_MSG(DLVL_FAIL,"Error in Skipping box, exiting"); - return false; + + std::deque trak = moovBox.getChildren(); + HIGH_MSG("Obtained %zu trak Boxes", trak.size()); + + for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ + uint64_t trackNo = myMeta.tracks.size()+1; + myMeta.tracks[trackNo].trackID = trackNo; + + MP4::TKHD tkhdBox = trakIt->getChild(); + if (tkhdBox.getWidth() > 0){ + myMeta.tracks[trackNo].width = tkhdBox.getWidth(); + myMeta.tracks[trackNo].height = tkhdBox.getHeight(); + } + + MP4::MDIA mdiaBox = trakIt->getChild(); + + MP4::MDHD mdhdBox = mdiaBox.getChild(); + uint64_t timescale = mdhdBox.getTimeScale(); + myMeta.tracks[trackNo].lang = mdhdBox.getLanguage(); + + std::string hdlrType = mdiaBox.getChild().getHandlerType(); + if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "stbl"){ + break; + } + + MP4::STBL stblBox = mdiaBox.getChild().getChild(); + + MP4::STSD stsdBox = stblBox.getChild(); + MP4::Box sEntryBox = stsdBox.getEntry(0); + std::string sType = sEntryBox.getType(); + HIGH_MSG("Found track %zu of type %s", trackNo, sType.c_str()); + + if (sType == "avc1" || sType == "h264" || sType == "mp4v"){ + MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox; + myMeta.tracks[trackNo].type = "video"; + myMeta.tracks[trackNo].codec = "H264"; + if (!myMeta.tracks[trackNo].width){ + myMeta.tracks[trackNo].width = vEntryBox.getWidth(); + myMeta.tracks[trackNo].height = vEntryBox.getHeight(); + } + MP4::Box initBox = vEntryBox.getCLAP(); + if (initBox.isType("avcC")){ + myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + } + initBox = vEntryBox.getPASP(); + if (initBox.isType("avcC")){ + myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + } + ///this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data... + if (!myMeta.tracks[trackNo].width){ + h264::sequenceParameterSet sps; + sps.fromDTSCInit(myMeta.tracks[trackNo].init); + h264::SPSMeta spsChar = sps.getCharacteristics(); + myMeta.tracks[trackNo].width = spsChar.width; + myMeta.tracks[trackNo].height = spsChar.height; + } + } + if (sType == "hev1" || sType == "hvc1"){ + MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox; + myMeta.tracks[trackNo].type = "video"; + myMeta.tracks[trackNo].codec = "HEVC"; + if (!myMeta.tracks[trackNo].width){ + myMeta.tracks[trackNo].width = vEntryBox.getWidth(); + myMeta.tracks[trackNo].height = vEntryBox.getHeight(); + } + MP4::Box initBox = vEntryBox.getCLAP(); + if (initBox.isType("hvcC")){ + myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + } + initBox = vEntryBox.getPASP(); + if (initBox.isType("hvcC")){ + myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); + } + } + if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){ + MP4::AudioSampleEntry & aEntryBox = (MP4::AudioSampleEntry&)sEntryBox; + myMeta.tracks[trackNo].type = "audio"; + myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount(); + myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate(); + + if (sType == "ac-3"){ + myMeta.tracks[trackNo].codec = "AC3"; + }else{ + MP4::ESDS esdsBox = (MP4::ESDS&)(aEntryBox.getCodecBox()); + myMeta.tracks[trackNo].codec = esdsBox.getCodec(); + myMeta.tracks[trackNo].init = esdsBox.getInitData(); + } + myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file; + } + + if (sType == "tx3g"){//plain text subtitles + myMeta.tracks[trackNo].type = "subtitle"; + myMeta.tracks[trackNo].codec = "TTXT"; + } + + MP4::STSS stssBox = stblBox.getChild(); + MP4::STTS sttsBox = stblBox.getChild(); + MP4::STSZ stszBox = stblBox.getChild(); + MP4::STCO stcoBox = stblBox.getChild(); + MP4::CO64 co64Box = stblBox.getChild(); + MP4::STSC stscBox = stblBox.getChild(); + MP4::CTTS cttsBox = stblBox.getChild();//optional ctts box + + bool stco64 = co64Box.isType("co64"); + bool hasCTTS = cttsBox.isType("ctts"); + + uint64_t totaldur = 0;///\todo note: set this to begin time + mp4PartBpos BsetPart; + + uint64_t entryNo = 0; + uint64_t sampleNo = 0; + + uint64_t stssIndex = 0; + uint64_t stcoIndex = 0; + uint64_t stscIndex = 0; + uint64_t cttsIndex = 0;//current ctts Index we are reading + uint64_t cttsEntryRead = 0;//current part of ctts we are reading + + uint64_t stssCount = stssBox.getEntryCount(); + uint64_t stscCount = stscBox.getEntryCount(); + uint64_t stszCount = stszBox.getSampleCount(); + uint64_t stcoCount = (stco64 ? co64Box.getEntryCount() : stcoBox.getEntryCount()); + + MP4::STTSEntry sttsEntry = sttsBox.getSTTSEntry(0); + + uint32_t fromSTCOinSTSC = 0; + uint64_t tmpOffset = (stco64 ? co64Box.getChunkOffset(0) : stcoBox.getChunkOffset(0)); + + uint64_t nextFirstChunk = (stscCount > 1 ? stscBox.getSTSCEntry(1).firstChunk - 1 : stcoCount); + + for (uint64_t stszIndex = 0; stszIndex < stszCount; ++stszIndex){ + if (stcoIndex >= nextFirstChunk){ + ++stscIndex; + nextFirstChunk = (stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount); + } + BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount && stszIndex + 1 == stssBox.getSampleNumber(stssIndex)); + if (BsetPart.keyframe){ + ++stssIndex; + } + //in bpos set + BsetPart.stcoNr = stcoIndex; + //bpos = chunkoffset[samplenr] in stco + BsetPart.bpos = tmpOffset; + ++fromSTCOinSTSC; + if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk + tmpOffset += stszBox.getEntrySize(stszIndex); + }else{ + ++stcoIndex; + fromSTCOinSTSC = 0; + tmpOffset = (stco64 ? co64Box.getChunkOffset(stcoIndex) : stcoBox.getChunkOffset(stcoIndex)); + } + BsetPart.time = (totaldur*1000)/timescale; + totaldur += sttsEntry.sampleDelta; + sampleNo++; + if (sampleNo >= sttsEntry.sampleCount){ + ++entryNo; + sampleNo = 0; + if (entryNo < sttsBox.getEntryCount()){ + sttsEntry = sttsBox.getSTTSEntry(entryNo); + } + } + + if (hasCTTS){ + MP4::CTTSEntry cttsEntry = cttsBox.getCTTSEntry(cttsIndex); + cttsEntryRead++; + if (cttsEntryRead >= cttsEntry.sampleCount){ + ++cttsIndex; + cttsEntryRead = 0; + } + BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000)/timescale; + }else{ + BsetPart.timeOffset = 0; + } + myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe); + } } - }// if moov - }// end while file read - //for all in bpos set, find its data + continue; + } + if (!MP4::skipBox(inFile)){//moving on to next box + FAIL_MSG("Error in Skipping box, exiting"); + return false; + } + } clearerr(inFile); //outputting dtsh file @@ -582,7 +440,7 @@ namespace Mist { } } if (fseeko(inFile,curPart.bpos,SEEK_SET)){ - DEBUG_MSG(DLVL_FAIL,"seek unsuccessful; bpos: %llu error: %s",curPart.bpos, strerror(errno)); + FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s",curPart.bpos, strerror(errno)); thisPacket.null(); return; } @@ -591,7 +449,7 @@ namespace Mist { malSize = curPart.size; } if (fread(data, curPart.size, 1, inFile)!=1){ - DEBUG_MSG(DLVL_FAIL,"read unsuccessful at %ld", ftell(inFile)); + FAIL_MSG("read unsuccessful at %" PRIu64, ftell(inFile)); thisPacket.null(); return; } diff --git a/src/input/input_mp4.h b/src/input/input_mp4.h index 6a8a3385..02e0aef6 100644 --- a/src/input/input_mp4.h +++ b/src/input/input_mp4.h @@ -9,49 +9,41 @@ namespace Mist { bool operator < (const mp4PartTime & rhs) const { if (time < rhs.time){ return true; - }else{ - if (time == rhs.time){ - if (trackID < rhs.trackID){ - return true; - }else{ - if (trackID == rhs.trackID && bpos < rhs.bpos){ - return true; - } - } - } } - return false; + if (time > rhs.time){ + return false; + } + if (trackID < rhs.trackID){ + return true; + } + return (trackID == rhs.trackID && bpos < rhs.bpos); } - long long unsigned int time; + uint64_t time; int32_t offset; - unsigned int trackID; - long long unsigned int bpos; - unsigned int size; - long unsigned int index; + size_t trackID; + uint64_t bpos; + uint32_t size; + uint64_t index; }; struct mp4PartBpos{ bool operator < (const mp4PartBpos & rhs) const { if (time < rhs.time){ return true; - }else{ - if (time == rhs.time){ - if (trackID < rhs.trackID){ - return true; - }else{ - if (trackID == rhs.trackID && bpos < rhs.bpos){ - return true; - } - } - } } - return false; + if (time > rhs.time){ + return false; + } + if (trackID < rhs.trackID){ + return true; + } + return (trackID == rhs.trackID && bpos < rhs.bpos); } - long long unsigned int time; - long long unsigned int trackID; - long long unsigned int bpos; - long long unsigned int size; - long long unsigned int stcoNr; + uint64_t time; + size_t trackID; + uint64_t bpos; + uint64_t size; + uint64_t stcoNr; int32_t timeOffset; bool keyframe; }; @@ -61,26 +53,29 @@ namespace Mist { mp4TrackHeader(); void read(MP4::TRAK & trakBox); MP4::STCO stcoBox; + MP4::CO64 co64Box; MP4::STSZ stszBox; MP4::STTS sttsBox; bool hasCTTS; MP4::CTTS cttsBox; MP4::STSC stscBox; - long unsigned int timeScale; - void getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, int32_t & timeOffset); - long unsigned int size(); + uint64_t timeScale; + void getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset); + uint64_t size(); private: bool initialised; //next variables are needed for the stsc/stco loop - long long unsigned int stscStart; - long long unsigned int sampleIndex; + uint64_t stscStart; + uint64_t sampleIndex; //next variables are needed for the stts loop - long long unsigned deltaIndex;///< Index in STTS box - long long unsigned deltaPos;///< Sample counter for STTS box - long long unsigned deltaTotal;///< Total timestamp for STTS box + uint64_t deltaIndex;///< Index in STTS box + uint64_t deltaPos;///< Sample counter for STTS box + uint64_t deltaTotal;///< Total timestamp for STTS box //for CTTS box loop - long long unsigned offsetIndex;///< Index in CTTS box - long long unsigned offsetPos;///< Sample counter for CTTS box + uint64_t offsetIndex;///< Index in CTTS box + uint64_t offsetPos;///< Sample counter for CTTS box + + bool stco64; }; class inputMP4 : public Input { @@ -104,7 +99,7 @@ namespace Mist { std::map nextKeyframe; //these next two variables keep a buffer for reading from filepointer inFile; - unsigned long long int malSize; + uint64_t malSize; char* data;///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files }; } diff --git a/src/output/output_progressive_mp4.cpp b/src/output/output_progressive_mp4.cpp index bc953fa1..97feec5c 100644 --- a/src/output/output_progressive_mp4.cpp +++ b/src/output/output_progressive_mp4.cpp @@ -5,6 +5,8 @@ #include #include "output_progressive_mp4.h" +#include + namespace Mist { OutProgressiveMP4::OutProgressiveMP4(Socket::Connection & conn) : HTTPOutput(conn) {} OutProgressiveMP4::~OutProgressiveMP4() {} @@ -29,8 +31,8 @@ namespace Mist { //capa["canRecord"].append("m3u"); } - long long unsigned OutProgressiveMP4::estimateFileSize() { - long long unsigned retVal = 0; + uint64_t OutProgressiveMP4::estimateFileSize() { + uint64_t retVal = 0; for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { for (std::deque::iterator keyIt = myMeta.tracks[*it].keySizes.begin(); keyIt != myMeta.tracks[*it].keySizes.end(); keyIt++) { retVal += *keyIt; @@ -39,8 +41,96 @@ namespace Mist { return retVal * 1.1; } + uint64_t OutProgressiveMP4::mp4HeaderSize(uint64_t & fileSize, int fragmented) { + bool useLargeBoxes = !fragmented && (estimateFileSize() > 0xFFFFFFFFull); + uint64_t res = 36 // FTYP Box + + 8 //MOOV box + + 108; //MVHD Box + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + DTSC::Track & thisTrack = myMeta.tracks[*it]; + uint64_t tmpRes = 0; + uint64_t partCount = thisTrack.parts.size(); + + tmpRes += 8 //TRAK Box + + 92 //TKHD Box + + 36 //EDTS Box + + 8 //MDIA Box + + 32 //MDHD Box + + 33 + thisTrack.getIdentifier().size() // HDLR Box + + 8 //MINF Box + + 36 //DINF Box + + 8; // STBL Box + + //These boxes are empty when generating fragmented output + tmpRes += 20 + (fragmented ? 0 : (partCount * 4));//STSZ + tmpRes += 16 + (fragmented ? 0 : (partCount * (useLargeBoxes ? 8 : 4)));//STCO + tmpRes += 16 + (fragmented ? 0 : (1 * 12));//STSC <-- Currently 1 entry, but might become more complex in near future + + //Type-specific boxes + if (thisTrack.type == "video"){ + tmpRes += 20//VMHD Box + + 16 //STSD + + 86 //AVC1 + + 8 + thisTrack.init.size();//avcC + if (!fragmented){ + tmpRes += 16 + (thisTrack.keys.size() * 4);//STSS + } + } + if (thisTrack.type == "audio"){ + tmpRes += 16//SMHD Box + + 16//STSD + + 36//MP4A + + 37 + thisTrack.init.size();//ESDS + } + + if (!fragmented){ + //Unfortunately, for our STTS and CTTS boxes, we need to loop through all parts of the track + uint64_t sttsCount = 1; + uint64_t prevDur = thisTrack.parts[0].getDuration(); + uint64_t prevOffset = thisTrack.parts[0].getOffset(); + uint64_t cttsCount = 1; + fileSize += thisTrack.parts[0].getSize(); + for (unsigned int part = 1; part < partCount; ++part){ + uint64_t partDur = thisTrack.parts[part].getDuration(); + uint64_t partOffset = thisTrack.parts[part].getOffset(); + uint64_t partSize = thisTrack.parts[part].getSize(); + if (prevDur != partDur){ + prevDur = partDur; + ++sttsCount; + } + if (partOffset != prevOffset){ + prevOffset = partOffset; + ++cttsCount; + } + fileSize += partSize; + } + if (cttsCount == 1 && ! prevOffset){ + cttsCount = 0; + } + tmpRes += 16 + (sttsCount * 8);//STTS + if (cttsCount){ + tmpRes += 16 + (cttsCount * 8);//CTTS + } + } else{ + tmpRes += 16;//empty STTS, no CTTS + } + + res += tmpRes; + } + if (fragmented){ + res += 8 + (selectedTracks.size() * 32);//Mvex + trex boxes + res += 1; //Horrible horrible length fix; + }else{ + res += 8; //mdat beginning + } + fileSize += res; + INFO_MSG("H size %llu, file: %llu", res, fileSize); + return res; + } + + ///\todo This function does not indicate errors anywhere... maybe fix this... - std::string OutProgressiveMP4::DTSCMeta2MP4Header(long long & size, int fragmented) { + std::string OutProgressiveMP4::DTSCMeta2MP4Header(uint64_t & size, int fragmented) { if (myMeta.live){ needsLookAhead = 420; } @@ -75,11 +165,11 @@ namespace Mist { //Then override it only when we are not sending a fragmented file if (!fragmented){ //calculating longest duration - long long unsigned firstms = 0xFFFFFFFFFFFFFFull; - long long unsigned lastms = 0; + uint64_t firstms = 0xFFFFFFFFFFFFFFull; + uint64_t 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); + lastms = std::max(lastms, (uint64_t)myMeta.tracks[*it].lastms); + firstms = std::min(firstms, (uint64_t)myMeta.tracks[*it].firstms); } mvhdBox.setDuration(lastms - firstms); } @@ -89,6 +179,8 @@ namespace Mist { for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { DTSC::Track & thisTrack = myMeta.tracks[*it]; + size_t partCount = thisTrack.parts.size(); + uint64_t tDuration = thisTrack.lastms - thisTrack.firstms; MP4::TRAK trakBox; //Keep track of the current index within the moovBox unsigned int trakOffset = 0; @@ -106,11 +198,7 @@ namespace Mist { elstBox.setVersion(0); elstBox.setFlags(0); elstBox.setCount(1); - if (!fragmented){ - elstBox.setSegmentDuration(0, thisTrack.lastms - thisTrack.firstms); - }else{ - elstBox.setSegmentDuration(0, -1); - } + elstBox.setSegmentDuration(0, fragmented ? -1 : tDuration); elstBox.setMediaTime(0, 0); elstBox.setMediaRateInteger(0, 1); elstBox.setMediaRateFraction(0, 0); @@ -118,10 +206,10 @@ namespace Mist { trakBox.setContent(edtsBox, trakOffset++); MP4::MDIA mdiaBox; - unsigned int mdiaOffset = 0; + size_t mdiaOffset = 0; //Add the mandatory MDHD and HDLR boxes to the MDIA - MP4::MDHD mdhdBox(thisTrack.lastms - thisTrack.firstms); + MP4::MDHD mdhdBox(tDuration); if (fragmented){ mdhdBox.setDuration(-1); } @@ -131,7 +219,7 @@ namespace Mist { mdiaBox.setContent(hdlrBox, mdiaOffset++); MP4::MINF minfBox; - unsigned int minfOffset = 0; + size_t minfOffset = 0; //Add a track-type specific box to the MINF box if (thisTrack.type == "video") { @@ -151,7 +239,7 @@ namespace Mist { MP4::STBL stblBox; - unsigned int stblOffset = 0; + size_t stblOffset = 0; //Add STSD box MP4::STSD stsdBox(0); @@ -164,30 +252,68 @@ namespace Mist { } stblBox.setContent(stsdBox, stblOffset++); + //Add STTS Box //note: STTS is empty when fragmented MP4::STTS sttsBox(0); + //Add STSZ Box + //note: STSZ is empty when fragmented + MP4::STSZ stszBox(0); if (!fragmented) { - 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())); - } - //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 + MP4::CTTS cttsBox; + cttsBox.setVersion(0); + + + MP4::CTTSEntry tmpEntry; + tmpEntry.sampleCount = 0; + tmpEntry.sampleOffset = thisTrack.parts[0].getOffset(); + + std::deque > sttsCounter; + stszBox.setEntrySize(0, partCount - 1);//Speed up allocation + size_t totalEntries = 0; + + for (size_t part = 0; part < partCount; ++part){ + stats(); + + uint64_t partDur = thisTrack.parts[part].getDuration(); + uint64_t partSize = thisTrack.parts[part].getSize(); + uint64_t partOffset = thisTrack.parts[part].getOffset(); + + //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 != partDur){ + sttsCounter.push_back(std::pair(0, partDur)); + } + //Update the counter + sttsCounter.rbegin()->first++; + + stszBox.setEntrySize(partSize, part); + size += partSize; + + if (partOffset != 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 = partOffset; + } + tmpEntry.sampleCount++; + } + + MP4::STTSEntry sttsEntry; + sttsBox.setSTTSEntry(sttsEntry, sttsCounter.size() - 1); + size_t sttsIdx = 0; + for (std::deque >::iterator it2 = sttsCounter.begin(); it2 != sttsCounter.end(); it2++){ + sttsEntry.sampleCount = it2->first; + sttsEntry.sampleDelta = it2->second; + sttsBox.setSTTSEntry(sttsEntry, sttsIdx++); + } + if (totalEntries || tmpEntry.sampleOffset) { + cttsBox.setCTTSEntry(tmpEntry, totalEntries++); + stblBox.setContent(cttsBox, stblOffset++); } } stblBox.setContent(sttsBox, stblOffset++); + stblBox.setContent(stszBox, stblOffset++); //Add STSS Box IF type is video and we are not fragmented if (thisTrack.type == "video" && !fragmented) { @@ -209,63 +335,20 @@ namespace Mist { } stblBox.setContent(stscBox, stblOffset++); - bool containsOffsets = false; - //Add STSZ Box - //note: STSZ is empty when fragmented - MP4::STSZ stszBox(0); - if (!fragmented) { - 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 - //note: CTTS will never exist in fragmented, since containsOffsets is set while generating the STSZ box - 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: 64bit boxes will never be used in fragmented //note: Inserting empty values on purpose here, will be fixed later. if (useLargeBoxes) { MP4::CO64 CO64Box; - CO64Box.setChunkOffset(0, thisTrack.parts.size() - 1); + CO64Box.setChunkOffset(0, partCount - 1); stblBox.setContent(CO64Box, stblOffset++); } else { MP4::STCO stcoBox(0); if (fragmented) { stcoBox.setEntryCount(0); } else { - stcoBox.setChunkOffset(0, thisTrack.parts.size() - 1); + stcoBox.setChunkOffset(0, partCount - 1); } stblBox.setContent(stcoBox, stblOffset++); } @@ -289,95 +372,62 @@ namespace Mist { moovBox.setContent(mvexBox, moovOffset++); }else{ //if we are making a non fragmented MP4 and there are parts //initial offset length ftyp, length moov + 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; - //for all tracks - for (unsigned int i = 1; i < moovBox.getContentCount(); i++) { - //10 lines to get the STCO box. - MP4::TRAK checkTrakBox; - MP4::Box checkMdiaBox; - MP4::Box checkTkhdBox; - MP4::MINF checkMinfBox; - MP4::STBL checkStblBox; - //MP4::STCO checkStcoBox; - checkTrakBox = ((MP4::TRAK &)moovBox.getContent(i)); - for (unsigned int j = 0; j < checkTrakBox.getContentCount(); j++) { - if (checkTrakBox.getContent(j).isType("mdia")) { - checkMdiaBox = checkTrakBox.getContent(j); - break; - } - if (checkTrakBox.getContent(j).isType("tkhd")) { - checkTkhdBox = checkTrakBox.getContent(j); - } - } - for (unsigned int j = 0; j < ((MP4::MDIA &)checkMdiaBox).getContentCount(); j++) { - if (((MP4::MDIA &)checkMdiaBox).getContent(j).isType("minf")) { - checkMinfBox = ((MP4::MINF &)((MP4::MDIA &)checkMdiaBox).getContent(j)); - break; - } - } - for (unsigned int j = 0; j < checkMinfBox.getContentCount(); j++) { - if (checkMinfBox.getContent(j).isType("stbl")) { - checkStblBox = ((MP4::STBL &)checkMinfBox.getContent(j)); - break; - } - } - for (unsigned int j = 0; j < checkStblBox.getContentCount(); j++) { - if (checkStblBox.getContent(j).isType("stco")) { - checkStcoBoxes.insert(std::pair(((MP4::TKHD &)checkTkhdBox).getTrackID(), ((MP4::STCO &)checkStblBox.getContent(j)))); - break; - } - if (checkStblBox.getContent(j).isType("co64")) { - checkCO64Boxes.insert(std::pair(((MP4::TKHD &)checkTkhdBox).getTrackID(), ((MP4::CO64 &)checkStblBox.getContent(j)))); - break; - } + uint64_t dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8; + + + std::map checkStcoBoxes; + std::map checkCO64Boxes; + + std::deque trak = moovBox.getChildren(); + for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ + MP4::TKHD tkhdBox = trakIt->getChild(); + MP4::STBL stblBox = trakIt->getChild().getChild().getChild(); + if (useLargeBoxes){ + checkCO64Boxes.insert(std::pair(tkhdBox.getTrackID(), stblBox.getChild())); + }else{ + checkStcoBoxes.insert(std::pair(tkhdBox.getTrackID(), stblBox.getChild())); } } + //inserting right values in the STCO box header //total = 0; //Keep track of the current size of the data within the mdat - long long unsigned int dataSize = 0; + uint64_t 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++) { keyPart temp; temp.trackID = *subIt; temp.time = myMeta.tracks[*subIt].firstms;//timeplace of frame - 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); + INFO_MSG("adding to sortSet: tid %lu time %lu", temp.trackID, temp.time); sortSet.insert(temp); } while (!sortSet.empty()) { - std::set::iterator keyBegin = sortSet.begin(); + stats(); + keyPart temp = *sortSet.begin(); + sortSet.erase(sortSet.begin()); + + DTSC::Track & thisTrack = myMeta.tracks[temp.trackID]; + //setting the right STCO size in the STCO box if (useLargeBoxes){//Re-using the previously defined boolean for speedup - checkCO64Boxes[keyBegin->trackID].setChunkOffset(dataOffset + dataSize, keyBegin->index); + checkCO64Boxes[temp.trackID].setChunkOffset(dataOffset + dataSize, temp.index); } else { - checkStcoBoxes[keyBegin->trackID].setChunkOffset(dataOffset + dataSize, keyBegin->index); + checkStcoBoxes[temp.trackID].setChunkOffset(dataOffset + dataSize, temp.index); } - dataSize += keyBegin->size; + dataSize += thisTrack.parts[temp.index].getSize(); //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 + if (temp.index + 1< thisTrack.parts.size()) {//Only create new element, when there are new elements to be added + temp.time += thisTrack.parts[temp.index].getDuration(); + ++temp.index; sortSet.insert(temp); } - //remove highest keyPart - sortSet.erase(keyBegin); } ///\todo Update this thing for boxes >4G? mdatSize = dataSize + 8;//+8 for mp4 header - } header << std::string(moovBox.asBox(), moovBox.boxedSize()); if (!fragmented) { //if we are making a non fragmented MP4 and there are parts @@ -392,6 +442,7 @@ namespace Mist { header << (char)(0); } size += header.str().size(); + INFO_MSG("Header %llu, file: %llu", header.str().size(), size); if (fragmented) { realBaseOffset = header.str().size(); } @@ -400,7 +451,7 @@ namespace Mist { /// Calculate a seekPoint, based on byteStart, metadata, tracks and headerSize. /// The seekPoint will be set to the timestamp of the first packet to send. - void OutProgressiveMP4::findSeekPoint(long long byteStart, long long & seekPoint, unsigned int headerSize) { + void OutProgressiveMP4::findSeekPoint(uint64_t byteStart, uint64_t & seekPoint, uint64_t headerSize) { seekPoint = 0; //if we're starting in the header, seekPoint is always zero. if (byteStart <= headerSize) { @@ -410,28 +461,33 @@ namespace Mist { byteStart -= headerSize; //forward through the file by headers, until we reach the point where we need to be while (!sortSet.empty()) { + //find the next part and erase it + keyPart temp = *sortSet.begin(); + + DTSC::Track & thisTrack = myMeta.tracks[temp.trackID]; + uint64_t partSize = thisTrack.parts[temp.index].getSize(); + //record where we are - seekPoint = sortSet.begin()->time; + seekPoint = temp.time; //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) { - INFO_MSG("We're starting at time %lld, skipping %lld bytes", seekPoint, byteStart+sortSet.begin()->size); + if (partSize > byteStart) { + INFO_MSG("We're starting at time %" PRIu64 ", skipping %" PRIu64 " bytes", seekPoint, partSize - byteStart); return; } + + + byteStart -= partSize; + //otherwise, set currPos to where we are now and continue - currPos += sortSet.begin()->size; - //find the next part - keyPart temp; - temp.index = sortSet.begin()->index + 1; - temp.trackID = sortSet.begin()->trackID; - if (temp.index < myMeta.tracks[temp.trackID].parts.size()) { //only insert when there are parts left - temp.time = sortSet.begin()->endTime;//timeplace of frame - temp.endTime = sortSet.begin()->endTime + myMeta.tracks[temp.trackID].parts[temp.index].getDuration(); - temp.size = myMeta.tracks[temp.trackID].parts[temp.index].getSize();//bytesize of frame + currPos += partSize; + + if (temp.index + 1 < myMeta.tracks[temp.trackID].parts.size()){ //only insert when there are parts left + temp.time += thisTrack.parts[temp.index].getDuration(); + ++temp.index; sortSet.insert(temp); } - //remove highest keyPart + //Remove just-parsed element sortSet.erase(sortSet.begin()); //wash, rinse, repeat } @@ -442,10 +498,10 @@ namespace Mist { /// Parses a "Range: " header, setting byteStart, byteEnd and seekPoint using data from metadata and tracks to do /// the calculations. /// On error, byteEnd is set to zero. - void OutProgressiveMP4::parseRange(std::string header, long long & byteStart, long long & byteEnd, long long & seekPoint, unsigned int headerSize) { + void OutProgressiveMP4::parseRange(std::string header, uint64_t & byteStart, uint64_t & byteEnd, uint64_t & seekPoint, uint64_t headerSize) { if (header.size() < 6 || header.substr(0, 6) != "bytes=") { byteEnd = 0; - DEBUG_MSG(DLVL_WARN, "Invalid range header: %s", header.c_str()); + WARN_MSG("Invalid range header: %s", header.c_str()); return; } header.erase(0, 6); @@ -485,7 +541,7 @@ namespace Mist { break; } if (header[i] != '-') { - DEBUG_MSG(DLVL_WARN, "Invalid range header: %s", header.c_str()); + WARN_MSG("Invalid range header: %s", header.c_str()); byteEnd = 0; return; } @@ -505,14 +561,13 @@ namespace Mist { } else { byteEnd = size; } - DEBUG_MSG(DLVL_MEDIUM, "Range request: %lli-%lli (%s)", byteStart, byteEnd, header.c_str()); + MEDIUM_MSG("Range request: %" PRIu64 "-%" PRIu64 " (%s)", byteStart, byteEnd, header.c_str()); findSeekPoint(byteStart, seekPoint, headerSize); return; } } void OutProgressiveMP4::sendFragmentHeader() { - long unsigned int dataOffset = 0; uint64_t mdatSize = 8; MP4::MOOF moofBox; MP4::MFHD mfhdBox; @@ -530,24 +585,23 @@ namespace Mist { for (uint32_t i = it->second.firstPart; i <= it->second.lastPart; i++) { keyPart temp; temp.trackID = it->first; - temp.size = thisTrack.parts[i].getSize(); - temp.duration = thisTrack.parts[i].getDuration(); temp.time = timeStamp; - timeStamp += temp.duration; - temp.endTime = timeStamp; - temp.timeOffset = thisTrack.parts[i].getOffset();//this will be changed soon, so now it is used for B-frame offset + temp.index = i; + timeStamp += thisTrack.parts[temp.index].getDuration(); trunOrder.insert(temp); } } //now all the parts have been sorted, we make a relative ByteOffset - long unsigned int relativeOffset = 0; + uint64_t relativeOffset = 0; for (std::set::iterator it = trunOrder.begin(); it != trunOrder.end(); it++) { + DTSC::Track & thisTrack = myMeta.tracks[it->trackID]; + uint64_t partSize = thisTrack.parts[it->index].getSize(); //We have to make a copy, because altering the element inside the set would invalidate the iterators keyPart temp = *it; temp.byteOffset = relativeOffset; - relativeOffset += it->size; - DONTEVEN_MSG("Anticipating tid: %lu size: %lu", it->trackID, it->size); + relativeOffset += partSize; + DONTEVEN_MSG("Anticipating tid: %lu size: %lu", it->trackID, partSize); sortSet.insert(temp); } trunOrder.clear();//erase the trunOrder set, to keep memory usage down @@ -591,20 +645,24 @@ namespace Mist { unsigned int trafOffset = 1; for (std::set::iterator trunIt = sortSet.begin(); trunIt != sortSet.end(); trunIt++) { if (trunIt->trackID == tid) { + uint64_t partOffset = thisTrack.parts[trunIt->index].getOffset(); + uint64_t partSize = thisTrack.parts[trunIt->index].getSize(); + uint64_t partDur = thisTrack.parts[trunIt->index].getDuration(); + MP4::TRUN trunBox; - trunBox.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleSize | MP4::trunsampleDuration | (trunIt->timeOffset ? MP4::trunsampleOffsets : 0)); + trunBox.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleSize | MP4::trunsampleDuration | (partOffset ? MP4::trunsampleOffsets : 0)); //The value set here, will be updated afterwards to the correct value trunBox.setDataOffset(trunIt->byteOffset); trunBox.setFirstSampleFlags(MP4::isIPicture | (firstSample ? MP4::isKeySample : MP4::noKeySample)); firstSample = false; - mdatSize += trunIt->size; + mdatSize += partSize; MP4::trunSampleInformation sampleInfo; - sampleInfo.sampleSize = trunIt->size; - sampleInfo.sampleDuration = trunIt->duration; - if (trunIt->timeOffset) { - sampleInfo.sampleOffset = trunIt->timeOffset; + sampleInfo.sampleSize = partSize; + sampleInfo.sampleDuration = partDur; + if (partOffset){ + sampleInfo.sampleOffset = partOffset; } trunBox.setSampleInformation(sampleInfo, 0); trafBox.setContent(trunBox, trafOffset++); @@ -706,9 +764,8 @@ namespace Mist { //Check if the url contains .3gp --> if yes, we will send a 3gp header sending3GP = (H.url.find(".3gp") != std::string::npos); - //For storing the header. - ///\todo Do we really need this though? - std::string headerData = DTSCMeta2MP4Header(fileSize, myMeta.live); + fileSize = 0; + uint64_t headerSize = mp4HeaderSize(fileSize, myMeta.live); seekPoint = 0; if (myMeta.live) { @@ -726,21 +783,19 @@ namespace Mist { keyPart temp; temp.trackID = *subIt; temp.time = myMeta.tracks[*subIt].firstms;//timeplace of frame - 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; sortSet.insert(temp); } if (!myMeta.live) { if (H.GetHeader("Range") != "") { - parseRange(H.GetHeader("Range"), byteStart, byteEnd, seekPoint, headerData.size()); + parseRange(H.GetHeader("Range"), byteStart, byteEnd, seekPoint, headerSize); rangeType = H.GetHeader("Range")[0]; } } H.Clean(); //make sure no parts of old requests are left in any buffers H.setCORSHeaders(); H.SetHeader("Content-Type", "video/MP4"); //Send the correct content-type for MP4 files - if (!myMeta.live) { + if (myMeta.vod) { H.SetHeader("Accept-Ranges", "bytes, parsec"); } if (rangeType != ' ') { @@ -764,7 +819,7 @@ namespace Mist { //H.StartResponse("206", "Partial content", HTTP_R, conn); } } else { - if (!myMeta.live) { + if (myMeta.vod) { H.SetHeader("Content-Length", byteEnd - byteStart + 1); } /// \todo Switch to chunked? @@ -772,12 +827,12 @@ namespace Mist { //HTTP_S.StartResponse(HTTP_R, conn); } 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? - 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; + if (byteStart < headerSize) { + std::string headerData = DTSCMeta2MP4Header(fileSize, myMeta.live); + myConn.SendNow(headerData.data() + byteStart, std::min(headerSize, byteEnd) - byteStart); //send MP4 header + leftOver -= std::min(headerSize, byteEnd) - byteStart; } - currPos += headerData.size();//we're now guaranteed to be past the header point, no matter what + currPos += headerSize;//we're now guaranteed to be past the header point, no matter what } ///Builds up a datastructure that allows for access in the fragment send header function @@ -785,13 +840,14 @@ namespace Mist { ///We take the corresponding keyframe and interframes of the main video track and take concurrent frames from its secondary (audio) tracks ///\todo See if we can use something more elegant than a member variable... void OutProgressiveMP4::buildFragment() { - DTSC::Key & currKey = myMeta.tracks[vidTrack].getKey(getKeyForTime(vidTrack, thisPacket.getTime())); - uint64_t startms = thisPacket.getTime(); if (!needsLookAhead){ needsLookAhead = 1000; currentPartSet.clear(); return; } + + DTSC::Key & currKey = myMeta.tracks[vidTrack].getKey(getKeyForTime(vidTrack, thisPacket.getTime())); + uint64_t startms = thisPacket.getTime(); uint64_t endms = startms + needsLookAhead; bool missingSome = true; @@ -829,7 +885,7 @@ namespace Mist { //Make sure we always look ahead at least a single frame if (pDur > needsLookAhead){ needsLookAhead = pDur; - INFO_MSG("Slow frame! Increasing lookAhead to %lums", needsLookAhead); + INFO_MSG("Slow frame! Increasing lookAhead to %ums", needsLookAhead); } thisRange.lastPart = i; thisRange.lastTime = curMS; @@ -840,7 +896,7 @@ namespace Mist { endms = thisTrack.lastms; if (needsLookAhead != endms - startms){ needsLookAhead = endms - startms; - INFO_MSG("False start! Increasing lookAhead to %lums", needsLookAhead); + INFO_MSG("False start! Increasing lookAhead to %ums", needsLookAhead); missingSome = true; } break; @@ -869,7 +925,7 @@ namespace Mist { // (sync delay = ~1s, minimum lookAhead is 420ms -> ~600ms extra needed, we use 2000ms to be safe) if (myMeta.sourceURI.find("http://") == std::string::npos || myMeta.sourceURI.find(".m3u") == std::string::npos){ if (fragSeqNum > 10 && thisPacket.getTime() + needsLookAhead + 2000 < mainTrk.keys.rbegin()->getTime() && mainTrk.lastms - mainTrk.keys.rbegin()->getTime() > needsLookAhead){ - INFO_MSG("Skipping forward %llums (%llu ms LA)", mainTrk.keys.rbegin()->getTime() - thisPacket.getTime(), needsLookAhead); + INFO_MSG("Skipping forward %llums (%u ms LA)", mainTrk.keys.rbegin()->getTime() - thisPacket.getTime(), needsLookAhead); seek(mainTrk.keys.rbegin()->getTime()); return; } @@ -889,15 +945,17 @@ namespace Mist { partListSent++; } - if ((unsigned long)thisPacket.getTrackId() != sortSet.begin()->trackID || thisPacket.getTime() != sortSet.begin()->time || len != sortSet.begin()->size) { - if (thisPacket.getTime() > sortSet.begin()->time || (unsigned long)thisPacket.getTrackId() > sortSet.begin()->trackID) { + keyPart thisPart = *sortSet.begin(); + uint64_t thisSize = myMeta.tracks[thisPart.trackID].parts[thisPart.index].getSize(); + if ((unsigned long)thisPacket.getTrackId() != thisPart.trackID || thisPacket.getTime() != thisPart.time || len != thisSize){ + if (thisPacket.getTime() > sortSet.begin()->time || thisPacket.getTrackId() > sortSet.begin()->trackID) { if (perfect) { - WARN_MSG("Warning: input is inconsistent. Expected %lu:%llu but got %ld:%llu - cancelling playback", sortSet.begin()->trackID, sortSet.begin()->time, thisPacket.getTrackId(), thisPacket.getTime()); + WARN_MSG("Warning: input is inconsistent. Expected %lu:%lu but got %ld:%llu - cancelling playback", thisPart.trackID, thisPart.time, thisPacket.getTrackId(), thisPacket.getTime()); perfect = false; myConn.close(); } } else { - WARN_MSG("Did not receive expected %lu:%llu (%lub) but got %ld:%llu (%lub) - throwing it away", sortSet.begin()->trackID, sortSet.begin()->time, sortSet.begin()->size, thisPacket.getTrackId(), thisPacket.getTime(), len); + WARN_MSG("Did not receive expected %lu:%lu (%lub) but got %ld:%llu (%ub) - throwing it away", thisPart.trackID, thisPart.time, thisSize, thisPacket.getTrackId(), thisPacket.getTime(), len); } return; } @@ -909,35 +967,34 @@ namespace Mist { } if (currPos >= byteStart) { - myConn.SendNow(dataPointer, std::min(leftOver, (long long)len)); + myConn.SendNow(dataPointer, std::min(leftOver, (int64_t)len)); leftOver -= len; } else { if (currPos + (long long)len > byteStart) { - myConn.SendNow(dataPointer + (byteStart - currPos), std::min(leftOver, (long long)(len - (byteStart - currPos)))); + myConn.SendNow(dataPointer + (byteStart - currPos), std::min(leftOver, (int64_t)(len - (byteStart - currPos)))); leftOver -= len - (byteStart - currPos); } } //keep track of where we are if (!sortSet.empty()) { - keyPart temp; - temp.index = sortSet.begin()->index + 1; - temp.trackID = sortSet.begin()->trackID; - if (temp.index < myMeta.tracks[temp.trackID].parts.size()) { //only insert when there are parts left - temp.time = sortSet.begin()->endTime;//timeplace of frame - temp.endTime = sortSet.begin()->endTime + myMeta.tracks[temp.trackID].parts[temp.index].getDuration(); - temp.size = myMeta.tracks[temp.trackID].parts[temp.index].getSize();//bytesize of frame + keyPart temp = *sortSet.begin(); + sortSet.erase(sortSet.begin()); + + + + DTSC::Track & thisTrack = myMeta.tracks[temp.trackID]; + + currPos += thisTrack.parts[temp.index].getSize(); + if (temp.index + 1 < thisTrack.parts.size()) { //only insert when there are parts left + temp.time += thisTrack.parts[temp.index].getDuration(); + ++temp.index; sortSet.insert(temp); } - currPos += sortSet.begin()->size; - //remove highest keyPart - sortSet.erase(sortSet.begin()); + } - - if (leftOver < 1) { - //stop playback, wait for new request stop(); wantRequest = true; @@ -958,7 +1015,7 @@ namespace Mist { } } if (reSeek){ - INFO_MSG("Increased initial lookAhead of %lums", needsLookAhead); + INFO_MSG("Increased initial lookAhead of %ums", needsLookAhead); initialSeek(); } }else{ diff --git a/src/output/output_progressive_mp4.h b/src/output/output_progressive_mp4.h index c21a569a..353ed631 100644 --- a/src/output/output_progressive_mp4.h +++ b/src/output/output_progressive_mp4.h @@ -9,29 +9,23 @@ namespace Mist { if (time < rhs.time){ return true; } - if (time == rhs.time){ - if (trackID < rhs.trackID){ - return true; - } - if (trackID == rhs.trackID){ - return index < rhs.index; - } + if (time > rhs.time){ + return false; } - return false; + if (trackID < rhs.trackID){ + return true; + } + return (trackID == rhs.trackID && index < rhs.index); } - long unsigned int trackID; - long unsigned int size; - long long unsigned int time; - long long unsigned int endTime; - long long unsigned int byteOffset;//added for MP4 fragmented - long int timeOffset;//added for MP4 fragmented - long unsigned int duration;//added for MP4 fragmented - long unsigned int index; + size_t trackID; + uint64_t time; + uint64_t byteOffset;//Stores relative bpos for fragmented MP4 + uint64_t index; }; struct fragSet{ - uint32_t firstPart; - uint32_t lastPart; + uint64_t firstPart; + uint64_t lastPart; uint64_t firstTime; uint64_t lastTime; }; @@ -40,42 +34,43 @@ namespace Mist { OutProgressiveMP4(Socket::Connection & conn); ~OutProgressiveMP4(); static void init(Util::Config * cfg); - void parseRange(std::string header, long long & byteStart, long long & byteEnd, long long & seekPoint, unsigned int headerSize); - std::string DTSCMeta2MP4Header(long long & size, int fragmented = 0); + void parseRange(std::string header, uint64_t & byteStart, uint64_t & byteEnd, uint64_t & seekPoint, uint64_t headerSize); + uint64_t mp4HeaderSize(uint64_t & fileSize, int fragmented = 0); + std::string DTSCMeta2MP4Header(uint64_t & size, int fragmented = 0); //int fragmented values: 0 = non fragmented stream, 1 = frag stream main header void buildFragment();//this builds the structure of the fragment header and stores it in a member variable void sendFragmentHeader();//this builds the moof box for fragmented MP4 - void findSeekPoint(long long byteStart, long long & seekPoint, unsigned int headerSize); + void findSeekPoint(uint64_t byteStart, uint64_t & seekPoint, uint64_t headerSize); void onHTTP(); void sendNext(); void sendHeader(); protected: - long long fileSize; - long long byteStart; - long long byteEnd; - long long leftOver; - long long currPos; - long long seekPoint; + uint64_t fileSize; + uint64_t byteStart; + uint64_t byteEnd; + int64_t leftOver; + uint64_t currPos; + uint64_t seekPoint; //variables for standard MP4 std::set sortSet;//needed for unfragmented MP4, remembers the order of keyparts //variables for fragmented - int fragSeqNum;//the sequence number of the next keyframe/fragment when producing fragmented MP4's - long unsigned int vidTrack;//the video track we use as fragmenting base - long long unsigned int realBaseOffset;//base offset for every moof packet + size_t fragSeqNum;//the sequence number of the next keyframe/fragment when producing fragmented MP4's + size_t vidTrack;//the video track we use as fragmenting base + uint64_t realBaseOffset;//base offset for every moof packet //from sendnext - long unsigned int partListSent;//parts of current fragSet sent - long unsigned int partListLength;//amount of packets in current fragment - long int fragKeyNumberShift;//the difference between the first fragment Number and the first keyframe number + size_t partListSent;//parts of current fragSet sent + size_t partListLength;//amount of packets in current fragment + int64_t fragKeyNumberShift;//the difference between the first fragment Number and the first keyframe number bool sending3GP; bool chromeWorkaround; - long long unsigned estimateFileSize(); + uint64_t estimateFileSize(); //This is a dirty solution... but it prevents copying and copying and copying again - std::map currentPartSet; + std::map currentPartSet; }; }