CMAF Push Output
This commit is contained in:
		
							parent
							
								
									0af992d405
								
							
						
					
					
						commit
						e217f41f17
					
				
					 7 changed files with 509 additions and 26 deletions
				
			
		|  | @ -16,13 +16,80 @@ | |||
| uint64_t bootMsOffset; | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   void CMAFPushTrack::connect(std::string debugParam) { | ||||
|     D.setHeader("Transfer-Encoding", "chunked"); | ||||
|     D.prepareRequest(url, "POST"); | ||||
| 
 | ||||
|     HTTP::Parser & http = D.getHTTP(); | ||||
|     http.sendingChunks = true; | ||||
|     http.SendRequest(D.getSocket()); | ||||
| 
 | ||||
|     if (debugParam.length()){ | ||||
|       if (debugParam[debugParam.length()-1] != '/'){ | ||||
|         debugParam += '/'; | ||||
|       } | ||||
|       debug = true; | ||||
|       std::string filename = url.getUrl(); | ||||
|       filename.erase(0, filename.rfind("/")+1); | ||||
|       snprintf(debugName, 500, "%s%s-%" PRIu64, debugParam.c_str(), filename.c_str(), Util::bootMS()); | ||||
|       INFO_MSG("CMAF DEBUG FILE: %s", debugName); | ||||
|       debugFile = fopen(debugName, "wb"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void CMAFPushTrack::disconnect() { | ||||
|     Socket::Connection & sock = D.getSocket(); | ||||
| 
 | ||||
|     MP4::MFRA mfraBox; | ||||
|     send(mfraBox.asBox(), mfraBox.boxedSize()); | ||||
|     send(""); | ||||
|     sock.close(); | ||||
| 
 | ||||
|     if (debugFile) { | ||||
|       fclose(debugFile); | ||||
|       debugFile = 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void CMAFPushTrack::send(const char * data, size_t len){ | ||||
|     D.getHTTP().Chunkify(data, len, D.getSocket()); | ||||
|     if (debug && debugFile) { | ||||
|       fwrite(data, 1, len, debugFile); | ||||
| 
 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void CMAFPushTrack::send(const std::string & data){ | ||||
|     send(data.data(), data.size()); | ||||
|   } | ||||
| 
 | ||||
|   OutCMAF::OutCMAF(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     uaDelay = 0; | ||||
|     realTime = 0; | ||||
|     if (config->getString("target").size()){ | ||||
|       needsLookAhead = 5000; | ||||
| 
 | ||||
|       streamName = config->getString("streamname"); | ||||
|       std::string target = config->getString("target"); | ||||
|       target.replace(0, 4, "http");//Translate to http for cmaf:// or https for cmafs://
 | ||||
|       pushUrl = HTTP::URL(target); | ||||
| 
 | ||||
|       INFO_MSG("About to push stream %s out. Host: %s, port: %d, location: %s", streamName.c_str(), | ||||
|                pushUrl.host.c_str(), pushUrl.getPort(), pushUrl.path.c_str()); | ||||
|       initialize(); | ||||
|       initialSeek(); | ||||
|       startPushOut(); | ||||
|     } else { | ||||
|       realTime = 0; | ||||
|     } | ||||
|     INFO_MSG("Out of constructor now"); | ||||
|   } | ||||
| 
 | ||||
|   OutCMAF::~OutCMAF(){} | ||||
|   //Properly end all tracks on shutdown.
 | ||||
|   OutCMAF::~OutCMAF() { | ||||
|     for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|       onTrackEnd(it->first); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void OutCMAF::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|  | @ -66,6 +133,16 @@ namespace Mist{ | |||
|         "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance " | ||||
|         "significantly, but increases compatibility somewhat."; | ||||
|     capa["optional"]["nonchunked"]["option"] = "--nonchunked"; | ||||
| 
 | ||||
|     capa["push_urls"].append("cmaf://*"); | ||||
|     capa["push_urls"].append("cmafs://*"); | ||||
| 
 | ||||
|     JSON::Value opt; | ||||
|     opt["arg"] = "string"; | ||||
|     opt["default"] = ""; | ||||
|     opt["arg_num"] = 1; | ||||
|     opt["help"] = "Target CMAF URL to push out towards."; | ||||
|     cfg->addOption("target", opt); | ||||
|   } | ||||
| 
 | ||||
|   void OutCMAF::onHTTP(){ | ||||
|  | @ -157,7 +234,7 @@ namespace Mist{ | |||
|     uint64_t fragmentIndex = M.getFragmentIndexForTime(idx, startTime); | ||||
|     targetTime = M.getTimeForFragmentIndex(idx, fragmentIndex + 1); | ||||
| 
 | ||||
|     std::string headerData = CMAF::fragmentHeader(M, idx, fragmentIndex); | ||||
|     std::string headerData = CMAF::fragmentHeader(M, idx, fragmentIndex, false, false); | ||||
|     H.Chunkify(headerData.c_str(), headerData.size(), myConn); | ||||
| 
 | ||||
|     uint64_t mdatSize = 8 + CMAF::payloadSize(M, idx, fragmentIndex); | ||||
|  | @ -172,7 +249,13 @@ namespace Mist{ | |||
|     parseData = true; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   void OutCMAF::sendNext(){ | ||||
|     if (isRecording()){ | ||||
|       pushNext(); | ||||
|       return; | ||||
|     } | ||||
|     if (thisPacket.getTime() >= targetTime){ | ||||
|       HIGH_MSG("Finished playback to %" PRIu64, targetTime); | ||||
|       wantRequest = true; | ||||
|  | @ -682,6 +765,122 @@ namespace Mist{ | |||
|     r << "</SmoothStreamingMedia>\n"; | ||||
| 
 | ||||
|     return toUTF16(r.str()); | ||||
|   }// namespace Mist
 | ||||
|   } | ||||
| 
 | ||||
|   /**********************************/ | ||||
|   /* CMAF Push Output functionality */ | ||||
|   /**********************************/ | ||||
| 
 | ||||
|   //When we disconnect a track, or when we're done pushing out, send an empty 'mfra' box to indicate track end.
 | ||||
|   void OutCMAF::onTrackEnd(size_t idx) { | ||||
|     if (!isRecording()){return;} | ||||
|     if (!pushTracks.count(idx) || !pushTracks.at(idx).D.getSocket()){return;} | ||||
|     INFO_MSG("Disconnecting track %zu", idx); | ||||
|     pushTracks[idx].disconnect(); | ||||
|      | ||||
|     pushTracks.erase(idx); | ||||
|   } | ||||
|    | ||||
|   //Create the connections and post request needed to start pushing out a track.
 | ||||
|   void OutCMAF::setupTrackObject(size_t idx) { | ||||
|     CMAFPushTrack & track = pushTracks[idx]; | ||||
|     track.url = pushUrl; | ||||
|     if (targetParams.count("usp") && targetParams["usp"] == "1"){ | ||||
|       std::string usp_path = "Streams(" + M.getType(idx) + + "_" + JSON::Value(idx).asString() + ")";  | ||||
|       track.url = track.url.link(usp_path); | ||||
|     }else{ | ||||
|       track.url.path += "/";  | ||||
|       track.url = track.url.link(M.getTrackIdentifier(idx)); | ||||
|     } | ||||
| 
 | ||||
|     track.connect(targetParams["debug"]); | ||||
| 
 | ||||
|     std::string header = CMAF::trackHeader(M, idx, true); | ||||
|     track.send(header); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   /// Function that waits at most `maxWait` ms (in steps of 100ms) for the next keyframe to become available.
 | ||||
|   /// Uses thisIdx and thisPacket to determine track and current timestamp respectively.
 | ||||
|   bool OutCMAF::waitForNextKey(uint64_t maxWait){ | ||||
|     size_t currentKey = M.getKeyIndexForTime(thisIdx, thisPacket.getTime()); | ||||
|     DTSC::Keys keys(M.keys(thisIdx)); | ||||
|     size_t waitTimes = maxWait / 100;  | ||||
|     for (size_t i = 0; i < waitTimes; ++i){ | ||||
|       if (keys.getEndValid() > currentKey + 1){return true;} | ||||
|       Util::wait(100); | ||||
|       //Make sure we don't accidentally timeout while waiting - runs approximately every second.
 | ||||
|       if (i % 10 == 0){ | ||||
|         for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ | ||||
|           it->second.keepAlive(); | ||||
|           stats(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return (keys.getEndValid() > currentKey + 1); | ||||
|   } | ||||
| 
 | ||||
|   //Set up an empty connection to the target to make sure we can push data towards it.
 | ||||
|   void OutCMAF::startPushOut(){ | ||||
|     myConn.close(); | ||||
|     myConn.Received().clear(); | ||||
|     myConn.open(pushUrl.host, pushUrl.getPort(), true); | ||||
|     wantRequest = false; | ||||
|     parseData = true; | ||||
|   } | ||||
| 
 | ||||
|   //CMAF Push output uses keyframe boundaries instead of fragment boundaries, to allow for lower latency
 | ||||
|   void OutCMAF::pushNext() { | ||||
|     //Set up a new connection if this is a new track, or if we have been disconnected.
 | ||||
|     if (!pushTracks.count(thisIdx) || !pushTracks.at(thisIdx).D.getSocket()){ | ||||
|       CMAFPushTrack & track = pushTracks[thisIdx]; | ||||
|       size_t keyIndex = M.getKeyIndexForTime(thisIdx, thisPacket.getTime()); | ||||
|       track.headerFrom = M.getTimeForKeyIndex(thisIdx, keyIndex); | ||||
|       if (track.headerFrom < thisPacket.getTime()){ | ||||
|         track.headerFrom = M.getTimeForKeyIndex(thisIdx, keyIndex + 1); | ||||
|       } | ||||
| 
 | ||||
|       HIGH_MSG("Starting track %zu at %" PRIu64 "ms into the stream, current packet at %" PRIu64 "ms", thisIdx, track.headerFrom, thisPacket.getTime()); | ||||
| 
 | ||||
|       setupTrackObject(thisIdx); | ||||
|       track.headerUntil = 0; | ||||
| 
 | ||||
|     } | ||||
|     CMAFPushTrack & track = pushTracks[thisIdx]; | ||||
|     if (thisPacket.getTime() < track.headerFrom){return;} | ||||
|     if (thisPacket.getTime() > track.headerUntil || !track.headerUntil){ | ||||
|       size_t keyIndex = M.getKeyIndexForTime(thisIdx, thisPacket.getTime()); | ||||
|       uint64_t keyTime = M.getTimeForKeyIndex(thisIdx, keyIndex); | ||||
|       if (keyTime != thisPacket.getTime()){ | ||||
|         WARN_MSG("Corruption probably occured, initiating reconnect %" PRIu64 " != %" PRIu64, keyTime, thisPacket.getTime()); | ||||
|         onTrackEnd(thisIdx); | ||||
|         track.headerFrom = M.getTimeForKeyIndex(thisIdx, keyIndex + 1); | ||||
|         track.headerUntil = 0; | ||||
|         pushNext(); | ||||
|         return; | ||||
|       } | ||||
|       track.headerFrom = keyTime; | ||||
|       if (!waitForNextKey()){ | ||||
|         onTrackEnd(thisIdx); | ||||
|         dropTrack(thisIdx, "No next keyframe available"); | ||||
|         return; | ||||
|       } | ||||
|       track.headerUntil = M.getTimeForKeyIndex(thisIdx, keyIndex + 1) - 1; | ||||
|       std::string keyHeader = CMAF::keyHeader(M, thisIdx, keyIndex, true, true); | ||||
| 
 | ||||
|       uint64_t mdatSize = 8 + CMAF::payloadSize(M, thisIdx, keyIndex, true); | ||||
|       char mdatHeader[] ={0x00, 0x00, 0x00, 0x00, 'm', 'd', 'a', 't'}; | ||||
|       Bit::htobl(mdatHeader, mdatSize); | ||||
| 
 | ||||
|       track.send(keyHeader); | ||||
|       track.send(mdatHeader, 8); | ||||
|     } | ||||
|     char *data; | ||||
|     size_t dataLen; | ||||
|     thisPacket.getString("data", data, dataLen); | ||||
| 
 | ||||
|     track.send(data, dataLen); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,8 +1,30 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/downloader.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   /// Keeps track of the state of an outgoing CMAF Push track.
 | ||||
|   class CMAFPushTrack { | ||||
|     public: | ||||
|       CMAFPushTrack() {debug = false; debugFile = 0;} | ||||
|       ~CMAFPushTrack() {disconnect();} | ||||
|       void connect(std::string debugParam = ""); | ||||
|       void disconnect(); | ||||
| 
 | ||||
|       void send(const char * data, size_t len); | ||||
|       void send(const std::string & data); | ||||
| 
 | ||||
|       HTTP::Downloader D; | ||||
|       HTTP::URL url; | ||||
|       uint64_t headerFrom; | ||||
|       uint64_t headerUntil; | ||||
| 
 | ||||
|       bool debug; | ||||
|       char debugName[500]; | ||||
|       FILE * debugFile; | ||||
|   }; | ||||
| 
 | ||||
|   class OutCMAF : public HTTPOutput{ | ||||
|   public: | ||||
|     OutCMAF(Socket::Connection &conn); | ||||
|  | @ -13,6 +35,8 @@ namespace Mist{ | |||
|     void sendHeader(){}; | ||||
| 
 | ||||
|   protected: | ||||
|     void onTrackEnd(size_t idx); | ||||
| 
 | ||||
|     void sendDashManifest(); | ||||
|     void dashAdaptationSet(size_t id, size_t idx, std::stringstream &r); | ||||
|     void dashRepresentation(size_t id, size_t idx, std::stringstream &r); | ||||
|  | @ -37,6 +61,16 @@ namespace Mist{ | |||
| 
 | ||||
|     std::string h264init(const std::string &initData); | ||||
|     std::string h265init(const std::string &initData); | ||||
| 
 | ||||
|     // For CMAF push out
 | ||||
|     void startPushOut(); | ||||
|     void pushNext(); | ||||
| 
 | ||||
|     HTTP::URL pushUrl; | ||||
|     std::map<size_t, CMAFPushTrack> pushTracks;  | ||||
|     void setupTrackObject(size_t idx); | ||||
|     bool waitForNextKey(uint64_t maxWait = 5000); | ||||
|     // End CMAF push out
 | ||||
|   }; | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Erik Zandvliet
						Erik Zandvliet