Merge branch 'development' into LTS_development
This commit is contained in:
commit
a2b83e0cd3
9 changed files with 675 additions and 687 deletions
|
@ -37,6 +37,9 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", "
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||||
|
static inline void show_stackframe(){}
|
||||||
|
#else
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
static inline void show_stackframe() {
|
static inline void show_stackframe() {
|
||||||
void *trace[16];
|
void *trace[16];
|
||||||
|
@ -52,6 +55,7 @@ static inline void show_stackframe() {
|
||||||
DEBUG_MSG(0, "Backtrace[%d]: %s", i, messages[i]+p);
|
DEBUG_MSG(0, "Backtrace[%d]: %s", i, messages[i]+p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
|
37
lib/mp4.cpp
37
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.
|
/// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,6 +802,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;
|
||||||
|
|
26
lib/mp4.h
26
lib/mp4.h
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
|
@ -842,7 +862,7 @@ namespace MP4 {
|
||||||
|
|
||||||
void HVCC::setPayload(std::string newPayload) {
|
void HVCC::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());
|
||||||
|
@ -1478,7 +1498,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)) {
|
||||||
|
@ -2571,9 +2591,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();
|
||||||
|
|
||||||
|
|
|
@ -113,11 +113,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);
|
||||||
|
@ -333,7 +335,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);
|
||||||
|
@ -472,7 +474,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);
|
||||||
|
@ -506,7 +508,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);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <mist/stream.h>
|
#include <mist/stream.h>
|
||||||
#include <mist/flv_tag.h>
|
#include <mist/flv_tag.h>
|
||||||
#include <mist/defines.h>
|
#include <mist/defines.h>
|
||||||
|
@ -29,14 +30,12 @@ namespace Mist {
|
||||||
cttsBox.clear();
|
cttsBox.clear();
|
||||||
stszBox.clear();
|
stszBox.clear();
|
||||||
stcoBox.clear();
|
stcoBox.clear();
|
||||||
|
co64Box.clear();
|
||||||
|
stco64 = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long unsigned int mp4TrackHeader::size(){
|
uint64_t mp4TrackHeader::size(){
|
||||||
if (!stszBox.asBox()){
|
return (stszBox.asBox() ? stszBox.getSampleCount() : 0);
|
||||||
return 0;
|
|
||||||
}else{
|
|
||||||
return stszBox.getSampleCount();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mp4TrackHeader::read(MP4::TRAK & trakBox){
|
void mp4TrackHeader::read(MP4::TRAK & trakBox){
|
||||||
|
@ -44,80 +43,40 @@ namespace Mist {
|
||||||
std::string tmp;//temporary string for copying box data
|
std::string tmp;//temporary string for copying box data
|
||||||
MP4::Box trakLoopPeek;
|
MP4::Box trakLoopPeek;
|
||||||
timeScale = 1;
|
timeScale = 1;
|
||||||
//for all in trak
|
|
||||||
for (uint32_t j = 0; j < trakBox.getContentCount(); j++){
|
MP4::MDIA mdiaBox = trakBox.getChild<MP4::MDIA>();
|
||||||
trakLoopPeek = MP4::Box(trakBox.getContent(j).asBox(),false);
|
|
||||||
std::string trakBoxType = trakLoopPeek.getType();
|
timeScale = mdiaBox.getChild<MP4::MDHD>().getTimeScale();
|
||||||
if (trakBoxType == "mdia"){//fi tkhd & if mdia
|
|
||||||
MP4::Box mdiaLoopPeek;
|
MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
|
||||||
//for all in mdia
|
|
||||||
for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){
|
sttsBox.copyFrom(stblBox.getChild<MP4::STTS>());
|
||||||
mdiaLoopPeek = MP4::Box(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),false);
|
cttsBox.copyFrom(stblBox.getChild<MP4::CTTS>());
|
||||||
std::string mdiaBoxType = mdiaLoopPeek.getType();
|
stszBox.copyFrom(stblBox.getChild<MP4::STSZ>());
|
||||||
if (mdiaBoxType == "mdhd"){
|
stcoBox.copyFrom(stblBox.getChild<MP4::STCO>());
|
||||||
timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale();
|
co64Box.copyFrom(stblBox.getChild<MP4::CO64>());
|
||||||
}else if (mdiaBoxType == "minf"){//fi hdlr
|
stscBox.copyFrom(stblBox.getChild<MP4::STSC>());
|
||||||
//for all in minf
|
stco64 = co64Box.isType("co64");
|
||||||
//get all boxes: stco stsz,stss
|
hasCTTS = cttsBox.isType("ctts");
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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){
|
if (index < sampleIndex){
|
||||||
sampleIndex = 0;
|
sampleIndex = 0;
|
||||||
stscStart = 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
|
//check where the next index starts
|
||||||
unsigned long long nextSampleIndex;
|
uint64_t nextSampleIndex;
|
||||||
if (stscStart + 1 < stscBox.getEntryCount()){
|
if (stscStart + 1 < stscCount){
|
||||||
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscBox.getSTSCEntry(stscStart).firstChunk) * stscBox.getSTSCEntry(stscStart).samplesPerChunk;
|
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscEntry.firstChunk) * stscEntry.samplesPerChunk;
|
||||||
}else{
|
}else{
|
||||||
nextSampleIndex = stszBox.getSampleCount();
|
nextSampleIndex = stszBox.getSampleCount();
|
||||||
}
|
}
|
||||||
//if the next one is too far, we're in the right spot
|
|
||||||
if (nextSampleIndex > index){
|
if (nextSampleIndex > index){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -126,19 +85,13 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sampleIndex > index){
|
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);
|
uint64_t stcoPlace = (stscEntry.firstChunk - 1 ) + ((index - sampleIndex) / stscEntry.samplesPerChunk);
|
||||||
long long unsigned stszStart = sampleIndex + (stcoPlace - (stscBox.getSTSCEntry(stscStart).firstChunk - 1)) * stscBox.getSTSCEntry(stscStart).samplesPerChunk;
|
uint64_t stszStart = sampleIndex + (stcoPlace - (stscEntry.firstChunk - 1)) * stscEntry.samplesPerChunk;
|
||||||
|
|
||||||
//set the offset to the correct value
|
|
||||||
if (stcoBox.getType() == "co64"){
|
|
||||||
offset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoPlace);
|
|
||||||
}else{
|
|
||||||
offset = stcoBox.getChunkOffset(stcoPlace);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
offset = (stco64 ? co64Box.getChunkOffset(stcoPlace) : stcoBox.getChunkOffset(stcoPlace));
|
||||||
for (int j = stszStart; j < index; j++){
|
for (int j = stszStart; j < index; j++){
|
||||||
offset += stszBox.getEntrySize(j);
|
offset += stszBox.getEntrySize(j);
|
||||||
}
|
}
|
||||||
|
@ -150,14 +103,14 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
MP4::STTSEntry tmpSTTS;
|
MP4::STTSEntry tmpSTTS;
|
||||||
while (deltaIndex < sttsBox.getEntryCount()){
|
uint64_t sttsCount = sttsBox.getEntryCount();
|
||||||
|
while (deltaIndex < sttsCount){
|
||||||
tmpSTTS = sttsBox.getSTTSEntry(deltaIndex);
|
tmpSTTS = sttsBox.getSTTSEntry(deltaIndex);
|
||||||
if ((index - deltaPos) < tmpSTTS.sampleCount){
|
if ((index - deltaPos) < tmpSTTS.sampleCount){
|
||||||
break;
|
break;
|
||||||
}else{
|
}
|
||||||
deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
|
deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
|
||||||
deltaPos += tmpSTTS.sampleCount;
|
deltaPos += tmpSTTS.sampleCount;
|
||||||
}
|
|
||||||
++deltaIndex;
|
++deltaIndex;
|
||||||
}
|
}
|
||||||
timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale;
|
timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale;
|
||||||
|
@ -169,20 +122,18 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
if (hasCTTS){
|
if (hasCTTS){
|
||||||
MP4::CTTSEntry tmpCTTS;
|
MP4::CTTSEntry tmpCTTS;
|
||||||
while (offsetIndex < cttsBox.getEntryCount()){
|
uint32_t cttsCount = cttsBox.getEntryCount();
|
||||||
|
while (offsetIndex < cttsCount){
|
||||||
tmpCTTS = cttsBox.getCTTSEntry(offsetIndex);
|
tmpCTTS = cttsBox.getCTTSEntry(offsetIndex);
|
||||||
if ((index - offsetPos) < tmpCTTS.sampleCount){
|
if ((index - offsetPos) < tmpCTTS.sampleCount){
|
||||||
timeOffset = (tmpCTTS.sampleOffset*1000)/(int32_t)timeScale;
|
timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
offsetPos += tmpCTTS.sampleCount;
|
offsetPos += tmpCTTS.sampleCount;
|
||||||
++offsetIndex;
|
++offsetIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//next lines are common for next-getting and seeking
|
|
||||||
size = stszBox.getEntrySize(index);
|
size = stszBox.getEntrySize(index);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) {
|
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) {
|
||||||
|
@ -220,6 +171,7 @@ namespace Mist {
|
||||||
std::cerr << "File output in player mode not supported" << std::endl;
|
std::cerr << "File output in player mode not supported" << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
streamName = config->getString("streamname");
|
||||||
}
|
}
|
||||||
|
|
||||||
//open File
|
//open File
|
||||||
|
@ -236,324 +188,230 @@ namespace Mist {
|
||||||
INFO_MSG("inFile failed!");
|
INFO_MSG("inFile failed!");
|
||||||
return false;
|
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
|
//first we get the necessary header parts
|
||||||
while(!feof(inFile)){
|
while(!feof(inFile)){
|
||||||
std::string boxType = MP4::readBoxType(inFile);
|
std::string boxType = MP4::readBoxType(inFile);
|
||||||
|
if (boxType == "erro"){
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (boxType=="moov"){
|
if (boxType=="moov"){
|
||||||
MP4::MOOV moovBox;
|
MP4::MOOV moovBox;
|
||||||
moovBox.read(inFile);
|
moovBox.read(inFile);
|
||||||
//for all box in moov
|
//for all box in moov
|
||||||
|
|
||||||
MP4::Box moovLoopPeek;
|
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
|
||||||
for (uint32_t i = 0; i < moovBox.getContentCount(); i++){
|
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
|
||||||
tmp = std::string(moovBox.getContent(i).asBox() ,moovBox.getContent(i).boxedSize());
|
headerData[++trackNo].read(*trakIt);
|
||||||
moovLoopPeek.read(tmp);
|
|
||||||
//if trak
|
|
||||||
if (moovLoopPeek.getType() == "trak"){
|
|
||||||
//create new track entry here
|
|
||||||
trackNo ++;
|
|
||||||
|
|
||||||
headerData[trackNo].read((MP4::TRAK&)moovLoopPeek);
|
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}else if (boxType == "erro"){
|
|
||||||
break;
|
|
||||||
}else{
|
|
||||||
if (!MP4::skipBox(inFile)){//moving on to next box
|
if (!MP4::skipBox(inFile)){//moving on to next box
|
||||||
DEBUG_MSG(DLVL_FAIL,"Error in skipping box, exiting");
|
FAIL_MSG("Error in skipping box, exiting");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}//fi moov
|
}
|
||||||
}//when at the end of the file
|
|
||||||
//seek file to 0;
|
|
||||||
fseeko(inFile,0,SEEK_SET);
|
fseeko(inFile,0,SEEK_SET);
|
||||||
|
|
||||||
//See whether a separate header file exists.
|
//See whether a separate header file exists.
|
||||||
if (readExistingHeader()){return true;}
|
if (readExistingHeader()){return true;}
|
||||||
|
HIGH_MSG("Not read existing header");
|
||||||
|
|
||||||
trackNo = 0;
|
trackNo = 0;
|
||||||
//Create header file from MP4 data
|
//Create header file from MP4 data
|
||||||
while(!feof(inFile)){
|
while(!feof(inFile)){
|
||||||
std::string boxType = MP4::readBoxType(inFile);
|
std::string boxType = MP4::readBoxType(inFile);
|
||||||
|
if (boxType=="erro"){
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (boxType=="moov"){
|
if (boxType=="moov"){
|
||||||
MP4::MOOV moovBox;
|
MP4::MOOV moovBox;
|
||||||
moovBox.read(inFile);
|
moovBox.read(inFile);
|
||||||
//for all box in moov
|
|
||||||
MP4::Box moovLoopPeek;
|
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
|
||||||
for (uint32_t i = 0; i < moovBox.getContentCount(); i++){
|
HIGH_MSG("Obtained %zu trak Boxes", trak.size());
|
||||||
tmp = std::string(moovBox.getContent(i).asBox(), moovBox.getContent(i).boxedSize());
|
|
||||||
moovLoopPeek.read(tmp);
|
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
|
||||||
//if trak
|
uint64_t trackNo = myMeta.tracks.size()+1;
|
||||||
if (moovLoopPeek.getType() == "trak"){
|
|
||||||
//create new track entry here
|
|
||||||
long long unsigned int trackNo = myMeta.tracks.size()+1;
|
|
||||||
myMeta.tracks[trackNo].trackID = trackNo;
|
myMeta.tracks[trackNo].trackID = trackNo;
|
||||||
MP4::Box trakLoopPeek;
|
|
||||||
unsigned long int timeScale = 1;
|
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
|
||||||
//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){
|
if (tkhdBox.getWidth() > 0){
|
||||||
myMeta.tracks[trackNo].width = tkhdBox.getWidth();
|
myMeta.tracks[trackNo].width = tkhdBox.getWidth();
|
||||||
myMeta.tracks[trackNo].height = tkhdBox.getHeight();
|
myMeta.tracks[trackNo].height = tkhdBox.getHeight();
|
||||||
}
|
}
|
||||||
}else if (trakBoxType == "mdia"){//fi tkhd & if mdia
|
|
||||||
MP4::Box mdiaLoopPeek;
|
MP4::MDIA mdiaBox = trakIt->getChild<MP4::MDIA>();
|
||||||
//for all in mdia
|
|
||||||
for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){
|
MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
|
||||||
tmp = std::string(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),((MP4::MDIA&)trakLoopPeek).getContent(k).boxedSize());
|
uint64_t timescale = mdhdBox.getTimeScale();
|
||||||
mdiaLoopPeek.read(tmp);
|
myMeta.tracks[trackNo].lang = mdhdBox.getLanguage();
|
||||||
std::string mdiaBoxType = mdiaLoopPeek.getType();
|
|
||||||
if (mdiaBoxType == "mdhd"){
|
std::string hdlrType = mdiaBox.getChild<MP4::HDLR>().getHandlerType();
|
||||||
timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale();
|
if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "stbl"){
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
}else if (mdiaBoxType == "minf"){//fi hdlr
|
|
||||||
//for all in minf
|
MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
|
||||||
//get all boxes: stco stsz,stss
|
|
||||||
MP4::Box minfLoopPeek;
|
MP4::STSD stsdBox = stblBox.getChild<MP4::STSD>();
|
||||||
for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){
|
MP4::Box sEntryBox = stsdBox.getEntry(0);
|
||||||
tmp = std::string(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),((MP4::MINF&)mdiaLoopPeek).getContent(l).boxedSize());
|
std::string sType = sEntryBox.getType();
|
||||||
minfLoopPeek.read(tmp);
|
HIGH_MSG("Found track %zu of type %s", trackNo, sType.c_str());
|
||||||
std::string minfBoxType = minfLoopPeek.getType();
|
|
||||||
///\todo more stuff here
|
if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
|
||||||
if (minfBoxType == "stbl"){
|
MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox;
|
||||||
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].type = "video";
|
||||||
myMeta.tracks[trackNo].codec = "H264";
|
myMeta.tracks[trackNo].codec = "H264";
|
||||||
if (!myMeta.tracks[trackNo].width){
|
if (!myMeta.tracks[trackNo].width){
|
||||||
myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth();
|
myMeta.tracks[trackNo].width = vEntryBox.getWidth();
|
||||||
myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight();
|
myMeta.tracks[trackNo].height = vEntryBox.getHeight();
|
||||||
}
|
}
|
||||||
MP4::Box tmpBox2 = tmpBox;
|
MP4::Box initBox = vEntryBox.getCLAP();
|
||||||
MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP();
|
if (initBox.isType("avcC")){
|
||||||
if (tmpContent.getType() == "avcC"){
|
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
|
||||||
}
|
}
|
||||||
tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP();
|
initBox = vEntryBox.getPASP();
|
||||||
if (tmpContent.getType() == "avcC"){
|
if (initBox.isType("avcC")){
|
||||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
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...
|
///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){
|
if (!myMeta.tracks[trackNo].width){
|
||||||
h264::sequenceParameterSet sps;
|
h264::sequenceParameterSet sps;
|
||||||
sps.fromDTSCInit(myMeta.tracks[trackNo].init);
|
sps.fromDTSCInit(myMeta.tracks[trackNo].init);
|
||||||
h264::SPSMeta spsChar = sps.getCharacteristics();
|
h264::SPSMeta spsChar = sps.getCharacteristics();
|
||||||
myMeta.tracks[trackNo].width = spsChar.width;
|
myMeta.tracks[trackNo].width = spsChar.width;
|
||||||
myMeta.tracks[trackNo].height = spsChar.height;
|
myMeta.tracks[trackNo].height = spsChar.height;
|
||||||
}
|
}
|
||||||
}else if (tmpType == "hev1" || tmpType == "hvc1"){
|
}
|
||||||
|
if (sType == "hev1" || sType == "hvc1"){
|
||||||
|
MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox;
|
||||||
myMeta.tracks[trackNo].type = "video";
|
myMeta.tracks[trackNo].type = "video";
|
||||||
myMeta.tracks[trackNo].codec = "HEVC";
|
myMeta.tracks[trackNo].codec = "HEVC";
|
||||||
if (!myMeta.tracks[trackNo].width){
|
if (!myMeta.tracks[trackNo].width){
|
||||||
myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth();
|
myMeta.tracks[trackNo].width = vEntryBox.getWidth();
|
||||||
myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight();
|
myMeta.tracks[trackNo].height = vEntryBox.getHeight();
|
||||||
}
|
}
|
||||||
MP4::Box tmpBox2 = tmpBox;
|
MP4::Box initBox = vEntryBox.getCLAP();
|
||||||
MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP();
|
if (initBox.isType("hvcC")){
|
||||||
if (tmpContent.getType() == "hvcC"){
|
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
|
||||||
}
|
}
|
||||||
tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP();
|
initBox = vEntryBox.getPASP();
|
||||||
if (tmpContent.getType() == "hvcC"){
|
if (initBox.isType("hvcC")){
|
||||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
|
||||||
}
|
}
|
||||||
}else if (tmpType == "mp4a" || tmpType == "aac " || tmpType == "ac-3"){
|
}
|
||||||
|
if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
|
||||||
|
MP4::AudioSampleEntry & aEntryBox = (MP4::AudioSampleEntry&)sEntryBox;
|
||||||
myMeta.tracks[trackNo].type = "audio";
|
myMeta.tracks[trackNo].type = "audio";
|
||||||
myMeta.tracks[trackNo].channels = ((MP4::AudioSampleEntry&)tmpBox).getChannelCount();
|
myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount();
|
||||||
myMeta.tracks[trackNo].rate = (long long int)(((MP4::AudioSampleEntry&)tmpBox).getSampleRate());
|
myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate();
|
||||||
if (tmpType == "ac-3"){
|
|
||||||
|
if (sType == "ac-3"){
|
||||||
myMeta.tracks[trackNo].codec = "AC3";
|
myMeta.tracks[trackNo].codec = "AC3";
|
||||||
}else{
|
}else{
|
||||||
MP4::Box esds = ((MP4::AudioSampleEntry&)tmpBox).getCodecBox();
|
MP4::ESDS esdsBox = (MP4::ESDS&)(aEntryBox.getCodecBox());
|
||||||
myMeta.tracks[trackNo].codec = ((MP4::ESDS&)esds).getCodec();
|
myMeta.tracks[trackNo].codec = esdsBox.getCodec();
|
||||||
myMeta.tracks[trackNo].init = ((MP4::ESDS&)esds).getInitData();
|
myMeta.tracks[trackNo].init = esdsBox.getInitData();
|
||||||
}
|
}
|
||||||
myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file;
|
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
|
|
||||||
|
if (sType == "tx3g"){//plain text subtitles
|
||||||
myMeta.tracks[trackNo].type = "subtitle";
|
myMeta.tracks[trackNo].type = "subtitle";
|
||||||
myMeta.tracks[trackNo].codec = "TTXT";
|
myMeta.tracks[trackNo].codec = "TTXT";
|
||||||
}else{
|
|
||||||
myMeta.tracks.erase(trackNo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
MP4::STSS stssBox = stblBox.getChild<MP4::STSS>();
|
||||||
}//rof stbl
|
MP4::STTS sttsBox = stblBox.getChild<MP4::STTS>();
|
||||||
|
MP4::STSZ stszBox = stblBox.getChild<MP4::STSZ>();
|
||||||
|
MP4::STCO stcoBox = stblBox.getChild<MP4::STCO>();
|
||||||
|
MP4::CO64 co64Box = stblBox.getChild<MP4::CO64>();
|
||||||
|
MP4::STSC stscBox = stblBox.getChild<MP4::STSC>();
|
||||||
|
MP4::CTTS cttsBox = stblBox.getChild<MP4::CTTS>();//optional ctts box
|
||||||
|
|
||||||
|
bool stco64 = co64Box.isType("co64");
|
||||||
|
bool hasCTTS = cttsBox.isType("ctts");
|
||||||
|
|
||||||
uint64_t totaldur = 0;///\todo note: set this to begin time
|
uint64_t totaldur = 0;///\todo note: set this to begin time
|
||||||
mp4PartBpos BsetPart;
|
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;
|
uint64_t entryNo = 0;
|
||||||
long long unsigned int tempOffset;
|
uint64_t sampleNo = 0;
|
||||||
bool stcoIs64 = (stcoBox.getType() == "co64");
|
|
||||||
if (stcoIs64){
|
uint64_t stssIndex = 0;
|
||||||
tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(0);
|
uint64_t stcoIndex = 0;
|
||||||
}else{
|
uint64_t stscIndex = 0;
|
||||||
tempOffset = stcoBox.getChunkOffset(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);
|
||||||
}
|
}
|
||||||
long long unsigned int nextFirstChunk;
|
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount && stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
|
||||||
if (stscBox.getEntryCount() > 1){
|
if (BsetPart.keyframe){
|
||||||
nextFirstChunk = stscBox.getSTSCEntry(1).firstChunk - 1;
|
++stssIndex;
|
||||||
}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
|
//in bpos set
|
||||||
BsetPart.stcoNr = stcoIndex;
|
BsetPart.stcoNr = stcoIndex;
|
||||||
//bpos = chunkoffset[samplenr] in stco
|
//bpos = chunkoffset[samplenr] in stco
|
||||||
BsetPart.bpos = tempOffset;
|
BsetPart.bpos = tmpOffset;
|
||||||
fromSTCOinSTSC ++;
|
++fromSTCOinSTSC;
|
||||||
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk
|
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk
|
||||||
tempOffset += stszBox.getEntrySize(sampleIndex);
|
tmpOffset += stszBox.getEntrySize(stszIndex);
|
||||||
}else{
|
}else{
|
||||||
stcoIndex ++;
|
++stcoIndex;
|
||||||
fromSTCOinSTSC = 0;
|
fromSTCOinSTSC = 0;
|
||||||
if (stcoIs64){
|
tmpOffset = (stco64 ? co64Box.getChunkOffset(stcoIndex) : stcoBox.getChunkOffset(stcoIndex));
|
||||||
tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoIndex);
|
|
||||||
}else{
|
|
||||||
tempOffset = stcoBox.getChunkOffset(stcoIndex);
|
|
||||||
}
|
}
|
||||||
}
|
BsetPart.time = (totaldur*1000)/timescale;
|
||||||
//time = totaldur + stts[entry][sample]
|
totaldur += sttsEntry.sampleDelta;
|
||||||
BsetPart.time = (totaldur*1000)/timeScale;
|
|
||||||
totaldur += tempSTTS.sampleDelta;
|
|
||||||
sampleNo++;
|
sampleNo++;
|
||||||
if (sampleNo >= tempSTTS.sampleCount){
|
if (sampleNo >= sttsEntry.sampleCount){
|
||||||
entryNo++;
|
++entryNo;
|
||||||
sampleNo = 0;
|
sampleNo = 0;
|
||||||
if (entryNo < sttsBox.getEntryCount()){
|
if (entryNo < sttsBox.getEntryCount()){
|
||||||
tempSTTS = sttsBox.getSTTSEntry(entryNo);
|
sttsEntry = sttsBox.getSTTSEntry(entryNo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//set time offset
|
|
||||||
if (hasCTTS){
|
|
||||||
|
|
||||||
cttsEntry = cttsBox.getCTTSEntry(cttsIndex);
|
if (hasCTTS){
|
||||||
|
MP4::CTTSEntry cttsEntry = cttsBox.getCTTSEntry(cttsIndex);
|
||||||
cttsEntryRead++;
|
cttsEntryRead++;
|
||||||
if (cttsEntryRead >= cttsEntry.sampleCount){
|
if (cttsEntryRead >= cttsEntry.sampleCount){
|
||||||
cttsIndex++;
|
++cttsIndex;
|
||||||
cttsEntryRead = 0;
|
cttsEntryRead = 0;
|
||||||
}
|
}
|
||||||
BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000)/(int32_t)timeScale;
|
BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000)/timescale;
|
||||||
}else{
|
}else{
|
||||||
BsetPart.timeOffset = 0;
|
BsetPart.timeOffset = 0;
|
||||||
}
|
}
|
||||||
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(sampleIndex), BsetPart.bpos, BsetPart.keyframe);
|
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), 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
|
continue;
|
||||||
}//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
|
if (!MP4::skipBox(inFile)){//moving on to next box
|
||||||
DEBUG_MSG(DLVL_FAIL,"Error in Skipping box, exiting");
|
FAIL_MSG("Error in Skipping box, exiting");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}// if moov
|
}
|
||||||
}// end while file read
|
|
||||||
//for all in bpos set, find its data
|
|
||||||
clearerr(inFile);
|
clearerr(inFile);
|
||||||
|
|
||||||
//outputting dtsh file
|
//outputting dtsh file
|
||||||
|
@ -582,7 +440,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fseeko(inFile,curPart.bpos,SEEK_SET)){
|
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();
|
thisPacket.null();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -591,7 +449,7 @@ namespace Mist {
|
||||||
malSize = curPart.size;
|
malSize = curPart.size;
|
||||||
}
|
}
|
||||||
if (fread(data, curPart.size, 1, inFile)!=1){
|
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();
|
thisPacket.null();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,49 +9,41 @@ namespace Mist {
|
||||||
bool operator < (const mp4PartTime & rhs) const {
|
bool operator < (const mp4PartTime & rhs) const {
|
||||||
if (time < rhs.time){
|
if (time < rhs.time){
|
||||||
return true;
|
return true;
|
||||||
}else{
|
|
||||||
if (time == rhs.time){
|
|
||||||
if (trackID < rhs.trackID){
|
|
||||||
return true;
|
|
||||||
}else{
|
|
||||||
if (trackID == rhs.trackID && bpos < rhs.bpos){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (time > rhs.time){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
long long unsigned int time;
|
if (trackID < rhs.trackID){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (trackID == rhs.trackID && bpos < rhs.bpos);
|
||||||
|
}
|
||||||
|
uint64_t time;
|
||||||
int32_t offset;
|
int32_t offset;
|
||||||
unsigned int trackID;
|
size_t trackID;
|
||||||
long long unsigned int bpos;
|
uint64_t bpos;
|
||||||
unsigned int size;
|
uint32_t size;
|
||||||
long unsigned int index;
|
uint64_t index;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mp4PartBpos{
|
struct mp4PartBpos{
|
||||||
bool operator < (const mp4PartBpos & rhs) const {
|
bool operator < (const mp4PartBpos & rhs) const {
|
||||||
if (time < rhs.time){
|
if (time < rhs.time){
|
||||||
return true;
|
return true;
|
||||||
}else{
|
|
||||||
if (time == rhs.time){
|
|
||||||
if (trackID < rhs.trackID){
|
|
||||||
return true;
|
|
||||||
}else{
|
|
||||||
if (trackID == rhs.trackID && bpos < rhs.bpos){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (time > rhs.time){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
long long unsigned int time;
|
if (trackID < rhs.trackID){
|
||||||
long long unsigned int trackID;
|
return true;
|
||||||
long long unsigned int bpos;
|
}
|
||||||
long long unsigned int size;
|
return (trackID == rhs.trackID && bpos < rhs.bpos);
|
||||||
long long unsigned int stcoNr;
|
}
|
||||||
|
uint64_t time;
|
||||||
|
size_t trackID;
|
||||||
|
uint64_t bpos;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t stcoNr;
|
||||||
int32_t timeOffset;
|
int32_t timeOffset;
|
||||||
bool keyframe;
|
bool keyframe;
|
||||||
};
|
};
|
||||||
|
@ -61,26 +53,29 @@ namespace Mist {
|
||||||
mp4TrackHeader();
|
mp4TrackHeader();
|
||||||
void read(MP4::TRAK & trakBox);
|
void read(MP4::TRAK & trakBox);
|
||||||
MP4::STCO stcoBox;
|
MP4::STCO stcoBox;
|
||||||
|
MP4::CO64 co64Box;
|
||||||
MP4::STSZ stszBox;
|
MP4::STSZ stszBox;
|
||||||
MP4::STTS sttsBox;
|
MP4::STTS sttsBox;
|
||||||
bool hasCTTS;
|
bool hasCTTS;
|
||||||
MP4::CTTS cttsBox;
|
MP4::CTTS cttsBox;
|
||||||
MP4::STSC stscBox;
|
MP4::STSC stscBox;
|
||||||
long unsigned int timeScale;
|
uint64_t timeScale;
|
||||||
void getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, int32_t & timeOffset);
|
void getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset);
|
||||||
long unsigned int size();
|
uint64_t size();
|
||||||
private:
|
private:
|
||||||
bool initialised;
|
bool initialised;
|
||||||
//next variables are needed for the stsc/stco loop
|
//next variables are needed for the stsc/stco loop
|
||||||
long long unsigned int stscStart;
|
uint64_t stscStart;
|
||||||
long long unsigned int sampleIndex;
|
uint64_t sampleIndex;
|
||||||
//next variables are needed for the stts loop
|
//next variables are needed for the stts loop
|
||||||
long long unsigned deltaIndex;///< Index in STTS box
|
uint64_t deltaIndex;///< Index in STTS box
|
||||||
long long unsigned deltaPos;///< Sample counter for STTS box
|
uint64_t deltaPos;///< Sample counter for STTS box
|
||||||
long long unsigned deltaTotal;///< Total timestamp for STTS box
|
uint64_t deltaTotal;///< Total timestamp for STTS box
|
||||||
//for CTTS box loop
|
//for CTTS box loop
|
||||||
long long unsigned offsetIndex;///< Index in CTTS box
|
uint64_t offsetIndex;///< Index in CTTS box
|
||||||
long long unsigned offsetPos;///< Sample counter for CTTS box
|
uint64_t offsetPos;///< Sample counter for CTTS box
|
||||||
|
|
||||||
|
bool stco64;
|
||||||
};
|
};
|
||||||
|
|
||||||
class inputMP4 : public Input {
|
class inputMP4 : public Input {
|
||||||
|
@ -104,7 +99,7 @@ namespace Mist {
|
||||||
std::map <unsigned int, unsigned int> nextKeyframe;
|
std::map <unsigned int, unsigned int> nextKeyframe;
|
||||||
|
|
||||||
//these next two variables keep a buffer for reading from filepointer inFile;
|
//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
|
char* data;///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {}
|
||||||
|
@ -29,8 +31,8 @@ namespace Mist {
|
||||||
//capa["canRecord"].append("m3u");
|
//capa["canRecord"].append("m3u");
|
||||||
}
|
}
|
||||||
|
|
||||||
long long unsigned OutProgressiveMP4::estimateFileSize() {
|
uint64_t OutProgressiveMP4::estimateFileSize() {
|
||||||
long long unsigned retVal = 0;
|
uint64_t 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;
|
||||||
|
@ -39,8 +41,96 @@ namespace Mist {
|
||||||
return retVal * 1.1;
|
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<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 + (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...
|
///\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){
|
if (myMeta.live){
|
||||||
needsLookAhead = 420;
|
needsLookAhead = 420;
|
||||||
}
|
}
|
||||||
|
@ -75,11 +165,11 @@ namespace Mist {
|
||||||
//Then override it only when we are not sending a fragmented file
|
//Then override it only when we are not sending a fragmented file
|
||||||
if (!fragmented){
|
if (!fragmented){
|
||||||
//calculating longest duration
|
//calculating longest duration
|
||||||
long long unsigned firstms = 0xFFFFFFFFFFFFFFull;
|
uint64_t firstms = 0xFFFFFFFFFFFFFFull;
|
||||||
long long unsigned lastms = 0;
|
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);
|
||||||
}
|
}
|
||||||
mvhdBox.setDuration(lastms - firstms);
|
mvhdBox.setDuration(lastms - firstms);
|
||||||
}
|
}
|
||||||
|
@ -89,6 +179,8 @@ namespace Mist {
|
||||||
|
|
||||||
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;
|
||||||
|
@ -106,11 +198,7 @@ namespace Mist {
|
||||||
elstBox.setVersion(0);
|
elstBox.setVersion(0);
|
||||||
elstBox.setFlags(0);
|
elstBox.setFlags(0);
|
||||||
elstBox.setCount(1);
|
elstBox.setCount(1);
|
||||||
if (!fragmented){
|
elstBox.setSegmentDuration(0, fragmented ? -1 : tDuration);
|
||||||
elstBox.setSegmentDuration(0, thisTrack.lastms - thisTrack.firstms);
|
|
||||||
}else{
|
|
||||||
elstBox.setSegmentDuration(0, -1);
|
|
||||||
}
|
|
||||||
elstBox.setMediaTime(0, 0);
|
elstBox.setMediaTime(0, 0);
|
||||||
elstBox.setMediaRateInteger(0, 1);
|
elstBox.setMediaRateInteger(0, 1);
|
||||||
elstBox.setMediaRateFraction(0, 0);
|
elstBox.setMediaRateFraction(0, 0);
|
||||||
|
@ -118,10 +206,10 @@ 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);
|
||||||
if (fragmented){
|
if (fragmented){
|
||||||
mdhdBox.setDuration(-1);
|
mdhdBox.setDuration(-1);
|
||||||
}
|
}
|
||||||
|
@ -131,7 +219,7 @@ namespace Mist {
|
||||||
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") {
|
||||||
|
@ -151,7 +239,7 @@ namespace Mist {
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -164,30 +252,68 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
stblBox.setContent(stsdBox, stblOffset++);
|
stblBox.setContent(stsdBox, stblOffset++);
|
||||||
|
|
||||||
|
|
||||||
//Add STTS Box
|
//Add STTS Box
|
||||||
//note: STTS is empty when fragmented
|
//note: STTS is empty when fragmented
|
||||||
MP4::STTS sttsBox(0);
|
MP4::STTS sttsBox(0);
|
||||||
|
//Add STSZ Box
|
||||||
|
//note: STSZ is empty when fragmented
|
||||||
|
MP4::STSZ stszBox(0);
|
||||||
if (!fragmented) {
|
if (!fragmented) {
|
||||||
std::deque<std::pair<int, int> > sttsCounter;
|
|
||||||
for (unsigned int part = 0; part < thisTrack.parts.size(); ++part) {
|
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" && !fragmented) {
|
if (thisTrack.type == "video" && !fragmented) {
|
||||||
|
@ -209,63 +335,20 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
stblBox.setContent(stscBox, stblOffset++);
|
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<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
|
|
||||||
//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<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: 64bit boxes will never be used in fragmented
|
//note: 64bit boxes will never be used in fragmented
|
||||||
//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);
|
||||||
if (fragmented) {
|
if (fragmented) {
|
||||||
stcoBox.setEntryCount(0);
|
stcoBox.setEntryCount(0);
|
||||||
} else {
|
} else {
|
||||||
stcoBox.setChunkOffset(0, thisTrack.parts.size() - 1);
|
stcoBox.setChunkOffset(0, partCount - 1);
|
||||||
}
|
}
|
||||||
stblBox.setContent(stcoBox, stblOffset++);
|
stblBox.setContent(stcoBox, stblOffset++);
|
||||||
}
|
}
|
||||||
|
@ -289,95 +372,62 @@ namespace Mist {
|
||||||
moovBox.setContent(mvexBox, moovOffset++);
|
moovBox.setContent(mvexBox, moovOffset++);
|
||||||
}else{ //if we are making a non fragmented MP4 and there are parts
|
}else{ //if we are making a non fragmented MP4 and there are parts
|
||||||
//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 <long unsigned, MP4::CO64> checkCO64Boxes;
|
std::map <size_t, MP4::STCO> checkStcoBoxes;
|
||||||
//for all tracks
|
std::map <size_t, MP4::CO64> checkCO64Boxes;
|
||||||
for (unsigned int i = 1; i < moovBox.getContentCount(); i++) {
|
|
||||||
//10 lines to get the STCO box.
|
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
|
||||||
MP4::TRAK checkTrakBox;
|
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
|
||||||
MP4::Box checkMdiaBox;
|
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
|
||||||
MP4::Box checkTkhdBox;
|
MP4::STBL stblBox = trakIt->getChild<MP4::MDIA>().getChild<MP4::MINF>().getChild<MP4::STBL>();
|
||||||
MP4::MINF checkMinfBox;
|
if (useLargeBoxes){
|
||||||
MP4::STBL checkStblBox;
|
checkCO64Boxes.insert(std::pair<size_t, MP4::CO64>(tkhdBox.getTrackID(), stblBox.getChild<MP4::CO64>()));
|
||||||
//MP4::STCO checkStcoBox;
|
}else{
|
||||||
checkTrakBox = ((MP4::TRAK &)moovBox.getContent(i));
|
checkStcoBoxes.insert(std::pair<size_t, MP4::STCO>(tkhdBox.getTrackID(), stblBox.getChild<MP4::STCO>()));
|
||||||
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());
|
||||||
if (!fragmented) { //if we are making a non fragmented MP4 and there are parts
|
if (!fragmented) { //if we are making a non fragmented MP4 and there are parts
|
||||||
|
@ -392,6 +442,7 @@ namespace Mist {
|
||||||
header << (char)(0);
|
header << (char)(0);
|
||||||
}
|
}
|
||||||
size += header.str().size();
|
size += header.str().size();
|
||||||
|
INFO_MSG("Header %llu, file: %llu", header.str().size(), size);
|
||||||
if (fragmented) {
|
if (fragmented) {
|
||||||
realBaseOffset = header.str().size();
|
realBaseOffset = header.str().size();
|
||||||
}
|
}
|
||||||
|
@ -400,7 +451,7 @@ namespace Mist {
|
||||||
|
|
||||||
/// 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) {
|
||||||
|
@ -410,28 +461,33 @@ namespace Mist {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -442,10 +498,10 @@ namespace Mist {
|
||||||
/// 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);
|
||||||
|
@ -485,7 +541,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -505,14 +561,13 @@ 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::sendFragmentHeader() {
|
void OutProgressiveMP4::sendFragmentHeader() {
|
||||||
long unsigned int dataOffset = 0;
|
|
||||||
uint64_t mdatSize = 8;
|
uint64_t mdatSize = 8;
|
||||||
MP4::MOOF moofBox;
|
MP4::MOOF moofBox;
|
||||||
MP4::MFHD mfhdBox;
|
MP4::MFHD mfhdBox;
|
||||||
|
@ -530,24 +585,23 @@ namespace Mist {
|
||||||
for (uint32_t i = it->second.firstPart; i <= it->second.lastPart; i++) {
|
for (uint32_t i = it->second.firstPart; i <= it->second.lastPart; i++) {
|
||||||
keyPart temp;
|
keyPart temp;
|
||||||
temp.trackID = it->first;
|
temp.trackID = it->first;
|
||||||
temp.size = thisTrack.parts[i].getSize();
|
|
||||||
temp.duration = thisTrack.parts[i].getDuration();
|
|
||||||
temp.time = timeStamp;
|
temp.time = timeStamp;
|
||||||
timeStamp += temp.duration;
|
temp.index = i;
|
||||||
temp.endTime = timeStamp;
|
timeStamp += thisTrack.parts[temp.index].getDuration();
|
||||||
temp.timeOffset = thisTrack.parts[i].getOffset();//this will be changed soon, so now it is used for B-frame offset
|
|
||||||
trunOrder.insert(temp);
|
trunOrder.insert(temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//now all the parts have been sorted, we make a relative ByteOffset
|
//now all the parts have been sorted, we make a relative ByteOffset
|
||||||
long unsigned int relativeOffset = 0;
|
uint64_t relativeOffset = 0;
|
||||||
for (std::set<keyPart>::iterator it = trunOrder.begin(); it != trunOrder.end(); it++) {
|
for (std::set<keyPart>::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
|
//We have to make a copy, because altering the element inside the set would invalidate the iterators
|
||||||
keyPart temp = *it;
|
keyPart temp = *it;
|
||||||
temp.byteOffset = relativeOffset;
|
temp.byteOffset = relativeOffset;
|
||||||
relativeOffset += it->size;
|
relativeOffset += partSize;
|
||||||
DONTEVEN_MSG("Anticipating tid: %lu size: %lu", it->trackID, it->size);
|
DONTEVEN_MSG("Anticipating tid: %lu size: %lu", it->trackID, partSize);
|
||||||
sortSet.insert(temp);
|
sortSet.insert(temp);
|
||||||
}
|
}
|
||||||
trunOrder.clear();//erase the trunOrder set, to keep memory usage down
|
trunOrder.clear();//erase the trunOrder set, to keep memory usage down
|
||||||
|
@ -591,20 +645,24 @@ namespace Mist {
|
||||||
unsigned int trafOffset = 1;
|
unsigned int trafOffset = 1;
|
||||||
for (std::set<keyPart>::iterator trunIt = sortSet.begin(); trunIt != sortSet.end(); trunIt++) {
|
for (std::set<keyPart>::iterator trunIt = sortSet.begin(); trunIt != sortSet.end(); trunIt++) {
|
||||||
if (trunIt->trackID == tid) {
|
if (trunIt->trackID == tid) {
|
||||||
|
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;
|
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
|
//The value set here, will be updated afterwards to the correct value
|
||||||
trunBox.setDataOffset(trunIt->byteOffset);
|
trunBox.setDataOffset(trunIt->byteOffset);
|
||||||
trunBox.setFirstSampleFlags(MP4::isIPicture | (firstSample ? MP4::isKeySample : MP4::noKeySample));
|
trunBox.setFirstSampleFlags(MP4::isIPicture | (firstSample ? MP4::isKeySample : MP4::noKeySample));
|
||||||
firstSample = false;
|
firstSample = false;
|
||||||
|
|
||||||
mdatSize += trunIt->size;
|
mdatSize += partSize;
|
||||||
|
|
||||||
MP4::trunSampleInformation sampleInfo;
|
MP4::trunSampleInformation sampleInfo;
|
||||||
sampleInfo.sampleSize = trunIt->size;
|
sampleInfo.sampleSize = partSize;
|
||||||
sampleInfo.sampleDuration = trunIt->duration;
|
sampleInfo.sampleDuration = partDur;
|
||||||
if (trunIt->timeOffset) {
|
if (partOffset){
|
||||||
sampleInfo.sampleOffset = trunIt->timeOffset;
|
sampleInfo.sampleOffset = partOffset;
|
||||||
}
|
}
|
||||||
trunBox.setSampleInformation(sampleInfo, 0);
|
trunBox.setSampleInformation(sampleInfo, 0);
|
||||||
trafBox.setContent(trunBox, trafOffset++);
|
trafBox.setContent(trunBox, trafOffset++);
|
||||||
|
@ -706,9 +764,8 @@ namespace Mist {
|
||||||
//Check if the url contains .3gp --> if yes, we will send a 3gp header
|
//Check if the url contains .3gp --> if yes, we will send a 3gp header
|
||||||
sending3GP = (H.url.find(".3gp") != std::string::npos);
|
sending3GP = (H.url.find(".3gp") != std::string::npos);
|
||||||
|
|
||||||
//For storing the header.
|
fileSize = 0;
|
||||||
///\todo Do we really need this though?
|
uint64_t headerSize = mp4HeaderSize(fileSize, myMeta.live);
|
||||||
std::string headerData = DTSCMeta2MP4Header(fileSize, myMeta.live);
|
|
||||||
|
|
||||||
seekPoint = 0;
|
seekPoint = 0;
|
||||||
if (myMeta.live) {
|
if (myMeta.live) {
|
||||||
|
@ -726,21 +783,19 @@ 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 (!myMeta.live) {
|
if (!myMeta.live) {
|
||||||
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
|
||||||
H.setCORSHeaders();
|
H.setCORSHeaders();
|
||||||
H.SetHeader("Content-Type", "video/MP4"); //Send the correct content-type for MP4 files
|
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");
|
H.SetHeader("Accept-Ranges", "bytes, parsec");
|
||||||
}
|
}
|
||||||
if (rangeType != ' ') {
|
if (rangeType != ' ') {
|
||||||
|
@ -764,7 +819,7 @@ namespace Mist {
|
||||||
//H.StartResponse("206", "Partial content", HTTP_R, conn);
|
//H.StartResponse("206", "Partial content", HTTP_R, conn);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!myMeta.live) {
|
if (myMeta.vod) {
|
||||||
H.SetHeader("Content-Length", byteEnd - byteStart + 1);
|
H.SetHeader("Content-Length", byteEnd - byteStart + 1);
|
||||||
}
|
}
|
||||||
/// \todo Switch to chunked?
|
/// \todo Switch to chunked?
|
||||||
|
@ -772,12 +827,12 @@ 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, myMeta.live);
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
///Builds up a datastructure that allows for access in the fragment send header function
|
///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
|
///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...
|
///\todo See if we can use something more elegant than a member variable...
|
||||||
void OutProgressiveMP4::buildFragment() {
|
void OutProgressiveMP4::buildFragment() {
|
||||||
DTSC::Key & currKey = myMeta.tracks[vidTrack].getKey(getKeyForTime(vidTrack, thisPacket.getTime()));
|
|
||||||
uint64_t startms = thisPacket.getTime();
|
|
||||||
if (!needsLookAhead){
|
if (!needsLookAhead){
|
||||||
needsLookAhead = 1000;
|
needsLookAhead = 1000;
|
||||||
currentPartSet.clear();
|
currentPartSet.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DTSC::Key & currKey = myMeta.tracks[vidTrack].getKey(getKeyForTime(vidTrack, thisPacket.getTime()));
|
||||||
|
uint64_t startms = thisPacket.getTime();
|
||||||
uint64_t endms = startms + needsLookAhead;
|
uint64_t endms = startms + needsLookAhead;
|
||||||
bool missingSome = true;
|
bool missingSome = true;
|
||||||
|
|
||||||
|
@ -829,7 +885,7 @@ namespace Mist {
|
||||||
//Make sure we always look ahead at least a single frame
|
//Make sure we always look ahead at least a single frame
|
||||||
if (pDur > needsLookAhead){
|
if (pDur > needsLookAhead){
|
||||||
needsLookAhead = pDur;
|
needsLookAhead = pDur;
|
||||||
INFO_MSG("Slow frame! Increasing lookAhead to %lums", needsLookAhead);
|
INFO_MSG("Slow frame! Increasing lookAhead to %ums", needsLookAhead);
|
||||||
}
|
}
|
||||||
thisRange.lastPart = i;
|
thisRange.lastPart = i;
|
||||||
thisRange.lastTime = curMS;
|
thisRange.lastTime = curMS;
|
||||||
|
@ -840,7 +896,7 @@ namespace Mist {
|
||||||
endms = thisTrack.lastms;
|
endms = thisTrack.lastms;
|
||||||
if (needsLookAhead != endms - startms){
|
if (needsLookAhead != endms - startms){
|
||||||
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;
|
missingSome = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -869,7 +925,7 @@ namespace Mist {
|
||||||
// (sync delay = ~1s, minimum lookAhead is 420ms -> ~600ms extra needed, we use 2000ms to be safe)
|
// (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 (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){
|
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());
|
seek(mainTrk.keys.rbegin()->getTime());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -889,15 +945,17 @@ namespace Mist {
|
||||||
partListSent++;
|
partListSent++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((unsigned long)thisPacket.getTrackId() != sortSet.begin()->trackID || thisPacket.getTime() != sortSet.begin()->time || len != sortSet.begin()->size) {
|
keyPart thisPart = *sortSet.begin();
|
||||||
if (thisPacket.getTime() > sortSet.begin()->time || (unsigned long)thisPacket.getTrackId() > sortSet.begin()->trackID) {
|
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) {
|
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;
|
perfect = false;
|
||||||
myConn.close();
|
myConn.close();
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -909,35 +967,34 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -958,7 +1015,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (reSeek){
|
if (reSeek){
|
||||||
INFO_MSG("Increased initial lookAhead of %lums", needsLookAhead);
|
INFO_MSG("Increased initial lookAhead of %ums", needsLookAhead);
|
||||||
initialSeek();
|
initialSeek();
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
|
|
@ -9,29 +9,23 @@ namespace Mist {
|
||||||
if (time < rhs.time){
|
if (time < rhs.time){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (time == rhs.time){
|
if (time > rhs.time){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (trackID < rhs.trackID){
|
if (trackID < rhs.trackID){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (trackID == rhs.trackID){
|
return (trackID == rhs.trackID && index < rhs.index);
|
||||||
return index < rhs.index;
|
|
||||||
}
|
}
|
||||||
}
|
size_t trackID;
|
||||||
return false;
|
uint64_t time;
|
||||||
}
|
uint64_t byteOffset;//Stores relative bpos for fragmented MP4
|
||||||
long unsigned int trackID;
|
uint64_t index;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fragSet{
|
struct fragSet{
|
||||||
uint32_t firstPart;
|
uint64_t firstPart;
|
||||||
uint32_t lastPart;
|
uint64_t lastPart;
|
||||||
uint64_t firstTime;
|
uint64_t firstTime;
|
||||||
uint64_t lastTime;
|
uint64_t lastTime;
|
||||||
};
|
};
|
||||||
|
@ -40,42 +34,43 @@ 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, int fragmented = 0);
|
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
|
//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 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 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 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
|
||||||
|
|
||||||
//variables for fragmented
|
//variables for fragmented
|
||||||
int fragSeqNum;//the sequence number of the next keyframe/fragment when producing fragmented MP4's
|
size_t 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
|
size_t vidTrack;//the video track we use as fragmenting base
|
||||||
long long unsigned int realBaseOffset;//base offset for every moof packet
|
uint64_t realBaseOffset;//base offset for every moof packet
|
||||||
//from sendnext
|
//from sendnext
|
||||||
long unsigned int partListSent;//parts of current fragSet sent
|
size_t partListSent;//parts of current fragSet sent
|
||||||
long unsigned int partListLength;//amount of packets in current fragment
|
size_t partListLength;//amount of packets in current fragment
|
||||||
long int fragKeyNumberShift;//the difference between the first fragment Number and the first keyframe number
|
int64_t fragKeyNumberShift;//the difference between the first fragment Number and the first keyframe number
|
||||||
|
|
||||||
|
|
||||||
bool sending3GP;
|
bool sending3GP;
|
||||||
bool chromeWorkaround;
|
bool chromeWorkaround;
|
||||||
long long unsigned estimateFileSize();
|
uint64_t estimateFileSize();
|
||||||
|
|
||||||
//This is a dirty solution... but it prevents copying and copying and copying again
|
//This is a dirty solution... but it prevents copying and copying and copying again
|
||||||
std::map<long unsigned int, fragSet> currentPartSet;
|
std::map<size_t, fragSet> currentPartSet;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue