diff --git a/src/buffer/player.cpp b/src/buffer/player.cpp index a54b24e5..c55b5afe 100644 --- a/src/buffer/player.cpp +++ b/src/buffer/player.cpp @@ -12,6 +12,7 @@ #include #include #include +#include //under cygwin, recv blocks for ~15ms if no data is available. //This is a hack to keep performance decent with that bug present. @@ -250,9 +251,7 @@ int main(int argc, char** argv){ playing = 0; } if (playing == 0){ -#if DEBUG >= 4 - std::cerr << "Completed VoD request in MistPlayer (" << (Util::getMS() - bench) << "ms)" << std::endl; -#endif + DEBUG_MSG(DLVL_DEVEL, "Completed VoD request in MistPlayer (%d ms)", (Util::getMS() - bench)); pausemark["time"] = source.getJSON()["time"]; pausemark.sendTo(in_out); in_out.setBlocking(true); @@ -266,12 +265,5 @@ int main(int argc, char** argv){ } StatsSocket.close(); in_out.close(); -#if DEBUG >= 5 - if (Util::epoch() - lasttime < 60){ - std::cerr << "MistPlayer exited (disconnect)." << std::endl; - }else{ - std::cerr << "MistPlayer exited (command timeout)." << std::endl; - } -#endif return 0; } diff --git a/src/connectors/conn_http_progressive_mp4.cpp b/src/connectors/conn_http_progressive_mp4.cpp index df27026e..71e95623 100644 --- a/src/connectors/conn_http_progressive_mp4.cpp +++ b/src/connectors/conn_http_progressive_mp4.cpp @@ -16,32 +16,379 @@ #include #include #include +#include #include #include #include #include +#include ///\brief Holds everything unique to HTTP Connectors. namespace Connector_HTTP { + + struct keyPart{ + public: + bool operator < (const keyPart& rhs) const { + if (time < rhs.time){ + return true; + } + if (time == rhs.time){ + if (trackID < rhs.trackID){ + return true; + } + } + return false; + } + long unsigned int trackID; + long unsigned int size; + long long unsigned int time; + long long unsigned int endTime; + long unsigned int index; + }; + + std::string DTSCMeta2MP4Header(DTSC::Meta & metaData, std::set & tracks){ + std::stringstream header; + //ftyp box + /// \todo fill ftyp with non hardcoded values from file + MP4::FTYP ftypBox; + ftypBox.setMajorBrand(0x6D703431);//mp41 + ftypBox.setMinorVersion(0); + ftypBox.setCompatibleBrands(0x69736f6d,0); + ftypBox.setCompatibleBrands(0x69736f32,1); + ftypBox.setCompatibleBrands(0x61766331,2); + ftypBox.setCompatibleBrands(0x6D703431,3); + header << std::string(ftypBox.asBox(),ftypBox.boxedSize()); + + uint64_t mdatSize = 0; + //moov box + MP4::MOOV moovBox;{ + //calculating longest duration + long long int fileDuration = 0; + long long int firstms = -1; + long long int lastms = -1; + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++) { + if (lastms == -1 || lastms < metaData.tracks[*it].lastms){ + lastms = metaData.tracks[*it].lastms; + } + if (firstms == -1 || firstms > metaData.tracks[*it].firstms){ + firstms = metaData.tracks[*it].firstms; + } + } + fileDuration = lastms - firstms; + //MP4::MVHD mvhdBox(fileDuration); + MP4::MVHD mvhdBox; + mvhdBox.setVersion(0); + mvhdBox.setCreationTime(0); + mvhdBox.setModificationTime(0); + mvhdBox.setTimeScale(1000); + mvhdBox.setRate(0x10000); + mvhdBox.setDuration(fileDuration); + mvhdBox.setTrackID(0); + mvhdBox.setVolume(256); + mvhdBox.setMatrix(0x00010000,0); + mvhdBox.setMatrix(0,1); + mvhdBox.setMatrix(0,2); + mvhdBox.setMatrix(0,3); + mvhdBox.setMatrix(0x00010000,4); + mvhdBox.setMatrix(0,5); + mvhdBox.setMatrix(0,6); + mvhdBox.setMatrix(0,7); + mvhdBox.setMatrix(0x40000000,8); + moovBox.setContent(mvhdBox, 0); + } + {//start arbitrary track addition for headeri + int boxOffset = 1; + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++) { + int timescale = 0; + MP4::TRAK trakBox; + { + { + MP4::TKHD tkhdBox; + tkhdBox.setVersion(0); + tkhdBox.setFlags(15); + tkhdBox.setTrackID(*it); + tkhdBox.setDuration(metaData.tracks[*it].lastms - metaData.tracks[*it].firstms); + + if (metaData.tracks[*it].type == "video"){ + tkhdBox.setWidth(metaData.tracks[*it].width << 16); + tkhdBox.setHeight(metaData.tracks[*it].height << 16); + tkhdBox.setVolume(0); + }else{ + tkhdBox.setVolume(256); + tkhdBox.setAlternateGroup(1); + } + tkhdBox.setMatrix(0x00010000,0); + tkhdBox.setMatrix(0,1); + tkhdBox.setMatrix(0,2); + tkhdBox.setMatrix(0,3); + tkhdBox.setMatrix(0x00010000,4); + tkhdBox.setMatrix(0,5); + tkhdBox.setMatrix(0,6); + tkhdBox.setMatrix(0,7); + tkhdBox.setMatrix(0x40000000,8); + trakBox.setContent(tkhdBox, 0); + }{ + MP4::MDIA mdiaBox; + { + MP4::MDHD mdhdBox(0);/// \todo fix constructor mdhd in lib + mdhdBox.setCreationTime(0); + mdhdBox.setModificationTime(0); + //Calculating media time based on sampledelta. Probably cheating, but it works... + timescale = ((double)(42 * metaData.tracks[*it].parts.size() ) / (metaData.tracks[*it].lastms - metaData.tracks[*it].firstms)) * 1000; + mdhdBox.setTimeScale(timescale); + mdhdBox.setDuration((metaData.tracks[*it].lastms - metaData.tracks[*it].firstms) * ((double)timescale / 1000)); + mdiaBox.setContent(mdhdBox, 0); + }//MDHD box + { + MP4::HDLR hdlrBox;/// \todo fix constructor hdlr in lib + if (metaData.tracks[*it].type == "video"){ + hdlrBox.setHandlerType(0x76696465);//vide + }else if (metaData.tracks[*it].type == "audio"){ + hdlrBox.setHandlerType(0x736F756E);//soun + } + hdlrBox.setName(metaData.tracks[*it].getIdentifier()); + mdiaBox.setContent(hdlrBox, 1); + }//hdlr box + { + MP4::MINF minfBox; + if (metaData.tracks[*it].type== "video"){ + MP4::VMHD vmhdBox; + vmhdBox.setFlags(1); + minfBox.setContent(vmhdBox,0); + }else if (metaData.tracks[*it].type == "audio"){ + MP4::SMHD smhdBox; + minfBox.setContent(smhdBox,0); + }//type box + { + MP4::DINF dinfBox; + MP4::DREF drefBox;/// \todo fix constructor dref in lib + drefBox.setVersion(0); + MP4::URL urlBox; + urlBox.setFlags(1); + drefBox.setDataEntry(urlBox,0); + dinfBox.setContent(drefBox,0); + minfBox.setContent(dinfBox,1); + }//dinf box + { + MP4::STBL stblBox; + { + MP4::STSD stsdBox; + stsdBox.setVersion(0); + if (metaData.tracks[*it].type == "video"){//boxname = codec + MP4::VisualSampleEntry vse; + if (metaData.tracks[*it].codec == "H264"){ + vse.setCodec("avc1"); + } + vse.setDataReferenceIndex(1); + vse.setWidth(metaData.tracks[*it].width); + vse.setHeight(metaData.tracks[*it].height); + MP4::AVCC avccBox; + avccBox.setPayload(metaData.tracks[*it].init); + vse.setCLAP(avccBox); + stsdBox.setEntry(vse,0); + }else if(metaData.tracks[*it].type == "audio"){//boxname = codec + MP4::AudioSampleEntry ase; + if (metaData.tracks[*it].codec == "AAC"){ + ase.setCodec("mp4a"); + ase.setDataReferenceIndex(1); + } + ase.setSampleRate(metaData.tracks[*it].rate); + ase.setChannelCount(metaData.tracks[*it].channels); + ase.setSampleSize(metaData.tracks[*it].size); + //MP4::ESDS esdsBox(metaData.tracks[*it].init, metaData.tracks[*it].bps); + MP4::ESDS esdsBox; + + //outputting these values first, so malloc isn't called as often. + esdsBox.setESHeaderStartCodes(metaData.tracks[*it].init); + esdsBox.setSLValue(2); + + esdsBox.setESDescriptorTypeLength(32+metaData.tracks[*it].init.size()); + esdsBox.setESID(2); + esdsBox.setStreamPriority(0); + esdsBox.setDecoderConfigDescriptorTypeLength(18 + metaData.tracks[*it].init.size()); + esdsBox.setByteObjectTypeID(0x40); + esdsBox.setStreamType(5); + esdsBox.setReservedFlag(1); + esdsBox.setBufferSize(1250000); + esdsBox.setMaximumBitRate(10000000); + esdsBox.setAverageBitRate(metaData.tracks[*it].bps * 8); + esdsBox.setConfigDescriptorTypeLength(5); + esdsBox.setSLConfigDescriptorTypeTag(0x6); + esdsBox.setSLConfigExtendedDescriptorTypeTag(0x808080); + esdsBox.setSLDescriptorTypeLength(1); + ase.setCodecBox(esdsBox); + stsdBox.setEntry(ase,0); + } + stblBox.setContent(stsdBox,0); + }//stsd box + /// \todo update following stts lines + { + MP4::STTS sttsBox;//current version probably causes problems + sttsBox.setVersion(0); + MP4::STTSEntry newEntry; + newEntry.sampleCount = metaData.tracks[*it].parts.size(); + //42, Used as magic number for timescale calculation + newEntry.sampleDelta = 42; + sttsBox.setSTTSEntry(newEntry, 0); + stblBox.setContent(sttsBox,1); + }//stts box + if (metaData.tracks[*it].type == "video"){ + //STSS Box here + MP4::STSS stssBox; + stssBox.setVersion(0); + int tmpCount = 1; + int tmpItCount = 0; + for ( std::deque< DTSC::Key>::iterator tmpIt = metaData.tracks[*it].keys.begin(); tmpIt != metaData.tracks[*it].keys.end(); tmpIt ++) { + stssBox.setSampleNumber(tmpCount,tmpItCount); + tmpCount += tmpIt->getParts(); + tmpItCount ++; + } + stblBox.setContent(stssBox,2); + }//stss box + + int offset = (metaData.tracks[*it].type == "video"); + { + MP4::STSC stscBox; + stscBox.setVersion(0); + MP4::STSCEntry stscEntry; + stscEntry.firstChunk = 1; + stscEntry.samplesPerChunk = 1; + stscEntry.sampleDescriptionIndex = 1; + stscBox.setSTSCEntry(stscEntry, 0); + stblBox.setContent(stscBox,2 + offset); + }//stsc box + { + uint32_t total = 0; + MP4::STSZ stszBox; + stszBox.setVersion(0); + total = 0; + for (std::deque< DTSC::Part>::iterator partIt = metaData.tracks[*it].parts.begin(); partIt != metaData.tracks[*it].parts.end(); partIt ++) { + stszBox.setEntrySize(partIt->getSize(), total);//in bytes in file + total++; + } + stblBox.setContent(stszBox,3 + offset); + }//stsz box + //add STCO boxes here + { + MP4::STCO stcoBox; + stcoBox.setVersion(1); + //Inserting empty values on purpose here, will be fixed later. + if (metaData.tracks[*it].parts.size() != 0){ + stcoBox.setChunkOffset(0, metaData.tracks[*it].parts.size() - 1);//this inserts all empty entries at once + } + stblBox.setContent(stcoBox,4 + offset); + }//stco box + minfBox.setContent(stblBox,2); + }//stbl box + mdiaBox.setContent(minfBox, 2); + }//minf box + trakBox.setContent(mdiaBox, 1); + } + }//trak Box + moovBox.setContent(trakBox, boxOffset); + boxOffset++; + } + }//end arbitrary track addition + //initial offset length ftyp, length moov + 8 + unsigned long long int byteOffset = ftypBox.boxedSize() + moovBox.boxedSize() + 8; + //update all STCO from the following map; + std::map checkStcoBoxes; + //for all tracks + for (unsigned int i = 1; i < moovBox.getContentCount(); i++){ + //10 lines to get the STCO box. + MP4::TRAK checkTrakBox; + MP4::MDIA checkMdiaBox; + MP4::TKHD checkTkhdBox; + MP4::MINF checkMinfBox; + MP4::STBL checkStblBox; + //MP4::STCO checkStcoBox; + checkTrakBox = ((MP4::TRAK&)moovBox.getContent(i)); + for (unsigned int j = 0; j < checkTrakBox.getContentCount(); j++){ + if (checkTrakBox.getContent(j).isType("mdia")){ + checkMdiaBox = ((MP4::MDIA&)checkTrakBox.getContent(j)); + break; + } + if (checkTrakBox.getContent(j).isType("tkhd")){ + checkTkhdBox = ((MP4::TKHD&)checkTrakBox.getContent(j)); + } + } + for (unsigned int j = 0; j < checkMdiaBox.getContentCount(); j++){ + if (checkMdiaBox.getContent(j).isType("minf")){ + checkMinfBox = ((MP4::MINF&)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(checkTkhdBox.getTrackID(), ((MP4::STCO&)checkStblBox.getContent(j)) )); + break; + } + } + } + //inserting right values in the STCO box header + //total = 0; + long long unsigned int totalByteOffset = 0; + //Current values are actual byte offset without header-sized offset + std::set sortSet;//filling sortset for interleaving parts + for (std::set::iterator subIt = tracks.begin(); subIt != tracks.end(); subIt++) { + keyPart temp; + temp.trackID = *subIt; + temp.time = metaData.tracks[*subIt].firstms;//timeplace of frame + temp.endTime = metaData.tracks[*subIt].firstms + metaData.tracks[*subIt].parts[0].getDuration(); + temp.size = metaData.tracks[*subIt].parts[0].getSize();//bytesize of frame (alle parts all together) + temp.index = 0; + sortSet.insert(temp); + } + while (!sortSet.empty()){ + //setting the right STCO size in the STCO box + checkStcoBoxes[sortSet.begin()->trackID].setChunkOffset(totalByteOffset + byteOffset, sortSet.begin()->index); + totalByteOffset += sortSet.begin()->size; + //add keyPart to sortSet + keyPart temp; + temp.index = sortSet.begin()->index + 1; + temp.trackID = sortSet.begin()->trackID; + if(temp.index < metaData.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 + metaData.tracks[temp.trackID].parts[temp.index].getDuration(); + temp.size = metaData.tracks[temp.trackID].parts[temp.index].getSize();//bytesize of frame + sortSet.insert(temp); + } + //remove highest keyPart + sortSet.erase(sortSet.begin()); + } + //calculating the offset where the STCO box will be in the main MOOV box + //needed for probable optimise + mdatSize = totalByteOffset; + + header << std::string(moovBox.asBox(),moovBox.boxedSize()); + + header << (char)((mdatSize>>24) & 0x000000FF) << (char)((mdatSize>>16) & 0x000000FF) << (char)((mdatSize>>8) & 0x000000FF) << (char)(mdatSize & 0x000000FF) << "mdat"; + //end of header + + return header.str(); + } + ///\brief Main function for the HTTP Progressive Connector ///\param conn A socket describing the connection the client. ///\return The exit code of the connector. int progressiveConnector(Socket::Connection & conn){ - bool ready4data = false; //Set to true when streaming is to begin. DTSC::Stream Strm; //Incoming stream buffer. HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. bool inited = false;//Whether the stream is initialized Socket::Connection ss( -1);//The Stream Socket, used to connect to the desired stream. std::string streamname;//Will contain the name of the stream. + #if DEBUG >= DLVL_DEVEL + std::set sortSet;//filling sortset for interleaving parts + #endif - //MP4 specific variables - MP4::DTSC2MP4Converter Conv; - unsigned int lastStats = 0;//Indicates the last time that we have sent stats to the server socket. - int videoID = -1; - int audioID = -1; - while (conn.connected()){ //Only attempt to parse input when not yet init'ed. if ( !inited){ @@ -51,73 +398,72 @@ namespace Connector_HTTP { std::cout << "Received request: " << HTTP_R.getUrl() << std::endl; #endif conn.setHost(HTTP_R.GetHeader("X-Origin")); - //we assume the URL is the stream name with a 3 letter extension - streamname = HTTP_R.getUrl().substr(1); - size_t extDot = streamname.rfind('.'); - if (extDot != std::string::npos){ - streamname.resize(extDot); - } //strip the extension - ready4data = true; + streamname = HTTP_R.GetHeader("X-Stream"); + //we are ready, connect the socket! + ss = Util::Stream::getStream(streamname); + Strm.waitForMeta(ss); + + int videoID = -1; + int audioID = -1; + if (HTTP_R.GetVar("audio") != ""){ + audioID = JSON::Value(HTTP_R.GetVar("audio")).asInt(); + } + if (HTTP_R.GetVar("video") != ""){ + videoID = JSON::Value(HTTP_R.GetVar("video")).asInt(); + } + for (std::map::iterator it = Strm.metadata.tracks.begin(); it != Strm.metadata.tracks.end(); it++){ + if (videoID == -1 && it->second.type == "video" && it->second.codec == "H264"){ + videoID = it->first; + } + if (audioID == -1 && it->second.type == "audio" && it->second.codec == "AAC"){ + audioID = it->first; + } + } + + std::set tracks; + if (videoID > 0){tracks.insert(videoID);} + if (audioID > 0){tracks.insert(audioID);} + + HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers + HTTP_S.SetHeader("Content-Type", "video/MP4"); //Send the correct content-type for FLV files + HTTP_S.protocol = "HTTP/1.0"; + conn.SendNow(HTTP_S.BuildResponse("200", "OK")); //no SetBody = unknown length - this is intentional, we will stream the entire file + conn.SendNow(DTSCMeta2MP4Header(Strm.metadata, tracks));//SENDING MP4HEADER HTTP_R.Clean(); //clean for any possible next requests + {//using scope to have cmd not declared after action + std::stringstream cmd; + cmd << "t"; + for (std::set::iterator it = tracks.begin(); it != tracks.end(); it++) { + cmd << " " << *it; + } + cmd << "\ns 0\np\n"; + ss.SendNow(cmd.str()); + } + #if DEBUG >= DLVL_DEVEL + for (std::set::iterator subIt = tracks.begin(); subIt != tracks.end(); subIt++) { + keyPart temp; + temp.trackID = *subIt; + temp.time = Strm.metadata.tracks[*subIt].firstms;//timeplace of frame + temp.endTime = Strm.metadata.tracks[*subIt].firstms + Strm.metadata.tracks[*subIt].parts[0].getDuration(); + temp.size = Strm.metadata.tracks[*subIt].parts[0].getSize();//bytesize of frame (alle parts all together) + temp.index = 0; + sortSet.insert(temp); + } + #endif + if ( !ss.connected()){ + #if DEBUG >= 1 + fprintf(stderr, "Could not connect to server for %s!\n", streamname.c_str()); + #endif + ss.close(); + HTTP_S.Clean(); + HTTP_S.SetBody("No such stream is available on the system. Please try again.\n"); + conn.SendNow(HTTP_S.BuildResponse("404", "Not found")); + continue; + } + inited = true; } } - } - if (ready4data){ - if ( !inited){ - //we are ready, connect the socket! - ss = Util::Stream::getStream(streamname); - Strm.waitForMeta(ss); - //build header here and set iterator - HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers - HTTP_S.SetHeader("Content-Type", "video/MP4"); //Send the correct content-type for FLV files - HTTP_S.protocol = "HTTP/1.0"; - conn.SendNow(HTTP_S.BuildResponse("200", "OK")); //no SetBody = unknown length - this is intentional, we will stream the entire file - conn.SendNow(Conv.DTSCMeta2MP4Header(Strm.metadata));//SENDING MP4HEADER - {//using scope to have cmd not declared after action - std::stringstream cmd; - cmd << "t 1 2"; - cmd << "\ns 0"; - cmd << "\np\n"; - ss.SendNow(cmd.str()); - } - if ( !ss.connected()){ -#if DEBUG >= 1 - fprintf(stderr, "Could not connect to server for %s!\n", streamname.c_str()); -#endif - ss.close(); - HTTP_S.Clean(); - HTTP_S.SetBody("No such stream is available on the system. Please try again.\n"); - conn.SendNow(HTTP_S.BuildResponse("404", "Not found")); - ready4data = false; - continue; - } - //wait until we have a header - while ( !Strm.metadata && ss.connected()){ - if (ss.spool()){ - Strm.parsePacket(ss.Received()); //read the metadata - }else{ - Util::sleep(5); - } - } - int byterate = 0; - for (std::map::iterator it = Strm.metadata.tracks.begin(); it != Strm.metadata.tracks.end(); it++){ - if (videoID == -1 && it->second.type == "video"){ - videoID = it->second.trackID; - } - if (audioID == -1 && it->second.type == "audio"){ - audioID = it->second.trackID; - } - } - if (videoID != -1){ - byterate += Strm.metadata.tracks[videoID].bps; - } - if (audioID != -1){ - byterate += Strm.metadata.tracks[audioID].bps; - } - if ( !byterate){byterate = 1;} - - inited = true; - } + }else{ unsigned int now = Util::epoch(); if (now != lastStats){ lastStats = now; @@ -128,18 +474,34 @@ namespace Connector_HTTP { if (Strm.lastType() == DTSC::PAUSEMARK){ conn.close(); }else if(Strm.lastType() == DTSC::AUDIO || Strm.lastType() == DTSC::VIDEO){ - //parse DTSC to MP4 here + #if DEBUG >= DLVL_DEVEL + if (!sortSet.empty()){ + if (sortSet.begin()->trackID != Strm.getPacket()["trackid"].asInt() || sortSet.begin()->time != Strm.getPacket()["time"].asInt()){ + DEBUG_MSG(DLVL_DEVEL, "Set[%d, %d] => Real[%d, %d]", sortSet.begin()->trackID, sortSet.begin()->time, Strm.getPacket()["trackid"].asInt(), Strm.getPacket()["time"].asInt()); + } + //add keyPart to sortSet + keyPart temp; + temp.index = sortSet.begin()->index + 1; + temp.trackID = sortSet.begin()->trackID; + if(temp.index < Strm.metadata.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 + Strm.metadata.tracks[temp.trackID].parts[temp.index].getDuration(); + temp.size = Strm.metadata.tracks[temp.trackID].parts[temp.index].getSize();//bytesize of frame + sortSet.insert(temp); + } + //remove highest keyPart + sortSet.erase(sortSet.begin()); + } + #endif conn.SendNow(Strm.lastData());//send out and clear Converter buffer } if (Strm.lastType() == DTSC::INVALID){ - #if DEBUG >= 3 - fprintf(stderr, "Invalid packet received - closing connection.\n"); - #endif + DEBUG_MSG(DLVL_FAIL, "Invalid packet received - closing connection"); conn.close(); } } }else{ - Util::sleep(1); + Util::sleep(10); } if ( !ss.connected()){ break; diff --git a/src/converters/ogg2dtsc.cpp b/src/converters/ogg2dtsc.cpp index d68968b9..83147086 100644 --- a/src/converters/ogg2dtsc.cpp +++ b/src/converters/ogg2dtsc.cpp @@ -125,7 +125,6 @@ namespace Converters{ //if we are in the vicinity of a new keyframe if (trackData[sNum].idHeader.parseGranuleUpper(trackData[sNum].lastGran) != trackData[sNum].idHeader.parseGranuleUpper(temp)){ //try to mark right - long long unsigned int temper = trackData[sNum].idHeader.parseGranuleUpper(temp) - trackData[sNum].idHeader.parseGranuleUpper(trackData[sNum].lastGran); DTSCOut["keyframe"] = 1; trackData[sNum].lastGran = temp; }else{