Converted MP4 input to use URIReader
This commit is contained in:
		
							parent
							
								
									b210b4f5af
								
							
						
					
					
						commit
						01a2ff54ed
					
				
					 2 changed files with 302 additions and 239 deletions
				
			
		| 
						 | 
				
			
			@ -159,11 +159,14 @@ namespace Mist{
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  inputMP4::inputMP4(Util::Config *cfg) : Input(cfg){
 | 
			
		||||
    malSize = 4; // initialise data read buffer to 0;
 | 
			
		||||
    data = (char *)malloc(malSize);
 | 
			
		||||
    capa["name"] = "MP4";
 | 
			
		||||
    capa["desc"] = "This input allows streaming of MP4 files as Video on Demand.";
 | 
			
		||||
    capa["source_match"] = "/*.mp4";
 | 
			
		||||
    capa["source_match"].append("/*.mp4");
 | 
			
		||||
    capa["source_match"].append("http://*.mp4");
 | 
			
		||||
    capa["source_match"].append("https://*.mp4");
 | 
			
		||||
    capa["source_match"].append("s3+http://*.mp4");
 | 
			
		||||
    capa["source_match"].append("s3+https://*.mp4");
 | 
			
		||||
    capa["source_match"].append("mp4:*");
 | 
			
		||||
    capa["source_file"] = "$source";
 | 
			
		||||
    capa["priority"] = 9;
 | 
			
		||||
    capa["codecs"][0u][0u].append("HEVC");
 | 
			
		||||
| 
						 | 
				
			
			@ -173,10 +176,9 @@ namespace Mist{
 | 
			
		|||
    capa["codecs"][0u][1u].append("AAC");
 | 
			
		||||
    capa["codecs"][0u][1u].append("AC3");
 | 
			
		||||
    capa["codecs"][0u][1u].append("MP3");
 | 
			
		||||
    readPos = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inputMP4::~inputMP4(){free(data);}
 | 
			
		||||
 | 
			
		||||
  bool inputMP4::checkArguments(){
 | 
			
		||||
    if (config->getString("input") == "-"){
 | 
			
		||||
      std::cerr << "Input from stdin not yet supported" << std::endl;
 | 
			
		||||
| 
						 | 
				
			
			@ -199,255 +201,292 @@ namespace Mist{
 | 
			
		|||
 | 
			
		||||
  bool inputMP4::preRun(){
 | 
			
		||||
    // open File
 | 
			
		||||
    inFile = fopen(config->getString("input").c_str(), "r");
 | 
			
		||||
    std::string inUrl = config->getString("input");
 | 
			
		||||
    if (inUrl.size() > 4 && inUrl.substr(0, 4) == "mp4:"){inUrl.erase(0, 4);}
 | 
			
		||||
    inFile.open(inUrl);
 | 
			
		||||
    if (!inFile){return false;}
 | 
			
		||||
    if (!inFile.isSeekable()){
 | 
			
		||||
      FAIL_MSG("MP4 input only supports seekable data sources, for now, and this source is not seekable: %s", config->getString("input").c_str());
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void inputMP4::dataCallback(const char *ptr, size_t size){readBuffer.append(ptr, size);}
 | 
			
		||||
 | 
			
		||||
  bool inputMP4::readHeader(){
 | 
			
		||||
    if (!inFile){
 | 
			
		||||
      INFO_MSG("inFile failed!");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    bool hasMoov = false;
 | 
			
		||||
    readBuffer.truncate(0);
 | 
			
		||||
    readPos = 0;
 | 
			
		||||
 | 
			
		||||
    // first we get the necessary header parts
 | 
			
		||||
    size_t tNumber = 0;
 | 
			
		||||
    while (!feof(inFile)){
 | 
			
		||||
      std::string boxType = MP4::readBoxType(inFile);
 | 
			
		||||
      if (boxType == "erro"){break;}
 | 
			
		||||
    activityCounter = Util::bootSecs();
 | 
			
		||||
    while (inFile && keepRunning()){
 | 
			
		||||
      //Read box header if needed
 | 
			
		||||
      while (readBuffer.size() < 16 && inFile && keepRunning()){inFile.readSome(16, *this);}
 | 
			
		||||
      //Failed? Abort.
 | 
			
		||||
      if (readBuffer.size() < 16){
 | 
			
		||||
        FAIL_MSG("Could not read box header from input!");
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      //Box type is always on bytes 5-8 from the start of a box
 | 
			
		||||
      std::string boxType = std::string(readBuffer+4, 4);
 | 
			
		||||
      uint64_t boxSize = MP4::calcBoxSize(readBuffer);
 | 
			
		||||
      if (boxType == "moov"){
 | 
			
		||||
        MP4::MOOV moovBox;
 | 
			
		||||
        moovBox.read(inFile);
 | 
			
		||||
        // for all box in moov
 | 
			
		||||
        while (readBuffer.size() < boxSize && inFile && keepRunning()){inFile.readSome(boxSize-readBuffer.size(), *this);}
 | 
			
		||||
        if (readBuffer.size() < boxSize){
 | 
			
		||||
          FAIL_MSG("Could not read entire MOOV box into memory");
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        MP4::Box moovBox(readBuffer, false);
 | 
			
		||||
 | 
			
		||||
        std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
 | 
			
		||||
        // for all box in moov
 | 
			
		||||
        std::deque<MP4::TRAK> trak = ((MP4::MOOV*)&moovBox)->getChildren<MP4::TRAK>();
 | 
			
		||||
        for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
 | 
			
		||||
          trackHeaders.push_back(mp4TrackHeader());
 | 
			
		||||
          trackHeaders.rbegin()->read(*trakIt);
 | 
			
		||||
        }
 | 
			
		||||
        continue;
 | 
			
		||||
        hasMoov = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (!MP4::skipBox(inFile)){// moving on to next box
 | 
			
		||||
        FAIL_MSG("Error in skipping box, exiting");
 | 
			
		||||
        return false;
 | 
			
		||||
      activityCounter = Util::bootSecs();
 | 
			
		||||
      //Skip to next box
 | 
			
		||||
      if (readBuffer.size() > boxSize){
 | 
			
		||||
        readBuffer.shift(boxSize);
 | 
			
		||||
        readPos += boxSize;
 | 
			
		||||
      }else{
 | 
			
		||||
        readBuffer.truncate(0);
 | 
			
		||||
        if (!inFile.seek(readPos + boxSize)){
 | 
			
		||||
          FAIL_MSG("Seek to %" PRIu64 " failed! Aborting load", readPos+boxSize);
 | 
			
		||||
        }
 | 
			
		||||
        readPos = inFile.getPos();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    fseeko(inFile, 0, SEEK_SET);
 | 
			
		||||
 | 
			
		||||
    // See whether a separate header file exists.
 | 
			
		||||
    if (readExistingHeader()){return true;}
 | 
			
		||||
    HIGH_MSG("Not read existing header");
 | 
			
		||||
    if (readExistingHeader()){
 | 
			
		||||
      bps = 0;
 | 
			
		||||
      std::set<size_t> tracks = M.getValidTracks();
 | 
			
		||||
      for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){bps += M.getBps(*it);}
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    INFO_MSG("Not read existing header");
 | 
			
		||||
 | 
			
		||||
    meta.reInit(isSingular() ? streamName : "");
 | 
			
		||||
    if (!hasMoov){
 | 
			
		||||
      FAIL_MSG("No MOOV box found; aborting header creation!");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tNumber = 0;
 | 
			
		||||
    // Create header file from MP4 data
 | 
			
		||||
    while (!feof(inFile)){
 | 
			
		||||
      std::string boxType = MP4::readBoxType(inFile);
 | 
			
		||||
      if (boxType == "erro"){break;}
 | 
			
		||||
      if (boxType == "moov"){
 | 
			
		||||
        MP4::MOOV moovBox;
 | 
			
		||||
        moovBox.read(inFile);
 | 
			
		||||
    MP4::Box moovBox(readBuffer, false);
 | 
			
		||||
 | 
			
		||||
        std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
 | 
			
		||||
        HIGH_MSG("Obtained %zu trak Boxes", trak.size());
 | 
			
		||||
    std::deque<MP4::TRAK> trak = ((MP4::MOOV*)&moovBox)->getChildren<MP4::TRAK>();
 | 
			
		||||
    HIGH_MSG("Obtained %zu trak Boxes", trak.size());
 | 
			
		||||
 | 
			
		||||
        for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
 | 
			
		||||
          MP4::MDIA mdiaBox = trakIt->getChild<MP4::MDIA>();
 | 
			
		||||
    for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
 | 
			
		||||
      MP4::MDIA mdiaBox = trakIt->getChild<MP4::MDIA>();
 | 
			
		||||
 | 
			
		||||
          std::string hdlrType = mdiaBox.getChild<MP4::HDLR>().getHandlerType();
 | 
			
		||||
          if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "sbtl"){
 | 
			
		||||
            INFO_MSG("Unsupported handler: %s", hdlrType.c_str());
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          tNumber = meta.addTrack();
 | 
			
		||||
 | 
			
		||||
          MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
 | 
			
		||||
          if (tkhdBox.getWidth() > 0){
 | 
			
		||||
            meta.setWidth(tNumber, tkhdBox.getWidth());
 | 
			
		||||
            meta.setHeight(tNumber, tkhdBox.getHeight());
 | 
			
		||||
          }
 | 
			
		||||
          meta.setID(tNumber, tkhdBox.getTrackID());
 | 
			
		||||
 | 
			
		||||
          MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
 | 
			
		||||
          uint64_t timescale = mdhdBox.getTimeScale();
 | 
			
		||||
          meta.setLang(tNumber, mdhdBox.getLanguage());
 | 
			
		||||
 | 
			
		||||
          MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
 | 
			
		||||
 | 
			
		||||
          MP4::STSD stsdBox = stblBox.getChild<MP4::STSD>();
 | 
			
		||||
          MP4::Box sEntryBox = stsdBox.getEntry(0);
 | 
			
		||||
          std::string sType = sEntryBox.getType();
 | 
			
		||||
          HIGH_MSG("Found track %zu of type %s", tNumber, sType.c_str());
 | 
			
		||||
 | 
			
		||||
          if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
 | 
			
		||||
            MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
 | 
			
		||||
            meta.setType(tNumber, "video");
 | 
			
		||||
            meta.setCodec(tNumber, "H264");
 | 
			
		||||
            if (!meta.getWidth(tNumber)){
 | 
			
		||||
              meta.setWidth(tNumber, vEntryBox.getWidth());
 | 
			
		||||
              meta.setHeight(tNumber, vEntryBox.getHeight());
 | 
			
		||||
            }
 | 
			
		||||
            MP4::Box initBox = vEntryBox.getCLAP();
 | 
			
		||||
            if (initBox.isType("avcC")){
 | 
			
		||||
              meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
            }
 | 
			
		||||
            initBox = vEntryBox.getPASP();
 | 
			
		||||
            if (initBox.isType("avcC")){
 | 
			
		||||
              meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
            }
 | 
			
		||||
            /// this is a hacky way around invalid FLV data (since it gets ignored nearly
 | 
			
		||||
            /// everywhere, but we do need correct data...
 | 
			
		||||
            if (!meta.getWidth(tNumber)){
 | 
			
		||||
              h264::sequenceParameterSet sps;
 | 
			
		||||
              sps.fromDTSCInit(meta.getInit(tNumber));
 | 
			
		||||
              h264::SPSMeta spsChar = sps.getCharacteristics();
 | 
			
		||||
              meta.setWidth(tNumber, spsChar.width);
 | 
			
		||||
              meta.setHeight(tNumber, spsChar.height);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          if (sType == "hev1" || sType == "hvc1"){
 | 
			
		||||
            MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
 | 
			
		||||
            meta.setType(tNumber, "video");
 | 
			
		||||
            meta.setCodec(tNumber, "HEVC");
 | 
			
		||||
            if (!meta.getWidth(tNumber)){
 | 
			
		||||
              meta.setWidth(tNumber, vEntryBox.getWidth());
 | 
			
		||||
              meta.setHeight(tNumber, vEntryBox.getHeight());
 | 
			
		||||
            }
 | 
			
		||||
            MP4::Box initBox = vEntryBox.getCLAP();
 | 
			
		||||
            if (initBox.isType("hvcC")){
 | 
			
		||||
              meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
            }
 | 
			
		||||
            initBox = vEntryBox.getPASP();
 | 
			
		||||
            if (initBox.isType("hvcC")){
 | 
			
		||||
              meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
 | 
			
		||||
            MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox;
 | 
			
		||||
            meta.setType(tNumber, "audio");
 | 
			
		||||
            meta.setChannels(tNumber, aEntryBox.getChannelCount());
 | 
			
		||||
            meta.setRate(tNumber, aEntryBox.getSampleRate());
 | 
			
		||||
 | 
			
		||||
            if (sType == "ac-3"){
 | 
			
		||||
              meta.setCodec(tNumber, "AC3");
 | 
			
		||||
            }else{
 | 
			
		||||
              MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox());
 | 
			
		||||
              meta.setCodec(tNumber, esdsBox.getCodec());
 | 
			
		||||
              meta.setInit(tNumber, esdsBox.getInitData());
 | 
			
		||||
            }
 | 
			
		||||
            meta.setSize(tNumber, 16); ///\todo this might be nice to calculate from mp4 file;
 | 
			
		||||
          }
 | 
			
		||||
          if (sType == "tx3g"){// plain text subtitles
 | 
			
		||||
            meta.setType(tNumber, "meta");
 | 
			
		||||
            meta.setCodec(tNumber, "subtitle");
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          MP4::STSS stssBox = stblBox.getChild<MP4::STSS>();
 | 
			
		||||
          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
 | 
			
		||||
          mp4PartBpos BsetPart;
 | 
			
		||||
 | 
			
		||||
          uint64_t entryNo = 0;
 | 
			
		||||
          uint64_t sampleNo = 0;
 | 
			
		||||
 | 
			
		||||
          uint64_t stssIndex = 0;
 | 
			
		||||
          uint64_t stcoIndex = 0;
 | 
			
		||||
          uint64_t stscIndex = 0;
 | 
			
		||||
          uint64_t cttsIndex = 0;     // current ctts Index we are reading
 | 
			
		||||
          uint64_t cttsEntryRead = 0; // current part of ctts we are reading
 | 
			
		||||
 | 
			
		||||
          uint64_t stssCount = stssBox.getEntryCount();
 | 
			
		||||
          uint64_t stscCount = stscBox.getEntryCount();
 | 
			
		||||
          uint64_t stszCount = stszBox.getSampleCount();
 | 
			
		||||
          uint64_t stcoCount = (stco64 ? co64Box.getEntryCount() : stcoBox.getEntryCount());
 | 
			
		||||
 | 
			
		||||
          MP4::STTSEntry sttsEntry = sttsBox.getSTTSEntry(0);
 | 
			
		||||
 | 
			
		||||
          uint32_t fromSTCOinSTSC = 0;
 | 
			
		||||
          uint64_t tmpOffset = (stco64 ? co64Box.getChunkOffset(0) : stcoBox.getChunkOffset(0));
 | 
			
		||||
 | 
			
		||||
          uint64_t nextFirstChunk = (stscCount > 1 ? stscBox.getSTSCEntry(1).firstChunk - 1 : stcoCount);
 | 
			
		||||
 | 
			
		||||
          for (uint64_t stszIndex = 0; stszIndex < stszCount; ++stszIndex){
 | 
			
		||||
            if (stcoIndex >= nextFirstChunk){
 | 
			
		||||
              ++stscIndex;
 | 
			
		||||
              nextFirstChunk =
 | 
			
		||||
                  (stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
 | 
			
		||||
            }
 | 
			
		||||
            BsetPart.keyframe = (meta.getType(tNumber) == "video" && stssIndex < stssCount &&
 | 
			
		||||
                                 stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
 | 
			
		||||
            if (BsetPart.keyframe){++stssIndex;}
 | 
			
		||||
            // in bpos set
 | 
			
		||||
            BsetPart.stcoNr = stcoIndex;
 | 
			
		||||
            // bpos = chunkoffset[samplenr] in stco
 | 
			
		||||
            BsetPart.bpos = tmpOffset;
 | 
			
		||||
            ++fromSTCOinSTSC;
 | 
			
		||||
            if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){// as long as we are still in this chunk
 | 
			
		||||
              tmpOffset += stszBox.getEntrySize(stszIndex);
 | 
			
		||||
            }else{
 | 
			
		||||
              ++stcoIndex;
 | 
			
		||||
              fromSTCOinSTSC = 0;
 | 
			
		||||
              tmpOffset = (stco64 ? co64Box.getChunkOffset(stcoIndex) : stcoBox.getChunkOffset(stcoIndex));
 | 
			
		||||
            }
 | 
			
		||||
            BsetPart.time = (totaldur * 1000) / timescale;
 | 
			
		||||
            totaldur += sttsEntry.sampleDelta;
 | 
			
		||||
            sampleNo++;
 | 
			
		||||
            if (sampleNo >= sttsEntry.sampleCount){
 | 
			
		||||
              ++entryNo;
 | 
			
		||||
              sampleNo = 0;
 | 
			
		||||
              if (entryNo < sttsBox.getEntryCount()){sttsEntry = sttsBox.getSTTSEntry(entryNo);}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (hasCTTS){
 | 
			
		||||
              MP4::CTTSEntry cttsEntry = cttsBox.getCTTSEntry(cttsIndex);
 | 
			
		||||
              cttsEntryRead++;
 | 
			
		||||
              if (cttsEntryRead >= cttsEntry.sampleCount){
 | 
			
		||||
                ++cttsIndex;
 | 
			
		||||
                cttsEntryRead = 0;
 | 
			
		||||
              }
 | 
			
		||||
              BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000) / timescale;
 | 
			
		||||
            }else{
 | 
			
		||||
              BsetPart.timeOffset = 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (sType == "tx3g"){
 | 
			
		||||
              if (stszBox.getEntrySize(stszIndex) <= 2 && false){
 | 
			
		||||
                FAIL_MSG("size <=2");
 | 
			
		||||
              }else{
 | 
			
		||||
                long long packSendSize = 0;
 | 
			
		||||
                packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
 | 
			
		||||
                               stszBox.getEntrySize(stszIndex) + 11 - 2 + 19;
 | 
			
		||||
                meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
 | 
			
		||||
                            stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
 | 
			
		||||
              }
 | 
			
		||||
            }else{
 | 
			
		||||
              meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
 | 
			
		||||
                          stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      std::string hdlrType = mdiaBox.getChild<MP4::HDLR>().getHandlerType();
 | 
			
		||||
      if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "sbtl"){
 | 
			
		||||
        INFO_MSG("Unsupported handler: %s", hdlrType.c_str());
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (!MP4::skipBox(inFile)){// moving on to next box
 | 
			
		||||
        FAIL_MSG("Error in Skipping box, exiting");
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
      tNumber = meta.addTrack();
 | 
			
		||||
 | 
			
		||||
      MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
 | 
			
		||||
      if (tkhdBox.getWidth() > 0){
 | 
			
		||||
        meta.setWidth(tNumber, tkhdBox.getWidth());
 | 
			
		||||
        meta.setHeight(tNumber, tkhdBox.getHeight());
 | 
			
		||||
      }
 | 
			
		||||
      meta.setID(tNumber, tkhdBox.getTrackID());
 | 
			
		||||
 | 
			
		||||
      MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
 | 
			
		||||
      uint64_t timescale = mdhdBox.getTimeScale();
 | 
			
		||||
      meta.setLang(tNumber, mdhdBox.getLanguage());
 | 
			
		||||
 | 
			
		||||
      MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
 | 
			
		||||
 | 
			
		||||
      MP4::STSD stsdBox = stblBox.getChild<MP4::STSD>();
 | 
			
		||||
      MP4::Box sEntryBox = stsdBox.getEntry(0);
 | 
			
		||||
      std::string sType = sEntryBox.getType();
 | 
			
		||||
      HIGH_MSG("Found track %zu of type %s", tNumber, sType.c_str());
 | 
			
		||||
 | 
			
		||||
      if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
 | 
			
		||||
        MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
 | 
			
		||||
        meta.setType(tNumber, "video");
 | 
			
		||||
        meta.setCodec(tNumber, "H264");
 | 
			
		||||
        if (!meta.getWidth(tNumber)){
 | 
			
		||||
          meta.setWidth(tNumber, vEntryBox.getWidth());
 | 
			
		||||
          meta.setHeight(tNumber, vEntryBox.getHeight());
 | 
			
		||||
        }
 | 
			
		||||
        MP4::Box initBox = vEntryBox.getCLAP();
 | 
			
		||||
        if (initBox.isType("avcC")){
 | 
			
		||||
          meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
        }
 | 
			
		||||
        initBox = vEntryBox.getPASP();
 | 
			
		||||
        if (initBox.isType("avcC")){
 | 
			
		||||
          meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
        }
 | 
			
		||||
        /// this is a hacky way around invalid FLV data (since it gets ignored nearly
 | 
			
		||||
        /// everywhere, but we do need correct data...
 | 
			
		||||
        if (!meta.getWidth(tNumber)){
 | 
			
		||||
          h264::sequenceParameterSet sps;
 | 
			
		||||
          sps.fromDTSCInit(meta.getInit(tNumber));
 | 
			
		||||
          h264::SPSMeta spsChar = sps.getCharacteristics();
 | 
			
		||||
          meta.setWidth(tNumber, spsChar.width);
 | 
			
		||||
          meta.setHeight(tNumber, spsChar.height);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (sType == "hev1" || sType == "hvc1"){
 | 
			
		||||
        MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
 | 
			
		||||
        meta.setType(tNumber, "video");
 | 
			
		||||
        meta.setCodec(tNumber, "HEVC");
 | 
			
		||||
        if (!meta.getWidth(tNumber)){
 | 
			
		||||
          meta.setWidth(tNumber, vEntryBox.getWidth());
 | 
			
		||||
          meta.setHeight(tNumber, vEntryBox.getHeight());
 | 
			
		||||
        }
 | 
			
		||||
        MP4::Box initBox = vEntryBox.getCLAP();
 | 
			
		||||
        if (initBox.isType("hvcC")){
 | 
			
		||||
          meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
        }
 | 
			
		||||
        initBox = vEntryBox.getPASP();
 | 
			
		||||
        if (initBox.isType("hvcC")){
 | 
			
		||||
          meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
 | 
			
		||||
        MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox;
 | 
			
		||||
        meta.setType(tNumber, "audio");
 | 
			
		||||
        meta.setChannels(tNumber, aEntryBox.getChannelCount());
 | 
			
		||||
        meta.setRate(tNumber, aEntryBox.getSampleRate());
 | 
			
		||||
 | 
			
		||||
        if (sType == "ac-3"){
 | 
			
		||||
          meta.setCodec(tNumber, "AC3");
 | 
			
		||||
        }else{
 | 
			
		||||
          MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox());
 | 
			
		||||
          meta.setCodec(tNumber, esdsBox.getCodec());
 | 
			
		||||
          meta.setInit(tNumber, esdsBox.getInitData());
 | 
			
		||||
        }
 | 
			
		||||
        meta.setSize(tNumber, 16); ///\todo this might be nice to calculate from mp4 file;
 | 
			
		||||
      }
 | 
			
		||||
      if (sType == "tx3g"){// plain text subtitles
 | 
			
		||||
        meta.setType(tNumber, "meta");
 | 
			
		||||
        meta.setCodec(tNumber, "subtitle");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      MP4::STSS stssBox = stblBox.getChild<MP4::STSS>();
 | 
			
		||||
      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
 | 
			
		||||
      mp4PartBpos BsetPart;
 | 
			
		||||
 | 
			
		||||
      uint64_t entryNo = 0;
 | 
			
		||||
      uint64_t sampleNo = 0;
 | 
			
		||||
 | 
			
		||||
      uint64_t stssIndex = 0;
 | 
			
		||||
      uint64_t stcoIndex = 0;
 | 
			
		||||
      uint64_t stscIndex = 0;
 | 
			
		||||
      uint64_t cttsIndex = 0;     // current ctts Index we are reading
 | 
			
		||||
      uint64_t cttsEntryRead = 0; // current part of ctts we are reading
 | 
			
		||||
 | 
			
		||||
      uint64_t stssCount = stssBox.getEntryCount();
 | 
			
		||||
      uint64_t stscCount = stscBox.getEntryCount();
 | 
			
		||||
      uint64_t stszCount = stszBox.getSampleCount();
 | 
			
		||||
      uint64_t stcoCount = (stco64 ? co64Box.getEntryCount() : stcoBox.getEntryCount());
 | 
			
		||||
 | 
			
		||||
      MP4::STTSEntry sttsEntry = sttsBox.getSTTSEntry(0);
 | 
			
		||||
 | 
			
		||||
      uint32_t fromSTCOinSTSC = 0;
 | 
			
		||||
      uint64_t tmpOffset = (stco64 ? co64Box.getChunkOffset(0) : stcoBox.getChunkOffset(0));
 | 
			
		||||
 | 
			
		||||
      uint64_t nextFirstChunk = (stscCount > 1 ? stscBox.getSTSCEntry(1).firstChunk - 1 : stcoCount);
 | 
			
		||||
 | 
			
		||||
      for (uint64_t stszIndex = 0; stszIndex < stszCount; ++stszIndex){
 | 
			
		||||
        if (stcoIndex >= nextFirstChunk){
 | 
			
		||||
          ++stscIndex;
 | 
			
		||||
          nextFirstChunk =
 | 
			
		||||
              (stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
 | 
			
		||||
        }
 | 
			
		||||
        BsetPart.keyframe = (meta.getType(tNumber) == "video" && stssIndex < stssCount &&
 | 
			
		||||
                             stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
 | 
			
		||||
        if (BsetPart.keyframe){++stssIndex;}
 | 
			
		||||
        // in bpos set
 | 
			
		||||
        BsetPart.stcoNr = stcoIndex;
 | 
			
		||||
        // bpos = chunkoffset[samplenr] in stco
 | 
			
		||||
        BsetPart.bpos = tmpOffset;
 | 
			
		||||
        ++fromSTCOinSTSC;
 | 
			
		||||
        if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){// as long as we are still in this chunk
 | 
			
		||||
          tmpOffset += stszBox.getEntrySize(stszIndex);
 | 
			
		||||
        }else{
 | 
			
		||||
          ++stcoIndex;
 | 
			
		||||
          fromSTCOinSTSC = 0;
 | 
			
		||||
          tmpOffset = (stco64 ? co64Box.getChunkOffset(stcoIndex) : stcoBox.getChunkOffset(stcoIndex));
 | 
			
		||||
        }
 | 
			
		||||
        BsetPart.time = (totaldur * 1000) / timescale;
 | 
			
		||||
        totaldur += sttsEntry.sampleDelta;
 | 
			
		||||
        sampleNo++;
 | 
			
		||||
        if (sampleNo >= sttsEntry.sampleCount){
 | 
			
		||||
          ++entryNo;
 | 
			
		||||
          sampleNo = 0;
 | 
			
		||||
          if (entryNo < sttsBox.getEntryCount()){sttsEntry = sttsBox.getSTTSEntry(entryNo);}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (hasCTTS){
 | 
			
		||||
          MP4::CTTSEntry cttsEntry = cttsBox.getCTTSEntry(cttsIndex);
 | 
			
		||||
          cttsEntryRead++;
 | 
			
		||||
          if (cttsEntryRead >= cttsEntry.sampleCount){
 | 
			
		||||
            ++cttsIndex;
 | 
			
		||||
            cttsEntryRead = 0;
 | 
			
		||||
          }
 | 
			
		||||
          BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000) / timescale;
 | 
			
		||||
        }else{
 | 
			
		||||
          BsetPart.timeOffset = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sType == "tx3g"){
 | 
			
		||||
          if (stszBox.getEntrySize(stszIndex) <= 2 && false){
 | 
			
		||||
            FAIL_MSG("size <=2");
 | 
			
		||||
          }else{
 | 
			
		||||
            long long packSendSize = 0;
 | 
			
		||||
            packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
 | 
			
		||||
                           stszBox.getEntrySize(stszIndex) + 11 - 2 + 19;
 | 
			
		||||
            meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
 | 
			
		||||
                        stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
 | 
			
		||||
          }
 | 
			
		||||
        }else{
 | 
			
		||||
          meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
 | 
			
		||||
                      stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    clearerr(inFile);
 | 
			
		||||
 | 
			
		||||
    // outputting dtsh file
 | 
			
		||||
    M.toFile(config->getString("input") + ".dtsh");
 | 
			
		||||
    std::string inUrl = config->getString("input");
 | 
			
		||||
    if (inUrl.size() > 4 && inUrl.substr(0, 4) == "mp4:"){inUrl.erase(0, 4);}
 | 
			
		||||
    if (inUrl != "-" && HTTP::URL(inUrl).isLocalPath()){
 | 
			
		||||
      M.toFile(inUrl + ".dtsh");
 | 
			
		||||
    }else{
 | 
			
		||||
      INFO_MSG("Skipping header write, as the source is not a local file");
 | 
			
		||||
    }
 | 
			
		||||
    bps = 0;
 | 
			
		||||
    std::set<size_t> tracks = M.getValidTracks();
 | 
			
		||||
    for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){bps += M.getBps(*it);}
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -474,23 +513,48 @@ namespace Mist{
 | 
			
		|||
        ++nextKeyNum;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (fseeko(inFile, curPart.bpos, SEEK_SET)){
 | 
			
		||||
      FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s", curPart.bpos, strerror(errno));
 | 
			
		||||
      thisPacket.null();
 | 
			
		||||
      return;
 | 
			
		||||
    if (curPart.bpos < readPos || curPart.bpos > readPos + readBuffer.size() + 512*1024 + bps){
 | 
			
		||||
      INFO_MSG("Buffer contains %" PRIu64 "-%" PRIu64 ", but we need %" PRIu64 "; seeking!", readPos, readPos + readBuffer.size(), curPart.bpos);
 | 
			
		||||
      readBuffer.truncate(0);
 | 
			
		||||
      if (!inFile.seek(curPart.bpos)){
 | 
			
		||||
        FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s", curPart.bpos, strerror(errno));
 | 
			
		||||
        thisPacket.null();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      readPos = inFile.getPos();
 | 
			
		||||
    }else{
 | 
			
		||||
      //If we have more than 5MiB buffered and are more than 5MiB into the buffer, shift the first 4MiB off the buffer.
 | 
			
		||||
      //This prevents infinite growth of the read buffer for large files
 | 
			
		||||
      if (readBuffer.size() >= 5*1024*1024 && curPart.bpos > readPos + 5*1024*1024 + bps){
 | 
			
		||||
        readBuffer.shift(4*1024*1024);
 | 
			
		||||
        readPos += 4*1024*1024;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (curPart.size > malSize){
 | 
			
		||||
      data = (char *)realloc(data, curPart.size);
 | 
			
		||||
      malSize = curPart.size;
 | 
			
		||||
 | 
			
		||||
    while (readPos+readBuffer.size() < curPart.bpos+curPart.size && inFile && keepRunning()){
 | 
			
		||||
      inFile.readSome((curPart.bpos+curPart.size) - (readPos+readBuffer.size()), *this);
 | 
			
		||||
    }
 | 
			
		||||
    if (fread(data, curPart.size, 1, inFile) != 1){
 | 
			
		||||
      FAIL_MSG("read unsuccessful at %ld", ftell(inFile));
 | 
			
		||||
      thisPacket.null();
 | 
			
		||||
      return;
 | 
			
		||||
    if (readPos+readBuffer.size() < curPart.bpos+curPart.size){
 | 
			
		||||
      FAIL_MSG("Read unsuccessful at %" PRIu64 ", seeking to retry...", readPos+readBuffer.size());
 | 
			
		||||
      readBuffer.truncate(0);
 | 
			
		||||
      if (!inFile.seek(curPart.bpos)){
 | 
			
		||||
        FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s", curPart.bpos, strerror(errno));
 | 
			
		||||
        thisPacket.null();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      readPos = inFile.getPos();
 | 
			
		||||
      while (readPos+readBuffer.size() < curPart.bpos+curPart.size && inFile && keepRunning()){
 | 
			
		||||
        inFile.readSome((curPart.bpos+curPart.size) - (readPos+readBuffer.size()), *this);
 | 
			
		||||
      }
 | 
			
		||||
      if (readPos+readBuffer.size() < curPart.bpos+curPart.size){
 | 
			
		||||
        FAIL_MSG("Read retry unsuccessful at %" PRIu64 ", aborting", readPos+readBuffer.size());
 | 
			
		||||
        thisPacket.null();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (M.getCodec(curPart.trackID) == "subtitle"){
 | 
			
		||||
      unsigned int txtLen = Bit::btohs(data);
 | 
			
		||||
      unsigned int txtLen = Bit::btohs(readBuffer + (curPart.bpos-readPos));
 | 
			
		||||
      if (!txtLen && false){
 | 
			
		||||
        curPart.index++;
 | 
			
		||||
        return getNext(idx);
 | 
			
		||||
| 
						 | 
				
			
			@ -499,14 +563,14 @@ namespace Mist{
 | 
			
		|||
      thisPack.null();
 | 
			
		||||
      thisPack["trackid"] = curPart.trackID;
 | 
			
		||||
      thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg();
 | 
			
		||||
      thisPack["data"] = std::string(data + 2, txtLen);
 | 
			
		||||
      thisPack["data"] = std::string(readBuffer + (curPart.bpos-readPos) + 2, txtLen);
 | 
			
		||||
      thisPack["time"] = curPart.time;
 | 
			
		||||
      if (curPart.duration){thisPack["duration"] = curPart.duration;}
 | 
			
		||||
      thisPack["keyframe"] = true;
 | 
			
		||||
      std::string tmpStr = thisPack.toNetPacked();
 | 
			
		||||
      thisPacket.reInit(tmpStr.data(), tmpStr.size());
 | 
			
		||||
    }else{
 | 
			
		||||
      thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0, isKeyframe);
 | 
			
		||||
      thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, readBuffer + (curPart.bpos-readPos), curPart.size, 0, isKeyframe);
 | 
			
		||||
    }
 | 
			
		||||
    thisTime = curPart.time;
 | 
			
		||||
    thisIdx = curPart.trackID;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
#include "input.h"
 | 
			
		||||
#include <mist/dtsc.h>
 | 
			
		||||
#include <mist/urireader.h>
 | 
			
		||||
#include <mist/mp4.h>
 | 
			
		||||
#include <mist/mp4_generic.h>
 | 
			
		||||
namespace Mist{
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +71,10 @@ namespace Mist{
 | 
			
		|||
    bool stco64;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  class inputMP4 : public Input{
 | 
			
		||||
  class inputMP4 : public Input, public Util::DataCallback {
 | 
			
		||||
  public:
 | 
			
		||||
    inputMP4(Util::Config *cfg);
 | 
			
		||||
    ~inputMP4();
 | 
			
		||||
    void dataCallback(const char *ptr, size_t size);
 | 
			
		||||
 | 
			
		||||
  protected:
 | 
			
		||||
    // Private Functions
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +86,10 @@ namespace Mist{
 | 
			
		|||
    void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
 | 
			
		||||
    void handleSeek(uint64_t seekTime, size_t idx);
 | 
			
		||||
 | 
			
		||||
    FILE *inFile;
 | 
			
		||||
    HTTP::URIReader inFile;
 | 
			
		||||
    Util::ResizeablePointer readBuffer;
 | 
			
		||||
    uint64_t readPos;
 | 
			
		||||
    uint64_t bps;
 | 
			
		||||
 | 
			
		||||
    mp4TrackHeader &headerData(size_t trackID);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -94,11 +98,6 @@ namespace Mist{
 | 
			
		|||
 | 
			
		||||
    // remember last seeked keyframe;
 | 
			
		||||
    std::map<size_t, uint32_t> nextKeyframe;
 | 
			
		||||
 | 
			
		||||
    // these next two variables keep a buffer for reading from filepointer inFile;
 | 
			
		||||
    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
 | 
			
		||||
  };
 | 
			
		||||
}// namespace Mist
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue