MP4 rework

This commit is contained in:
Erik Zandvliet 2017-07-19 13:24:54 +02:00 committed by Thulinma
parent 266248a67e
commit f5293ea29f
6 changed files with 362 additions and 242 deletions

View file

@ -65,13 +65,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. /// Returns the values at byte positions 4 through 7.
std::string Box::getType() { std::string Box::getType() {
return std::string(data + 4, 4); 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. /// 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); return !memcmp(boxType, data + 4, 4);
} }
@ -742,6 +754,29 @@ namespace MP4 {
return getBox(tempLoc); 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<Box> containerBox::getChildren(const char * boxName){
std::deque<Box> 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::string containerBox::toPrettyString(uint32_t indent) {
std::stringstream r; std::stringstream r;
r << std::string(indent, ' ') << "[" << getType() << "] Container Box (" << boxedSize() << ")" << std::endl; r << std::string(indent, ' ') << "[" << getType() << "] Container Box (" << boxedSize() << ")" << std::endl;

View file

@ -26,10 +26,16 @@ namespace MP4 {
Box(const Box & rs); Box(const Box & rs);
Box & operator = (const Box & rs); Box & operator = (const Box & rs);
~Box(); ~Box();
operator bool() const {
return data && data_size >= 8 && !isType("erro");
}
void copyFrom(const Box & rs);
std::string getType(); std::string getType();
bool isType(const char * boxType); bool isType(const char * boxType) const;
bool read(FILE * newData); bool read(FILE * newData);
bool read(std::string & newData); bool read(std::string & newData);
uint64_t boxedSize(); uint64_t boxedSize();
uint64_t payloadSize(); uint64_t payloadSize();
char * asBox(); char * asBox();
@ -84,6 +90,24 @@ namespace MP4 {
void setContent(Box & newContent, uint32_t no); void setContent(Box & newContent, uint32_t no);
Box & getContent(uint32_t no, bool unsafe = false); Box & getContent(uint32_t no, bool unsafe = false);
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
Box getChild(const char * boxName);
template <typename T>
T getChild(){
T a;
MP4::Box r = getChild(a.getType().c_str());
return (T&)r;
}
std::deque<Box> getChildren(const char * boxName);
template <typename T>
std::deque<T> getChildren(){
T a;
std::deque<Box> tmpRes = getChildren(a.getType().c_str());
std::deque<T> res;
for (std::deque<Box>::iterator it = tmpRes.begin(); it != tmpRes.end(); it++){
res.push_back((T&)*it);
}
return res;
}
}; };
class containerFullBox: public fullBox { class containerFullBox: public fullBox {

View file

@ -581,6 +581,26 @@ namespace MP4 {
return payload() + 8; 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) { void AVCC::setPPSNumber(uint32_t newPPSNumber) {
int offset = 8 + getSPSLen(); int offset = 8 + getSPSLen();
setInt8(newPPSNumber, offset); setInt8(newPPSNumber, offset);
@ -617,9 +637,9 @@ namespace MP4 {
r << std::string(indent + 1, ' ') << "Compatible Profiles: " << getCompatibleProfiles() << std::endl; r << std::string(indent + 1, ' ') << "Compatible Profiles: " << getCompatibleProfiles() << std::endl;
r << std::string(indent + 1, ' ') << "Level: " << getLevel() << 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 + 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 + 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(); return r.str();
} }
@ -634,7 +654,7 @@ namespace MP4 {
void AVCC::setPayload(std::string newPayload) { void AVCC::setPayload(std::string newPayload) {
if (!reserve(0, payloadSize(), newPayload.size())) { 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; return;
} }
memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size());
@ -1243,7 +1263,7 @@ namespace MP4 {
return r.str(); 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); memcpy(data + 4, "hdlr", 4);
//reserve an entire box, except for the string part at the end //reserve an entire box, except for the string part at the end
if (!reserve(0, 8, 32)) { if (!reserve(0, 8, 32)) {
@ -2336,9 +2356,7 @@ namespace MP4 {
for (unsigned int i = 0; i < getEntryCount(); i++) { for (unsigned int i = 0; i < getEntryCount(); i++) {
static STTSEntry temp; static STTSEntry temp;
temp = getSTTSEntry(i); temp = getSTTSEntry(i);
r << std::string(indent + 1, ' ') << "Entry[" << i << "]:" << std::endl; r << std::string(indent + 1, ' ') << "Entry[" << i << "]: " << temp.sampleCount << " sample(s) of " << temp.sampleDelta << "ms each" << std::endl;
r << std::string(indent + 2, ' ') << "SampleCount: " << temp.sampleCount << std::endl;
r << std::string(indent + 2, ' ') << "SampleDelta: " << temp.sampleDelta << std::endl;
} }
return r.str(); return r.str();

View file

@ -112,11 +112,13 @@ namespace MP4 {
void setSPS(std::string newSPS); void setSPS(std::string newSPS);
uint32_t getSPSLen(); uint32_t getSPSLen();
char * getSPS(); char * getSPS();
std::string hexSPS();
void setPPSNumber(uint32_t newPPSNumber); void setPPSNumber(uint32_t newPPSNumber);
uint32_t getPPSNumber(); uint32_t getPPSNumber();
void setPPS(std::string newPPS); void setPPS(std::string newPPS);
uint32_t getPPSLen(); uint32_t getPPSLen();
char * getPPS(); char * getPPS();
std::string hexPPS();
std::string asAnnexB(); std::string asAnnexB();
void setPayload(std::string newPayload); void setPayload(std::string newPayload);
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
@ -281,7 +283,7 @@ namespace MP4 {
class HDLR: public Box { class HDLR: public Box {
public: public:
HDLR(std::string & type, std::string name); HDLR(const std::string & type = "", const std::string & name = "");
void setHandlerType(const char * newHandlerType); void setHandlerType(const char * newHandlerType);
std::string getHandlerType(); std::string getHandlerType();
void setName(std::string newName); void setName(std::string newName);
@ -420,7 +422,7 @@ namespace MP4 {
class TKHD: public fullBox { class TKHD: public fullBox {
public: 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); TKHD(DTSC::Track & track, bool fragmented);
void setCreationTime(uint64_t newCreationTime); void setCreationTime(uint64_t newCreationTime);
@ -454,7 +456,7 @@ namespace MP4 {
class MDHD: public fullBox { class MDHD: public fullBox {
public: public:
MDHD(uint64_t duration); MDHD(uint64_t duration = 0);
void setCreationTime(uint64_t newCreationTime); void setCreationTime(uint64_t newCreationTime);
uint64_t getCreationTime(); uint64_t getCreationTime();
void setModificationTime(uint64_t newModificationTime); void setModificationTime(uint64_t newModificationTime);

View file

@ -5,6 +5,8 @@
#include <mist/bitfields.h> #include <mist/bitfields.h>
#include "output_progressive_mp4.h" #include "output_progressive_mp4.h"
#include <inttypes.h>
namespace Mist { namespace Mist {
OutProgressiveMP4::OutProgressiveMP4(Socket::Connection & conn) : HTTPOutput(conn){} OutProgressiveMP4::OutProgressiveMP4(Socket::Connection & conn) : HTTPOutput(conn){}
OutProgressiveMP4::~OutProgressiveMP4() {} OutProgressiveMP4::~OutProgressiveMP4() {}
@ -23,19 +25,94 @@ namespace Mist {
capa["methods"][0u]["priority"] = 8ll; capa["methods"][0u]["priority"] = 8ll;
capa["methods"][0u]["nolive"] = 1; capa["methods"][0u]["nolive"] = 1;
} }
uint64_t OutProgressiveMP4::estimateFileSize() {
long long unsigned OutProgressiveMP4::estimateFileSize(){ uint64_t retVal = 0;
long long unsigned retVal = 0; for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) {
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::deque<unsigned long>::iterator keyIt = myMeta.tracks[*it].keySizes.begin(); keyIt != myMeta.tracks[*it].keySizes.end(); keyIt++) {
for (std::deque<unsigned long>::iterator keyIt = myMeta.tracks[*it].keySizes.begin(); keyIt != myMeta.tracks[*it].keySizes.end(); keyIt++){
retVal += *keyIt; retVal += *keyIt;
} }
} }
return retVal * 1.1; return retVal * 1.1;
} }
uint64_t OutProgressiveMP4::mp4HeaderSize(uint64_t & fileSize) {
bool useLargeBoxes = (estimateFileSize() > 0xFFFFFFFFull);
uint64_t res = 36 // FTYP Box
+ 8 //MOOV box
+ 108; //MVHD Box
for (std::set<unsigned long>::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 + (partCount * 4);//STSZ
tmpRes += 16 + (partCount * (useLargeBoxes ? 8 : 4));//STCO
tmpRes += 16 + (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
tmpRes += 16 + (thisTrack.keys.size() * 4);
}
if (thisTrack.type == "audio"){
tmpRes += 16//SMHD Box
+ 16//STSD
+ 36//MP4A
+ 37 + thisTrack.init.size();//ESDS
}
//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
}
res += tmpRes;
}
res += 8; //mdat beginning
fileSize += res;
return res;
}
///\todo This function does not indicate errors anywhere... maybe fix this... ///\todo This function does not indicate errors anywhere... maybe fix this...
std::string OutProgressiveMP4::DTSCMeta2MP4Header(long long & size){ std::string OutProgressiveMP4::DTSCMeta2MP4Header(uint64_t & size) {
//Make sure we have a proper being value for the size... //Make sure we have a proper being value for the size...
size = 0; size = 0;
//Stores the result of the function //Stores the result of the function
@ -56,20 +133,26 @@ namespace Mist {
MP4::MOOV moovBox; MP4::MOOV moovBox;
//Keep track of the current index within the moovBox //Keep track of the current index within the moovBox
unsigned int moovOffset = 0; unsigned int moovOffset = 0;
//calculating longest duration
long long unsigned firstms = 0xFFFFFFFFFFFFFFull;
long long unsigned lastms = 0; //Construct with duration of -1
MP4::MVHD mvhdBox(-1);
//Then override it to set the correct duration
uint64_t firstms = 0xFFFFFFFFFFFFFFull;
uint64_t lastms = 0;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) {
lastms = std::max(lastms, myMeta.tracks[*it].lastms); lastms = std::max(lastms, (uint64_t)myMeta.tracks[*it].lastms);
firstms = std::min(firstms, myMeta.tracks[*it].firstms); firstms = std::min(firstms, (uint64_t)myMeta.tracks[*it].firstms);
} }
MP4::MVHD mvhdBox(lastms - firstms); mvhdBox.setDuration(lastms - firstms);
//Set the trackid for the first "empty" track within the file. //Set the trackid for the first "empty" track within the file.
mvhdBox.setTrackID(selectedTracks.size() + 1); mvhdBox.setTrackID(selectedTracks.size() + 1);
moovBox.setContent(mvhdBox, moovOffset++); moovBox.setContent(mvhdBox, moovOffset++);
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) {
DTSC::Track & thisTrack = myMeta.tracks[*it]; DTSC::Track & thisTrack = myMeta.tracks[*it];
size_t partCount = thisTrack.parts.size();
uint64_t tDuration = thisTrack.lastms - thisTrack.firstms;
MP4::TRAK trakBox; MP4::TRAK trakBox;
//Keep track of the current index within the moovBox //Keep track of the current index within the moovBox
unsigned int trakOffset = 0; unsigned int trakOffset = 0;
@ -84,7 +167,7 @@ namespace Mist {
elstBox.setVersion(0); elstBox.setVersion(0);
elstBox.setFlags(0); elstBox.setFlags(0);
elstBox.setCount(1); elstBox.setCount(1);
elstBox.setSegmentDuration(0, thisTrack.lastms - thisTrack.firstms); elstBox.setSegmentDuration(0, tDuration);
elstBox.setMediaTime(0, 0); elstBox.setMediaTime(0, 0);
elstBox.setMediaRateInteger(0, 1); elstBox.setMediaRateInteger(0, 1);
elstBox.setMediaRateFraction(0, 0); elstBox.setMediaRateFraction(0, 0);
@ -92,17 +175,17 @@ namespace Mist {
trakBox.setContent(edtsBox, trakOffset++); trakBox.setContent(edtsBox, trakOffset++);
MP4::MDIA mdiaBox; MP4::MDIA mdiaBox;
unsigned int mdiaOffset = 0; size_t mdiaOffset = 0;
//Add the mandatory MDHD and HDLR boxes to the MDIA //Add the mandatory MDHD and HDLR boxes to the MDIA
MP4::MDHD mdhdBox(thisTrack.lastms - thisTrack.firstms); MP4::MDHD mdhdBox(tDuration);
mdhdBox.setLanguage(thisTrack.lang); mdhdBox.setLanguage(thisTrack.lang);
mdiaBox.setContent(mdhdBox, mdiaOffset++); mdiaBox.setContent(mdhdBox, mdiaOffset++);
MP4::HDLR hdlrBox(thisTrack.type, thisTrack.getIdentifier()); MP4::HDLR hdlrBox(thisTrack.type, thisTrack.getIdentifier());
mdiaBox.setContent(hdlrBox, mdiaOffset++); mdiaBox.setContent(hdlrBox, mdiaOffset++);
MP4::MINF minfBox; MP4::MINF minfBox;
unsigned int minfOffset = 0; size_t minfOffset = 0;
//Add a track-type specific box to the MINF box //Add a track-type specific box to the MINF box
if (thisTrack.type == "video") { if (thisTrack.type == "video") {
@ -121,9 +204,8 @@ namespace Mist {
minfBox.setContent(dinfBox, minfOffset++); minfBox.setContent(dinfBox, minfOffset++);
MP4::STBL stblBox; MP4::STBL stblBox;
unsigned int stblOffset = 0; size_t stblOffset = 0;
//Add STSD box //Add STSD box
MP4::STSD stsdBox(0); MP4::STSD stsdBox(0);
@ -138,25 +220,59 @@ namespace Mist {
//Add STTS Box //Add STTS Box
MP4::STTS sttsBox(0); MP4::STTS sttsBox(0);
std::deque<std::pair<int, int> > sttsCounter; //Add STSZ Box
for (unsigned int part = 0; part < thisTrack.parts.size(); ++part) { MP4::STSZ stszBox(0);
MP4::CTTS cttsBox;
cttsBox.setVersion(0);
MP4::CTTSEntry tmpEntry;
tmpEntry.sampleCount = 0;
tmpEntry.sampleOffset = thisTrack.parts[0].getOffset();
std::deque<std::pair<size_t, size_t> > 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 //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()){ if (!sttsCounter.size() || sttsCounter.rbegin()->second != partDur){
//Set the counter to 0, so we don't have to handle this situation diffent when updating sttsCounter.push_back(std::pair<size_t,size_t>(0, partDur));
sttsCounter.push_back(std::pair<int,int>(0, thisTrack.parts[part].getDuration()));
} }
//Then update the counter //Update the counter
sttsCounter.rbegin()->first++; 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++;
} }
//Write all entries in reverse MP4::STTSEntry sttsEntry;
for (unsigned int entry = sttsCounter.size(); entry > 0; --entry){ sttsBox.setSTTSEntry(sttsEntry, sttsCounter.size() - 1);
MP4::STTSEntry newEntry; size_t sttsIdx = 0;
newEntry.sampleCount = sttsCounter[entry - 1].first;; for (std::deque<std::pair<size_t, size_t> >::iterator it2 = sttsCounter.begin(); it2 != sttsCounter.end(); it2++){
newEntry.sampleDelta = sttsCounter[entry - 1].second; sttsEntry.sampleCount = it2->first;
sttsBox.setSTTSEntry(newEntry, entry - 1);///\todo rewrite for sanity 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(sttsBox, stblOffset++);
stblBox.setContent(stszBox, stblOffset++);
//Add STSS Box IF type is video and we are not fragmented //Add STSS Box IF type is video and we are not fragmented
if (thisTrack.type == "video") { if (thisTrack.type == "video") {
@ -175,56 +291,16 @@ namespace Mist {
stscBox.setSTSCEntry(stscEntry, 0); stscBox.setSTSCEntry(stscEntry, 0);
stblBox.setContent(stscBox, stblOffset++); stblBox.setContent(stscBox, stblOffset++);
bool containsOffsets = false;
//Add STSZ Box
MP4::STSZ stszBox(0);
if (thisTrack.parts.size()) {
std::deque<DTSC::Part>::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<DTSC::Part>::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) //Create STCO Box (either stco or co64)
//note: Inserting empty values on purpose here, will be fixed later. //note: Inserting empty values on purpose here, will be fixed later.
if (useLargeBoxes) { if (useLargeBoxes) {
MP4::CO64 CO64Box; MP4::CO64 CO64Box;
CO64Box.setChunkOffset(0, thisTrack.parts.size() - 1); CO64Box.setChunkOffset(0, partCount - 1);
stblBox.setContent(CO64Box, stblOffset++); stblBox.setContent(CO64Box, stblOffset++);
} else { } else {
MP4::STCO stcoBox(0); MP4::STCO stcoBox(0);
stcoBox.setChunkOffset(0, thisTrack.parts.size() - 1); stcoBox.setChunkOffset(0, partCount - 1);
stblBox.setContent(stcoBox, stblOffset++); stblBox.setContent(stcoBox, stblOffset++);
} }
@ -237,110 +313,74 @@ namespace Mist {
moovBox.setContent(trakBox, moovOffset++); moovBox.setContent(trakBox, moovOffset++);
} }
//initial offset length ftyp, length moov + 8 //initial offset length ftyp, length moov + 8
unsigned long long int dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8; uint64_t dataOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8;
//update all STCO or CO64 from the following maps;
std::map <long unsigned, MP4::STCO> checkStcoBoxes; std::map <size_t, MP4::STCO> checkStcoBoxes;
std::map <long unsigned, MP4::CO64> checkCO64Boxes; std::map <size_t, MP4::CO64> checkCO64Boxes;
//for all tracks
for (unsigned int i = 1; i < moovBox.getContentCount(); i++){ std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
//10 lines to get the STCO box. for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
MP4::TRAK checkTrakBox; MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
MP4::Box checkMdiaBox; MP4::STBL stblBox = trakIt->getChild<MP4::MDIA>().getChild<MP4::MINF>().getChild<MP4::STBL>();
MP4::Box checkTkhdBox; if (useLargeBoxes){
MP4::MINF checkMinfBox; checkCO64Boxes.insert(std::pair<size_t, MP4::CO64>(tkhdBox.getTrackID(), stblBox.getChild<MP4::CO64>()));
MP4::STBL checkStblBox; }else{
//MP4::STCO checkStcoBox; checkStcoBoxes.insert(std::pair<size_t, MP4::STCO>(tkhdBox.getTrackID(), stblBox.getChild<MP4::STCO>()));
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<long unsigned, MP4::STCO>(((MP4::TKHD&)checkTkhdBox).getTrackID(), ((MP4::STCO&)checkStblBox.getContent(j)) ));
break;
}
if (checkStblBox.getContent(j).isType("co64")){
checkCO64Boxes.insert( std::pair<long unsigned, MP4::CO64>(((MP4::TKHD&)checkTkhdBox).getTrackID(), ((MP4::CO64&)checkStblBox.getContent(j)) ));
break;
}
} }
} }
//inserting right values in the STCO box header //inserting right values in the STCO box header
//total = 0; //total = 0;
//Keep track of the current size of the data within the mdat //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 //Current values are actual byte offset without header-sized offset
std::set <keyPart> sortSet;//filling sortset for interleaving parts std::set <keyPart> sortSet;//filling sortset for interleaving parts
for (std::set<long unsigned int>::iterator subIt = selectedTracks.begin(); subIt != selectedTracks.end(); subIt++) { for (std::set<long unsigned int>::iterator subIt = selectedTracks.begin(); subIt != selectedTracks.end(); subIt++) {
keyPart temp; keyPart temp;
temp.trackID = *subIt; temp.trackID = *subIt;
temp.time = myMeta.tracks[*subIt].firstms;//timeplace of frame 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; 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); sortSet.insert(temp);
} }
while (!sortSet.empty()){ while (!sortSet.empty()) {
std::set<keyPart>::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 //setting the right STCO size in the STCO box
if (useLargeBoxes){//Re-using the previously defined boolean for speedup 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 { } 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 //add next keyPart to sortSet
DTSC::Track & thisTrack = myMeta.tracks[keyBegin->trackID]; if (temp.index + 1< thisTrack.parts.size()) {//Only create new element, when there are new elements to be added
if (keyBegin->index < thisTrack.parts.size() - 1) {//Only create new element, when there are new elements to be added temp.time += thisTrack.parts[temp.index].getDuration();
keyPart temp = *keyBegin; ++temp.index;
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); sortSet.insert(temp);
} }
//remove highest keyPart
sortSet.erase(keyBegin);
} }
///\todo Update this thing for boxes >4G? ///\todo Update this thing for boxes >4G?
mdatSize = dataSize + 8;//+8 for mp4 header mdatSize = dataSize + 8;//+8 for mp4 header
header << std::string(moovBox.asBox(), moovBox.boxedSize()); header << std::string(moovBox.asBox(), moovBox.boxedSize());
char mdatHeader[8] = {0x00,0x00,0x00,0x00,'m','d','a','t'}; char mdatHeader[8] = {0x00,0x00,0x00,0x00,'m','d','a','t'};
if (mdatSize < 0xFFFFFFFF){ if (mdatSize < 0xFFFFFFFF){
Bit::htobl(mdatHeader, mdatSize); Bit::htobl(mdatHeader, mdatSize);
} }
header.write(mdatHeader, 8); header << std::string(mdatHeader, 8);
size += header.str().size(); size += header.str().size();
return header.str(); return header.str();
} }
/// Calculate a seekPoint, based on byteStart, metadata, tracks and headerSize. /// Calculate a seekPoint, based on byteStart, metadata, tracks and headerSize.
/// The seekPoint will be set to the timestamp of the first packet to send. /// 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; seekPoint = 0;
//if we're starting in the header, seekPoint is always zero. //if we're starting in the header, seekPoint is always zero.
if (byteStart <= headerSize) { if (byteStart <= headerSize) {
@ -349,29 +389,34 @@ namespace Mist {
//okay, we're past the header. Substract the headersize from the starting postion. //okay, we're past the header. Substract the headersize from the starting postion.
byteStart -= headerSize; byteStart -= headerSize;
//forward through the file by headers, until we reach the point where we need to be //forward through the file by headers, until we reach the point where we need to be
while (!sortSet.empty()){ 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 //record where we are
seekPoint = sortSet.begin()->time; seekPoint = temp.time;
//substract the size of this fragment from byteStart //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 that put us past the point where we wanted to be, return right now
if (byteStart < 0) { if (partSize > byteStart) {
INFO_MSG("We're starting at time %lld, skipping %lld bytes", seekPoint, byteStart+sortSet.begin()->size); INFO_MSG("We're starting at time %" PRIu64 ", skipping %" PRIu64 " bytes", seekPoint, partSize - byteStart);
return; return;
} }
byteStart -= partSize;
//otherwise, set currPos to where we are now and continue //otherwise, set currPos to where we are now and continue
currPos += sortSet.begin()->size; currPos += partSize;
//find the next part
keyPart temp; if (temp.index + 1 < myMeta.tracks[temp.trackID].parts.size()){ //only insert when there are parts left
temp.index = sortSet.begin()->index + 1; temp.time += thisTrack.parts[temp.index].getDuration();
temp.trackID = sortSet.begin()->trackID; ++temp.index;
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
sortSet.insert(temp); sortSet.insert(temp);
} }
//remove highest keyPart //Remove just-parsed element
sortSet.erase(sortSet.begin()); sortSet.erase(sortSet.begin());
//wash, rinse, repeat //wash, rinse, repeat
} }
@ -379,13 +424,13 @@ namespace Mist {
//That's technically legal, of course. //That's technically legal, of course.
} }
/// Parses a "Range: " header, setting byteStart, byteEnd and seekPoint using data from metadata and tracks to do /// Parses a "Range: " header, setting byteStart, byteEnd and seekPoint using data from metadata and tracks to do
/// the calculations. /// the calculations.
/// On error, byteEnd is set to zero. /// 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="){ if (header.size() < 6 || header.substr(0, 6) != "bytes=") {
byteEnd = 0; byteEnd = 0;
DEBUG_MSG(DLVL_WARN, "Invalid range header: %s", header.c_str()); WARN_MSG("Invalid range header: %s", header.c_str());
return; return;
} }
header.erase(0, 6); header.erase(0, 6);
@ -424,8 +469,8 @@ namespace Mist {
} }
break; break;
} }
if (header[i] != '-'){ 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; byteEnd = 0;
return; return;
} }
@ -445,12 +490,12 @@ namespace Mist {
}else{ }else{
byteEnd = size; 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); findSeekPoint(byteStart, seekPoint, headerSize);
return; return;
} }
} }
void OutProgressiveMP4::onHTTP(){ void OutProgressiveMP4::onHTTP() {
if(H.method == "OPTIONS" || H.method == "HEAD"){ if(H.method == "OPTIONS" || H.method == "HEAD"){
H.Clean(); H.Clean();
H.setCORSHeaders(); H.setCORSHeaders();
@ -469,10 +514,8 @@ namespace Mist {
wantRequest = false; wantRequest = false;
sentHeader = false; sentHeader = false;
//For storing the header. fileSize = 0;
///\todo Do we really need this though? uint64_t headerSize = mp4HeaderSize(fileSize);
std::string headerData = DTSCMeta2MP4Header(fileSize);
seekPoint = 0; seekPoint = 0;
byteStart = 0; byteStart = 0;
byteEnd = fileSize - 1; byteEnd = fileSize - 1;
@ -483,13 +526,11 @@ namespace Mist {
keyPart temp; keyPart temp;
temp.trackID = *subIt; temp.trackID = *subIt;
temp.time = myMeta.tracks[*subIt].firstms;//timeplace of frame 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; temp.index = 0;
sortSet.insert(temp); sortSet.insert(temp);
} }
if (H.GetHeader("Range") != ""){ 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]; rangeType = H.GetHeader("Range")[0];
} }
H.Clean(); //make sure no parts of old requests are left in any buffers H.Clean(); //make sure no parts of old requests are left in any buffers
@ -523,64 +564,66 @@ namespace Mist {
//HTTP_S.StartResponse(HTTP_R, conn); //HTTP_S.StartResponse(HTTP_R, conn);
} }
leftOver = byteEnd - byteStart + 1;//add one byte, because range "0-0" = 1 byte of data leftOver = byteEnd - byteStart + 1;//add one byte, because range "0-0" = 1 byte of data
if (byteStart < (long long)headerData.size()){ if (byteStart < headerSize) {
/// \todo Switch to chunked? std::string headerData = DTSCMeta2MP4Header(fileSize);
myConn.SendNow(headerData.data()+byteStart, std::min((long long)headerData.size(), byteEnd) - byteStart);//send MP4 header myConn.SendNow(headerData.data() + byteStart, std::min(headerSize, byteEnd) - byteStart); //send MP4 header
leftOver -= std::min((long long)headerData.size(), byteEnd) - byteStart; 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
} }
void OutProgressiveMP4::sendNext(){ void OutProgressiveMP4::sendNext() {
static bool perfect = true; static bool perfect = true;
//Obtain a pointer to the data of this packet //Obtain a pointer to the data of this packet
char * dataPointer = 0; char * dataPointer = 0;
unsigned int len = 0; unsigned int len = 0;
thisPacket.getString("data", dataPointer, len); thisPacket.getString("data", dataPointer, len);
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();
if (perfect){ uint64_t thisSize = myMeta.tracks[thisPart.trackID].parts[thisPart.index].getSize();
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()); 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:%lu but got %ld:%llu - cancelling playback", thisPart.trackID, thisPart.time, thisPacket.getTrackId(), thisPacket.getTime());
perfect = false; perfect = false;
myConn.close(); myConn.close();
} }
}else{ } else {
DEBUG_MSG(DLVL_HIGH, "Did not receive expected %lu:%llu but got %ld:%llu - throwing it away", sortSet.begin()->trackID, sortSet.begin()->time, thisPacket.getTrackId(), thisPacket.getTime()); 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; return;
} }
if (currPos >= byteStart) { if (currPos >= byteStart) {
myConn.SendNow(dataPointer, std::min(leftOver, (long long)len)); myConn.SendNow(dataPointer, std::min(leftOver, (int64_t)len));
leftOver -= len; leftOver -= len;
} else { } else {
if (currPos + (long long)len > byteStart) { 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); leftOver -= len - (byteStart - currPos);
} }
} }
//keep track of where we are //keep track of where we are
if (!sortSet.empty()){ if (!sortSet.empty()) {
keyPart temp; keyPart temp = *sortSet.begin();
temp.index = sortSet.begin()->index + 1; sortSet.erase(sortSet.begin());
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(); DTSC::Track & thisTrack = myMeta.tracks[temp.trackID];
temp.size = myMeta.tracks[temp.trackID].parts[temp.index].getSize();//bytesize of frame
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); sortSet.insert(temp);
} }
currPos += sortSet.begin()->size;
//remove highest keyPart
sortSet.erase(sortSet.begin());
} }
if (leftOver < 1) {
if (leftOver < 1){
//stop playback, wait for new request //stop playback, wait for new request
stop(); stop();
wantRequest = true; wantRequest = true;

View file

@ -8,21 +8,18 @@ namespace Mist {
if (time < rhs.time){ if (time < rhs.time){
return true; return true;
} }
if (time == rhs.time){ if (time > rhs.time){
if (trackID < rhs.trackID){ return false;
return true;
}
if (trackID == rhs.trackID){
return index < rhs.index;
}
} }
return false; if (trackID < rhs.trackID){
return true;
}
return (trackID == rhs.trackID && index < rhs.index);
} }
long unsigned int trackID; size_t trackID;
long unsigned int size; uint64_t time;
long long unsigned int time; uint64_t byteOffset;//Stores relative bpos for fragmented MP4
long long unsigned int endTime; uint64_t index;
long unsigned int index;
}; };
class OutProgressiveMP4 : public HTTPOutput { class OutProgressiveMP4 : public HTTPOutput {
@ -30,24 +27,25 @@ namespace Mist {
OutProgressiveMP4(Socket::Connection & conn); OutProgressiveMP4(Socket::Connection & conn);
~OutProgressiveMP4(); ~OutProgressiveMP4();
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void parseRange(std::string header, long long & byteStart, long long & byteEnd, long long & seekPoint, unsigned int headerSize); void parseRange(std::string header, uint64_t & byteStart, uint64_t & byteEnd, uint64_t & seekPoint, uint64_t headerSize);
std::string DTSCMeta2MP4Header(long long & size); uint64_t mp4HeaderSize(uint64_t & fileSize);
void findSeekPoint(long long byteStart, long long & seekPoint, unsigned int headerSize); std::string DTSCMeta2MP4Header(uint64_t & size);
void findSeekPoint(uint64_t byteStart, uint64_t & seekPoint, uint64_t headerSize);
void onHTTP(); void onHTTP();
void sendNext(); void sendNext();
void sendHeader(); void sendHeader();
protected: protected:
long long fileSize; uint64_t fileSize;
long long byteStart; uint64_t byteStart;
long long byteEnd; uint64_t byteEnd;
long long leftOver; int64_t leftOver;
long long currPos; uint64_t currPos;
long long seekPoint; uint64_t seekPoint;
//variables for standard MP4 //variables for standard MP4
std::set <keyPart> sortSet;//needed for unfragmented MP4, remembers the order of keyparts std::set <keyPart> sortSet;//needed for unfragmented MP4, remembers the order of keyparts
long long unsigned estimateFileSize(); uint64_t estimateFileSize();
}; };
} }