Restyle
This commit is contained in:
		
							parent
							
								
									5b79f296d6
								
							
						
					
					
						commit
						fccf66fba2
					
				
					 280 changed files with 56975 additions and 71885 deletions
				
			
		
							
								
								
									
										42537
									
								
								src/output/flashPlayer.h
									
										
									
									
									
								
							
							
						
						
									
										42537
									
								
								src/output/flashPlayer.h
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,10 +1,10 @@ | |||
| #include OUTPUTTYPE | ||||
| #include <mist/config.h> | ||||
| #include <mist/socket.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/socket.h> | ||||
| #include <mist/util.h> | ||||
| 
 | ||||
| int spawnForked(Socket::Connection & S){ | ||||
| int spawnForked(Socket::Connection &S){ | ||||
|   mistOut tmp(S); | ||||
|   return tmp.run(); | ||||
| } | ||||
|  | @ -15,12 +15,12 @@ void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){ | |||
|   Util::Config::is_active = false; | ||||
| } | ||||
| 
 | ||||
| int main(int argc, char * argv[]) { | ||||
| int main(int argc, char *argv[]){ | ||||
|   Util::redirectLogsIfNeeded(); | ||||
|   Util::Config conf(argv[0]); | ||||
|   mistOut::init(&conf); | ||||
|   if (conf.parseArgs(argc, argv)) { | ||||
|     if (conf.getBool("json")) { | ||||
|   if (conf.parseArgs(argc, argv)){ | ||||
|     if (conf.getBool("json")){ | ||||
|       mistOut::capa["version"] = PACKAGE_VERSION; | ||||
|       std::cout << mistOut::capa.toString() << std::endl; | ||||
|       return -1; | ||||
|  | @ -41,7 +41,7 @@ int main(int argc, char * argv[]) { | |||
|         FAIL_MSG("Error reloading: %s", strerror(errno)); | ||||
|       } | ||||
|     }else{ | ||||
|       Socket::Connection S(fileno(stdout),fileno(stdin) ); | ||||
|       Socket::Connection S(fileno(stdout), fileno(stdin)); | ||||
|       mistOut tmp(S); | ||||
|       return tmp.run(); | ||||
|     } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,25 +1,23 @@ | |||
| #pragma once | ||||
| #include <set> | ||||
| #include "../io.h" | ||||
| #include <cstdlib> | ||||
| #include <map> | ||||
| #include <mist/config.h> | ||||
| #include <mist/json.h> | ||||
| #include <mist/flv_tag.h> | ||||
| #include <mist/timing.h> | ||||
| #include <mist/dtsc.h> | ||||
| #include <mist/socket.h> | ||||
| #include <mist/flv_tag.h> | ||||
| #include <mist/json.h> | ||||
| #include <mist/shared_memory.h> | ||||
| #include "../io.h" | ||||
| #include <mist/socket.h> | ||||
| #include <mist/timing.h> | ||||
| #include <set> | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
| 
 | ||||
|   /// This struct keeps packet information sorted in playback order, so the
 | ||||
|   /// Mist::Output class knows when to buffer which packet.
 | ||||
|   struct sortedPageInfo { | ||||
|     bool operator < (const sortedPageInfo & rhs) const { | ||||
|       if (time < rhs.time) { | ||||
|         return true; | ||||
|       } | ||||
|   struct sortedPageInfo{ | ||||
|     bool operator<(const sortedPageInfo &rhs) const{ | ||||
|       if (time < rhs.time){return true;} | ||||
|       return (time == rhs.time && tid < rhs.tid); | ||||
|     } | ||||
|     uint64_t tid; | ||||
|  | @ -33,122 +31,120 @@ namespace Mist { | |||
|   /// It contains several virtual functions, that may be overridden to "hook" into
 | ||||
|   /// the streaming process at those particular points, simplifying child class
 | ||||
|   /// logic and implementation details.
 | ||||
|   class Output : public InOutBase { | ||||
|     public: | ||||
|       //constructor and destructor
 | ||||
|       Output(Socket::Connection & conn); | ||||
|       //static members for initialization and capabilities
 | ||||
|       static void init(Util::Config * cfg); | ||||
|       static JSON::Value capa; | ||||
|       /*LTS-START*/ | ||||
|       std::string reqUrl; | ||||
|       /*LTS-END*/ | ||||
|       //non-virtual generic functions
 | ||||
|       virtual int run(); | ||||
|       virtual void stats(bool force = false); | ||||
|       void seek(unsigned long long pos, bool toKey = false); | ||||
|       bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false); | ||||
|       void stop(); | ||||
|       uint64_t currentTime(); | ||||
|       uint64_t startTime(); | ||||
|       uint64_t endTime(); | ||||
|       uint64_t liveTime(); | ||||
|       void setBlocking(bool blocking); | ||||
|       void updateMeta(); | ||||
|       void selectTrack(const std::string &trackType, const std::string &trackVal); /*LTS*/ | ||||
|       bool selectDefaultTracks(); | ||||
|       bool connectToFile(std::string file); | ||||
|       static bool listenMode(){return true;} | ||||
|       uint32_t currTrackCount() const; | ||||
|       virtual bool isReadyForPlay(); | ||||
|       virtual bool reachedPlannedStop(); | ||||
|       //virtuals. The optional virtuals have default implementations that do as little as possible.
 | ||||
|       /// This function is called whenever a packet is ready for sending.
 | ||||
|       /// Inside it, thisPacket is guaranteed to contain a valid packet.
 | ||||
|       virtual void sendNext() {}//REQUIRED! Others are optional.
 | ||||
|       bool prepareNext(); | ||||
|       virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true); | ||||
|       virtual void onRequest(); | ||||
|       static void listener(Util::Config & conf, int (*callback)(Socket::Connection & S)); | ||||
|       virtual void initialSeek(); | ||||
|       virtual bool liveSeek(); | ||||
|       virtual bool onFinish() { | ||||
|         return false; | ||||
|       } | ||||
|       void reconnect(); | ||||
|       void disconnect(); | ||||
|       virtual void initialize(); | ||||
|       virtual void sendHeader(); | ||||
|       virtual void onFail(const std::string & msg, bool critical = false); | ||||
|       virtual void requestHandler(); | ||||
|       static Util::Config * config; | ||||
|       void playbackSleep(uint64_t millis); | ||||
|     private://these *should* not be messed with in child classes.
 | ||||
|       /*LTS-START*/ | ||||
|       void Log(std::string type, std::string message); | ||||
|       bool checkLimits(); | ||||
|       bool isBlacklisted(std::string host, std::string streamName, int timeConnected); | ||||
|       std::string hostLookup(std::string ip); | ||||
|       bool onList(std::string ip, std::string list); | ||||
|       std::string getCountry(std::string ip); | ||||
|       void doSync(bool force = false); | ||||
|       /*LTS-END*/ | ||||
|       std::map<unsigned long, unsigned int> currKeyOpen; | ||||
|       void loadPageForKey(long unsigned int trackId, long long int keyNum); | ||||
|       int pageNumForKey(long unsigned int trackId, long long int keyNum); | ||||
|       int pageNumMax(long unsigned int trackId); | ||||
|       bool isRecordingToFile; | ||||
|       unsigned int lastStats;///<Time of last sending of stats.
 | ||||
|       std::map<unsigned long, unsigned long> nxtKeyNum;///< Contains the number of the next key, for page seeking purposes.
 | ||||
|       std::set<sortedPageInfo> buffer;///< A sorted list of next-to-be-loaded packets.
 | ||||
|       bool sought;///<If a seek has been done, this is set to true. Used for seeking on prepareNext().
 | ||||
|     protected://these are to be messed with by child classes
 | ||||
|       virtual bool inlineRestartCapable() const{return false;}///< True if the output is capable of restarting mid-stream. This is used for swapping recording files
 | ||||
|       bool pushing; | ||||
|       std::map<std::string, std::string> targetParams; /*LTS*/ | ||||
|       std::string UA; ///< User Agent string, if known.
 | ||||
|       uint16_t uaDelay;///<Seconds to wait before setting the UA.
 | ||||
|       uint64_t lastRecv; | ||||
|       uint64_t extraKeepAway; | ||||
|       long long unsigned int firstTime;///< Time of first packet after last seek. Used for real-time sending.
 | ||||
|       virtual std::string getConnectedHost(); | ||||
|       virtual std::string getConnectedBinHost(); | ||||
|       virtual std::string getStatsName(); | ||||
|       virtual bool hasSessionIDs(){return false;} | ||||
|   class Output : public InOutBase{ | ||||
|   public: | ||||
|     // constructor and destructor
 | ||||
|     Output(Socket::Connection &conn); | ||||
|     // static members for initialization and capabilities
 | ||||
|     static void init(Util::Config *cfg); | ||||
|     static JSON::Value capa; | ||||
|     /*LTS-START*/ | ||||
|     std::string reqUrl; | ||||
|     /*LTS-END*/ | ||||
|     // non-virtual generic functions
 | ||||
|     virtual int run(); | ||||
|     virtual void stats(bool force = false); | ||||
|     void seek(unsigned long long pos, bool toKey = false); | ||||
|     bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false); | ||||
|     void stop(); | ||||
|     uint64_t currentTime(); | ||||
|     uint64_t startTime(); | ||||
|     uint64_t endTime(); | ||||
|     uint64_t liveTime(); | ||||
|     void setBlocking(bool blocking); | ||||
|     void updateMeta(); | ||||
|     void selectTrack(const std::string &trackType, const std::string &trackVal); /*LTS*/ | ||||
|     bool selectDefaultTracks(); | ||||
|     bool connectToFile(std::string file); | ||||
|     static bool listenMode(){return true;} | ||||
|     uint32_t currTrackCount() const; | ||||
|     virtual bool isReadyForPlay(); | ||||
|     virtual bool reachedPlannedStop(); | ||||
|     // virtuals. The optional virtuals have default implementations that do as little as possible.
 | ||||
|     /// This function is called whenever a packet is ready for sending.
 | ||||
|     /// Inside it, thisPacket is guaranteed to contain a valid packet.
 | ||||
|     virtual void sendNext(){}// REQUIRED! Others are optional.
 | ||||
|     bool prepareNext(); | ||||
|     virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true); | ||||
|     virtual void onRequest(); | ||||
|     static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S)); | ||||
|     virtual void initialSeek(); | ||||
|     virtual bool liveSeek(); | ||||
|     virtual bool onFinish(){return false;} | ||||
|     void reconnect(); | ||||
|     void disconnect(); | ||||
|     virtual void initialize(); | ||||
|     virtual void sendHeader(); | ||||
|     virtual void onFail(const std::string &msg, bool critical = false); | ||||
|     virtual void requestHandler(); | ||||
|     static Util::Config *config; | ||||
|     void playbackSleep(uint64_t millis); | ||||
| 
 | ||||
|       IPC::sharedClient statsPage;///< Shared memory used for statistics reporting.
 | ||||
|       bool isBlocking;///< If true, indicates that myConn is blocking.
 | ||||
|       uint32_t crc;///< Checksum, if any, for usage in the stats.
 | ||||
|       unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp); | ||||
|       uint64_t nextKeyTime(); | ||||
|        | ||||
|       //stream delaying variables
 | ||||
|       unsigned int maxSkipAhead;///< Maximum ms that we will go ahead of the intended timestamps.
 | ||||
|       unsigned int realTime;///< Playback speed in ms of data per second. eg: 0 is infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed.
 | ||||
|       uint32_t needsLookAhead;///< Amount of millis we need to be able to look ahead in the metadata
 | ||||
|   private: // these *should* not be messed with in child classes.
 | ||||
|     /*LTS-START*/ | ||||
|     void Log(std::string type, std::string message); | ||||
|     bool checkLimits(); | ||||
|     bool isBlacklisted(std::string host, std::string streamName, int timeConnected); | ||||
|     std::string hostLookup(std::string ip); | ||||
|     bool onList(std::string ip, std::string list); | ||||
|     std::string getCountry(std::string ip); | ||||
|     void doSync(bool force = false); | ||||
|     /*LTS-END*/ | ||||
|     std::map<unsigned long, unsigned int> currKeyOpen; | ||||
|     void loadPageForKey(long unsigned int trackId, long long int keyNum); | ||||
|     int pageNumForKey(long unsigned int trackId, long long int keyNum); | ||||
|     int pageNumMax(long unsigned int trackId); | ||||
|     bool isRecordingToFile; | ||||
|     unsigned int lastStats;                           ///< Time of last sending of stats.
 | ||||
|     std::map<unsigned long, unsigned long> nxtKeyNum; ///< Contains the number of the next key, for page seeking purposes.
 | ||||
|     std::set<sortedPageInfo> buffer; ///< A sorted list of next-to-be-loaded packets.
 | ||||
|     bool sought; ///< If a seek has been done, this is set to true. Used for seeking on prepareNext().
 | ||||
|   protected:     // these are to be messed with by child classes
 | ||||
|     virtual bool inlineRestartCapable() const{ | ||||
|       return false; | ||||
|     }///< True if the output is capable of restarting mid-stream. This is used for swapping recording files
 | ||||
|     bool pushing; | ||||
|     std::map<std::string, std::string> targetParams; /*LTS*/ | ||||
|     std::string UA;                                  ///< User Agent string, if known.
 | ||||
|     uint16_t uaDelay;                                ///< Seconds to wait before setting the UA.
 | ||||
|     uint64_t lastRecv; | ||||
|     uint64_t extraKeepAway; | ||||
|     long long unsigned int firstTime; ///< Time of first packet after last seek. Used for real-time sending.
 | ||||
|     virtual std::string getConnectedHost(); | ||||
|     virtual std::string getConnectedBinHost(); | ||||
|     virtual std::string getStatsName(); | ||||
|     virtual bool hasSessionIDs(){return false;} | ||||
| 
 | ||||
|       //Read/write status variables
 | ||||
|       Socket::Connection & myConn;///< Connection to the client.
 | ||||
|     IPC::sharedClient statsPage; ///< Shared memory used for statistics reporting.
 | ||||
|     bool isBlocking;             ///< If true, indicates that myConn is blocking.
 | ||||
|     uint32_t crc;                ///< Checksum, if any, for usage in the stats.
 | ||||
|     unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp); | ||||
|     uint64_t nextKeyTime(); | ||||
| 
 | ||||
|       bool wantRequest;///< If true, waits for a request.
 | ||||
|       bool parseData;///< If true, triggers initalization if not already done, sending of header, sending of packets.
 | ||||
|       bool isInitialized;///< If false, triggers initialization if parseData is true.
 | ||||
|       bool sentHeader;///< If false, triggers sendHeader if parseData is true.
 | ||||
|     // stream delaying variables
 | ||||
|     unsigned int maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps.
 | ||||
|     unsigned int realTime; ///< Playback speed in ms of data per second. eg: 0 is infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed.
 | ||||
|     uint32_t needsLookAhead; ///< Amount of millis we need to be able to look ahead in the metadata
 | ||||
| 
 | ||||
|       std::map<int,DTSCPageData> bookKeeping; | ||||
|       virtual bool isRecording(); | ||||
|       virtual bool isFileTarget(); | ||||
|       virtual bool isPushing(){return pushing;}; | ||||
|       bool allowPush(const std::string & passwd); | ||||
|       void waitForStreamPushReady(); | ||||
|       bool pushIsOngoing; | ||||
|       void bufferLivePacket(const DTSC::Packet & packet); | ||||
|       uint64_t firstPacketTime; | ||||
|       uint64_t lastPacketTime; | ||||
|       inline bool keepGoing(){ | ||||
|         return config->is_active && myConn; | ||||
|       } | ||||
|     // Read/write status variables
 | ||||
|     Socket::Connection &myConn; ///< Connection to the client.
 | ||||
| 
 | ||||
|     bool wantRequest; ///< If true, waits for a request.
 | ||||
|     bool parseData; ///< If true, triggers initalization if not already done, sending of header, sending of packets.
 | ||||
|     bool isInitialized; ///< If false, triggers initialization if parseData is true.
 | ||||
|     bool sentHeader;    ///< If false, triggers sendHeader if parseData is true.
 | ||||
| 
 | ||||
|     std::map<int, DTSCPageData> bookKeeping; | ||||
|     virtual bool isRecording(); | ||||
|     virtual bool isFileTarget(); | ||||
|     virtual bool isPushing(){return pushing;}; | ||||
|     bool allowPush(const std::string &passwd); | ||||
|     void waitForStreamPushReady(); | ||||
|     bool pushIsOngoing; | ||||
|     void bufferLivePacket(const DTSC::Packet &packet); | ||||
|     uint64_t firstPacketTime; | ||||
|     uint64_t lastPacketTime; | ||||
|     inline bool keepGoing(){return config->is_active && myConn;} | ||||
|   }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,20 +1,20 @@ | |||
| #include "output_dash_mp4.h" | ||||
| #include <iomanip> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/mp4.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/mp4_dash.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/timing.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/stream.h> | ||||
| #include <iomanip> | ||||
| #include <mist/timing.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   OutDashMP4::OutDashMP4(Socket::Connection & conn) : HTTPOutput(conn){ | ||||
|   OutDashMP4::OutDashMP4(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     uaDelay = 0; | ||||
|     realTime = 0; | ||||
|   } | ||||
|   OutDashMP4::~OutDashMP4(){} | ||||
|    | ||||
| 
 | ||||
|   std::string OutDashMP4::makeTime(uint64_t time){ | ||||
|     std::stringstream r; | ||||
|     r << "PT"; | ||||
|  | @ -23,10 +23,10 @@ namespace Mist{ | |||
|     r << (time / 1000) % 60 << "." << std::setfill('0') << std::setw(3) << (time % 1000) << "S"; | ||||
|     return r.str(); | ||||
|   } | ||||
|   | ||||
| 
 | ||||
|   /// Sends an empty moov box for the given track to the connected client, for following up with moof box(es).
 | ||||
|   void OutDashMP4::sendMoov(uint32_t tid){ | ||||
|     DTSC::Track & Trk = myMeta.tracks[tid]; | ||||
|     DTSC::Track &Trk = myMeta.tracks[tid]; | ||||
| 
 | ||||
|     MP4::MOOV moovBox; | ||||
|     MP4::MVHD mvhdBox(0); | ||||
|  | @ -41,8 +41,7 @@ namespace Mist{ | |||
|       iodsBox.setODAudioLevel(0xFE); | ||||
|     } | ||||
|     moovBox.setContent(iodsBox, 1); | ||||
|      | ||||
|      | ||||
| 
 | ||||
|     MP4::MVEX mvexBox; | ||||
|     MP4::MEHD mehdBox; | ||||
|     mehdBox.setFragmentDuration(0xFFFFFFFF); | ||||
|  | @ -51,7 +50,7 @@ namespace Mist{ | |||
|     trexBox.setTrackID(1); | ||||
|     mvexBox.setContent(trexBox, 1); | ||||
|     moovBox.setContent(mvexBox, 2); | ||||
|      | ||||
| 
 | ||||
|     MP4::TRAK trakBox; | ||||
|     MP4::TKHD tkhdBox(1, 0, Trk.width, Trk.height); | ||||
|     tkhdBox.setFlags(3); | ||||
|  | @ -62,18 +61,18 @@ namespace Mist{ | |||
|     } | ||||
|     tkhdBox.setDuration(0xFFFFFFFF); | ||||
|     trakBox.setContent(tkhdBox, 0); | ||||
|      | ||||
| 
 | ||||
|     MP4::MDIA mdiaBox; | ||||
|     MP4::MDHD mdhdBox(0); | ||||
|     mdhdBox.setLanguage(0x44); | ||||
|     mdhdBox.setDuration(Trk.lastms); | ||||
|     mdiaBox.setContent(mdhdBox, 0); | ||||
|      | ||||
| 
 | ||||
|     if (Trk.type == "video"){ | ||||
|       MP4::HDLR hdlrBox(Trk.type,"VideoHandler"); | ||||
|       MP4::HDLR hdlrBox(Trk.type, "VideoHandler"); | ||||
|       mdiaBox.setContent(hdlrBox, 1); | ||||
|     }else{ | ||||
|       MP4::HDLR hdlrBox(Trk.type,"SoundHandler"); | ||||
|       MP4::HDLR hdlrBox(Trk.type, "SoundHandler"); | ||||
|       mdiaBox.setContent(hdlrBox, 1); | ||||
|     } | ||||
| 
 | ||||
|  | @ -82,16 +81,16 @@ namespace Mist{ | |||
|     MP4::DREF drefBox; | ||||
|     dinfBox.setContent(drefBox, 0); | ||||
|     minfBox.setContent(dinfBox, 0); | ||||
|      | ||||
| 
 | ||||
|     MP4::STBL stblBox; | ||||
|     MP4::STSD stsdBox; | ||||
|     stsdBox.setVersion(0); | ||||
|      | ||||
| 
 | ||||
|     if (Trk.codec == "H264"){ | ||||
|       MP4::AVC1 avc1Box; | ||||
|       avc1Box.setWidth(Trk.width); | ||||
|       avc1Box.setHeight(Trk.height); | ||||
|        | ||||
| 
 | ||||
|       MP4::AVCC avccBox; | ||||
|       avccBox.setPayload(Trk.init); | ||||
|       avc1Box.setCLAP(avccBox); | ||||
|  | @ -101,7 +100,7 @@ namespace Mist{ | |||
|       MP4::HEV1 hev1Box; | ||||
|       hev1Box.setWidth(Trk.width); | ||||
|       hev1Box.setHeight(Trk.height); | ||||
|        | ||||
| 
 | ||||
|       MP4::HVCC hvccBox; | ||||
|       hvccBox.setPayload(Trk.init); | ||||
|       hev1Box.setCLAP(hvccBox); | ||||
|  | @ -116,7 +115,7 @@ namespace Mist{ | |||
|       ase.setSampleSize(Trk.size); | ||||
|       MP4::ESDS esdsBox(Trk.init); | ||||
|       ase.setCodecBox(esdsBox); | ||||
|       stsdBox.setEntry(ase,0); | ||||
|       stsdBox.setEntry(ase, 0); | ||||
|     } | ||||
|     if (Trk.codec == "AC3"){ | ||||
|       ///\todo Note: this code is copied, note for muxing seperation
 | ||||
|  | @ -128,29 +127,29 @@ namespace Mist{ | |||
|       ase.setSampleSize(Trk.size); | ||||
|       MP4::DAC3 dac3Box(Trk.rate, Trk.channels); | ||||
|       ase.setCodecBox(dac3Box); | ||||
|       stsdBox.setEntry(ase,0); | ||||
|       stsdBox.setEntry(ase, 0); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     stblBox.setContent(stsdBox, 0); | ||||
|      | ||||
| 
 | ||||
|     MP4::STTS sttsBox; | ||||
|     sttsBox.setVersion(0); | ||||
|     stblBox.setContent(sttsBox, 1); | ||||
|      | ||||
| 
 | ||||
|     MP4::STSC stscBox; | ||||
|     stscBox.setVersion(0); | ||||
|     stblBox.setContent(stscBox, 2); | ||||
|      | ||||
| 
 | ||||
|     MP4::STCO stcoBox; | ||||
|     stcoBox.setVersion(0); | ||||
|     stblBox.setContent(stcoBox, 3); | ||||
|      | ||||
| 
 | ||||
|     MP4::STSZ stszBox; | ||||
|     stszBox.setVersion(0); | ||||
|     stblBox.setContent(stszBox, 4); | ||||
|      | ||||
| 
 | ||||
|     minfBox.setContent(stblBox, 1); | ||||
|      | ||||
| 
 | ||||
|     if (Trk.type == "video"){ | ||||
|       MP4::VMHD vmhdBox; | ||||
|       vmhdBox.setFlags(1); | ||||
|  | @ -163,12 +162,12 @@ namespace Mist{ | |||
|     mdiaBox.setContent(minfBox, 2); | ||||
|     trakBox.setContent(mdiaBox, 1); | ||||
|     moovBox.setContent(trakBox, 3); | ||||
|     | ||||
| 
 | ||||
|     H.Chunkify(moovBox.asBox(), moovBox.boxedSize(), myConn); | ||||
|   } | ||||
|      | ||||
| 
 | ||||
|   void OutDashMP4::sendMoof(uint32_t tid, uint32_t fragIndice){ | ||||
|     DTSC::Track & Trk = myMeta.tracks[tid]; | ||||
|     DTSC::Track &Trk = myMeta.tracks[tid]; | ||||
|     MP4::MOOF moofBox; | ||||
|     MP4::MFHD mfhdBox; | ||||
|     mfhdBox.setSequenceNumber(fragIndice + Trk.missedFrags); | ||||
|  | @ -203,16 +202,15 @@ namespace Mist{ | |||
|           } | ||||
|         } | ||||
|       } | ||||
|       trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | MP4::trunfirstSampleFlags | MP4::trunsampleOffsets); | ||||
|       trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | | ||||
|                        MP4::trunfirstSampleFlags | MP4::trunsampleOffsets); | ||||
|       trunBox.setFirstSampleFlags(MP4::isKeySample); | ||||
|       trunBox.setDataOffset(0); | ||||
|       uint32_t j = 0; | ||||
|       for (DTSC::PartIter parts(Trk, Trk.fragments[fragIndice]); parts; ++parts){ | ||||
|         MP4::trunSampleInformation trunEntry; | ||||
|         trunEntry.sampleSize = parts->getSize(); | ||||
|         if (!j){ | ||||
|           trunEntry.sampleSize += headSize; | ||||
|         } | ||||
|         if (!j){trunEntry.sampleSize += headSize;} | ||||
|         trunEntry.sampleDuration = parts->getDuration(); | ||||
|         trunEntry.sampleOffset = parts->getOffset(); | ||||
|         trunBox.setSampleInformation(trunEntry, j); | ||||
|  | @ -237,20 +235,20 @@ namespace Mist{ | |||
|     moofBox.setContent(trafBox, 1); | ||||
|     H.Chunkify(moofBox.asBox(), moofBox.boxedSize(), myConn); | ||||
|   } | ||||
|    | ||||
|   std::string OutDashMP4::buildNalUnit(unsigned int len, const char * data){ | ||||
| 
 | ||||
|   std::string OutDashMP4::buildNalUnit(unsigned int len, const char *data){ | ||||
|     std::stringstream r; | ||||
|     r << (char)((len >> 24) & 0xFF); | ||||
|     r << (char)((len >> 16) & 0xFF); | ||||
|     r << (char)((len >> 8) & 0xFF); | ||||
|     r << (char)((len) & 0xFF); | ||||
|     r << (char)((len)&0xFF); | ||||
|     r << std::string(data, len); | ||||
|     return r.str(); | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   void OutDashMP4::sendMdat(uint32_t tid, uint32_t fragIndice){ | ||||
|     DTSC::Track & Trk = myMeta.tracks[tid]; | ||||
|     DTSC::Fragment & Frag = Trk.fragments[fragIndice]; | ||||
|     DTSC::Track &Trk = myMeta.tracks[tid]; | ||||
|     DTSC::Fragment &Frag = Trk.fragments[fragIndice]; | ||||
|     uint32_t size = 8 + Frag.getSize(); | ||||
|     if (Trk.codec == "H264"){ | ||||
|       MP4::AVCC avccBox; | ||||
|  | @ -271,14 +269,14 @@ namespace Mist{ | |||
|     mdatstr[0] = (char)((size >> 24) & 0xFF); | ||||
|     mdatstr[1] = (char)((size >> 16) & 0xFF); | ||||
|     mdatstr[2] = (char)((size >> 8) & 0xFF); | ||||
|     mdatstr[3] = (char)((size) & 0xFF); | ||||
|     mdatstr[3] = (char)((size)&0xFF); | ||||
|     H.Chunkify(mdatstr, 8, myConn); | ||||
|     std::string init; | ||||
|     if (Trk.codec == "H264"){ | ||||
|       MP4::AVCC avccBox; | ||||
|       avccBox.setPayload(Trk.init); | ||||
|       init = buildNalUnit(2, "\011\340"); | ||||
|       H.Chunkify(init, myConn);//09E0
 | ||||
|       H.Chunkify(init, myConn); // 09E0
 | ||||
|       init = buildNalUnit(avccBox.getSPSLen(), avccBox.getSPS()); | ||||
|       H.Chunkify(init, myConn); | ||||
|       init = buildNalUnit(avccBox.getPPSLen(), avccBox.getPPS()); | ||||
|  | @ -295,13 +293,13 @@ namespace Mist{ | |||
|         } | ||||
|       } | ||||
|     } | ||||
|     //we pull these values first, because seek() destroys our Trk reference
 | ||||
|     // we pull these values first, because seek() destroys our Trk reference
 | ||||
|     uint64_t startTime = Trk.getKey(Frag.getNumber()).getTime(); | ||||
|     targetTime = startTime + Frag.getDuration(); | ||||
|     HIGH_MSG("Starting playback from %llu to %llu", startTime, targetTime); | ||||
|     wantRequest = false; | ||||
|     parseData = true; | ||||
|     //select only the tid track, and seek to the start time
 | ||||
|     // select only the tid track, and seek to the start time
 | ||||
|     selectedTracks.clear(); | ||||
|     selectedTracks.insert(tid); | ||||
|     seek(startTime); | ||||
|  | @ -316,23 +314,23 @@ namespace Mist{ | |||
|       H.Clean(); | ||||
|       return; | ||||
|     } | ||||
|     char *  data; | ||||
|     char *data; | ||||
|     size_t dataLen; | ||||
|     thisPacket.getString("data", data, dataLen); | ||||
|     H.Chunkify(data, dataLen, myConn); | ||||
|   } | ||||
| 
 | ||||
|   /// Examines Trk and adds playable fragments from it to r.
 | ||||
|   void OutDashMP4::addSegmentTimeline(std::stringstream & r, DTSC::Track & Trk, bool live){ | ||||
|   void OutDashMP4::addSegmentTimeline(std::stringstream &r, DTSC::Track &Trk, bool live){ | ||||
|     std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); | ||||
|     bool first = true; | ||||
|     //skip the first two fragments if live
 | ||||
|     // skip the first two fragments if live
 | ||||
|     if (live && Trk.fragments.size() > 6){++(++it);} | ||||
|     for (; it != Trk.fragments.end(); it++){ | ||||
|       uint64_t starttime = Trk.getKey(it->getNumber()).getTime(); | ||||
|       uint32_t duration = it->getDuration(); | ||||
|       if (!duration){ | ||||
|         if (live){continue;}//skip last fragment when live
 | ||||
|         if (live){continue;}// skip last fragment when live
 | ||||
|         duration = Trk.lastms - starttime; | ||||
|       } | ||||
|       if (first){ | ||||
|  | @ -364,29 +362,44 @@ namespace Mist{ | |||
|         lastAudTime = myMeta.tracks[*it].lastms; | ||||
|         audInitTrack = *it; | ||||
|       } | ||||
|       if(myMeta.tracks[*it].codec == "subtitle"){ | ||||
|         subInitTrack = *it; | ||||
|       } | ||||
|       if (myMeta.tracks[*it].codec == "subtitle"){subInitTrack = *it;} | ||||
|     } | ||||
|     std::stringstream r; | ||||
| 
 | ||||
|     r << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl; | ||||
|     r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "; | ||||
|     r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " | ||||
|          "xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" " | ||||
|          "xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 " | ||||
|          "http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/" | ||||
|          "DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "; | ||||
|     if (myMeta.vod){ | ||||
|       r << "type=\"static\" mediaPresentationDuration=\"" << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - myMeta.tracks[getMainSelectedTrack()].firstms) << "\" minBufferTime=\"PT1.5S\" >" << std::endl; | ||||
|       r << "type=\"static\" mediaPresentationDuration=\"" | ||||
|         << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - | ||||
|                     myMeta.tracks[getMainSelectedTrack()].firstms) | ||||
|         << "\" minBufferTime=\"PT1.5S\" >" << std::endl; | ||||
|     }else{ | ||||
|       r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\"" << Util::getUTCString(Util::epoch() - myMeta.tracks[getMainSelectedTrack()].lastms/1000) << "\" " << "timeShiftBufferDepth=\"" << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - myMeta.tracks[getMainSelectedTrack()].firstms) << "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" publishTime=\"" << Util::getUTCString(Util::epoch()) << "\" >" << std::endl; | ||||
|       r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\"" | ||||
|         << Util::getUTCString(Util::epoch() - myMeta.tracks[getMainSelectedTrack()].lastms / 1000) << "\" " | ||||
|         << "timeShiftBufferDepth=\"" | ||||
|         << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - | ||||
|                     myMeta.tracks[getMainSelectedTrack()].firstms) | ||||
|         << "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" publishTime=\"" | ||||
|         << Util::getUTCString(Util::epoch()) << "\" >" << std::endl; | ||||
|     } | ||||
|     r << "  <ProgramInformation><Title>" << streamName << "</Title></ProgramInformation>" << std::endl; | ||||
|     r << "  <Period "; | ||||
|     if (myMeta.live){ | ||||
|       r << "start=\"0\" "; | ||||
|     } | ||||
|     if (myMeta.live){r << "start=\"0\" ";} | ||||
|     r << ">" << std::endl; | ||||
|     if (vidInitTrack){ | ||||
|       DTSC::Track & trackRef = myMeta.tracks[vidInitTrack]; | ||||
|       r << "    <AdaptationSet group=\"1\" id=\"9998\" mimeType=\"video/mp4\" width=\"" << trackRef.width << "\" height=\"" << trackRef.height << "\" frameRate=\"" << trackRef.fpks / 1000 << "\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">" << std::endl; | ||||
|       r << "      <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl; | ||||
|       DTSC::Track &trackRef = myMeta.tracks[vidInitTrack]; | ||||
|       r << "    <AdaptationSet group=\"1\" id=\"9998\" mimeType=\"video/mp4\" width=\"" | ||||
|         << trackRef.width << "\" height=\"" << trackRef.height << "\" frameRate=\"" | ||||
|         << trackRef.fpks / 1000 << "\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">" | ||||
|         << std::endl; | ||||
|       r << "      <SegmentTemplate timescale=\"1000\" " | ||||
|            "media=\"chunk_$RepresentationID$_$Time$.m4s\" " | ||||
|            "initialization=\"chunk_$RepresentationID$_init.m4s\">" | ||||
|         << std::endl; | ||||
|       r << "        <SegmentTimeline>" << std::endl; | ||||
|       addSegmentTimeline(r, trackRef, myMeta.live); | ||||
|       r << "        </SegmentTimeline>" << std::endl; | ||||
|  | @ -395,55 +408,64 @@ namespace Mist{ | |||
|         if (myMeta.tracks[*it].codec == "H264"){ | ||||
|           r << "      <Representation id=\"" << *it << "\" "; | ||||
|           r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" "; | ||||
|           //bandwidth is in bits per seconds, we have bytes, so times 8
 | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\" "; | ||||
|           // bandwidth is in bits per seconds, we have bytes, so times 8
 | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps * 8) << "\" "; | ||||
|           r << "/>" << std::endl; | ||||
|         } | ||||
|         if (myMeta.tracks[*it].codec == "HEVC"){ | ||||
|           r << "      <Representation "; | ||||
|           r << "id=\"" << *it << "\" "; | ||||
|           r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" "; | ||||
|           //bandwidth is in bits per seconds, we have bytes, so times 8
 | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\" "; | ||||
|           // bandwidth is in bits per seconds, we have bytes, so times 8
 | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps * 8) << "\" "; | ||||
|           r << "/>" << std::endl; | ||||
|         } | ||||
|       } | ||||
|       r << "    </AdaptationSet>" << std::endl; | ||||
|     } | ||||
|     if (audInitTrack){ | ||||
|       DTSC::Track & trackRef = myMeta.tracks[audInitTrack]; | ||||
|       r << "    <AdaptationSet group=\"2\" id=\"9999\" mimeType=\"audio/mp4\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\" >" << std::endl; | ||||
|       DTSC::Track &trackRef = myMeta.tracks[audInitTrack]; | ||||
|       r << "    <AdaptationSet group=\"2\" id=\"9999\" mimeType=\"audio/mp4\" " | ||||
|            "segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" " | ||||
|            "subsegmentStartsWithSAP=\"1\" >" | ||||
|         << std::endl; | ||||
|       r << "      <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>" << std::endl; | ||||
|       r << "      <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl; | ||||
|       r << "      <SegmentTemplate timescale=\"1000\" " | ||||
|            "media=\"chunk_$RepresentationID$_$Time$.m4s\" " | ||||
|            "initialization=\"chunk_$RepresentationID$_init.m4s\">" | ||||
|         << std::endl; | ||||
| 
 | ||||
|       r << "        <SegmentTimeline>" << std::endl; | ||||
|       addSegmentTimeline(r, trackRef, myMeta.live); | ||||
|       r << "        </SegmentTimeline>" << std::endl; | ||||
|       r << "      </SegmentTemplate>" << std::endl; | ||||
|   | ||||
| 
 | ||||
|       for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ | ||||
|         if (myMeta.tracks[*it].codec == "AAC" || myMeta.tracks[*it].codec == "MP3" || myMeta.tracks[*it].codec == "AC3"){ | ||||
|         if (myMeta.tracks[*it].codec == "AAC" || myMeta.tracks[*it].codec == "MP3" || | ||||
|             myMeta.tracks[*it].codec == "AC3"){ | ||||
|           r << "      <Representation id=\"" << *it << "\" "; | ||||
|           // (see RFC6381): sample description entry , ObjectTypeIndication [MP4RA, RFC], ObjectTypeIndication [MP4A ISO/IEC 14496-3:2009]
 | ||||
|           r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" "; | ||||
|           r << "audioSamplingRate=\"" << myMeta.tracks[*it].rate << "\" "; | ||||
|           //bandwidth is in bits per seconds, we have bytes, so times 8
 | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\">" << std::endl; | ||||
|           r << "        <AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"" << myMeta.tracks[*it].channels << "\" />" << std::endl; | ||||
|           // bandwidth is in bits per seconds, we have bytes, so times 8
 | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps * 8) << "\">" << std::endl; | ||||
|           r << "        <AudioChannelConfiguration " | ||||
|                "schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"" | ||||
|             << myMeta.tracks[*it].channels << "\" />" << std::endl; | ||||
|           r << "      </Representation>" << std::endl; | ||||
|         } | ||||
|       } | ||||
|       r << "    </AdaptationSet>" << std::endl; | ||||
|     } | ||||
| 
 | ||||
|     if(subInitTrack){ | ||||
|     if (subInitTrack){ | ||||
|       for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ | ||||
|         if(myMeta.tracks[*it].codec == "subtitle"){ | ||||
|         if (myMeta.tracks[*it].codec == "subtitle"){ | ||||
|           subInitTrack = *it; | ||||
|           std::string lang = (myMeta.tracks[*it].lang == "" ? "unknown" : myMeta.tracks[*it].lang); | ||||
|           r << "<AdaptationSet id=\"" << *it << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang <<  "\">"; | ||||
|           r << "<AdaptationSet id=\"" << *it << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang << "\">"; | ||||
|           r << " <Representation id=\"" << *it << "\" bandwidth=\"256\">"; | ||||
|           r <<   " <BaseURL>../../" << streamName << ".vtt?track=" << *it << "</BaseURL>"; | ||||
|           r << " <BaseURL>../../" << streamName << ".vtt?track=" << *it << "</BaseURL>"; | ||||
|           r << " </Representation></AdaptationSet>" << std::endl; | ||||
|         } | ||||
|       } | ||||
|  | @ -454,8 +476,8 @@ namespace Mist{ | |||
| 
 | ||||
|     return r.str(); | ||||
|   } | ||||
|    | ||||
|   void OutDashMP4::init(Util::Config * cfg){ | ||||
| 
 | ||||
|   void OutDashMP4::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "DASHMP4"; | ||||
|     capa["friendly"] = "DASH (fMP4) over HTTP"; | ||||
|  | @ -473,25 +495,27 @@ namespace Mist{ | |||
|     capa["methods"][0u]["handler"] = "http"; | ||||
|     capa["methods"][0u]["type"] = "dash/video/mp4"; | ||||
| 
 | ||||
|     //MP3 does not work in browsers
 | ||||
|     // MP3 does not work in browsers
 | ||||
|     capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); | ||||
|     //HEVC does not work in browsers
 | ||||
|     // HEVC does not work in browsers
 | ||||
|     capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); | ||||
|     capa["methods"][0u]["priority"] = 8; | ||||
| 
 | ||||
|     cfg->addOption("nonchunked", JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not send chunked, but buffer whole segments.\"}")); | ||||
|     cfg->addOption("nonchunked", | ||||
|                    JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not " | ||||
|                                     "send chunked, but buffer whole segments.\"}")); | ||||
|     capa["optional"]["nonchunked"]["name"] = "Send whole segments"; | ||||
|     capa["optional"]["nonchunked"]["help"] = "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance significantly, but increases compatibility somewhat."; | ||||
|     capa["optional"]["nonchunked"]["help"] = | ||||
|         "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance " | ||||
|         "significantly, but increases compatibility somewhat."; | ||||
|     capa["optional"]["nonchunked"]["option"] = "--nonchunked"; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   void OutDashMP4::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|      | ||||
| 
 | ||||
|     initialize(); | ||||
|     if (myMeta.live){ | ||||
|       updateMeta(); | ||||
|     } | ||||
|     if (myMeta.live){updateMeta();} | ||||
|     std::string url = H.url; | ||||
|     // Send a manifest for any URL with .mpd in the path
 | ||||
|     if (url.find(".mpd") != std::string::npos){ | ||||
|  | @ -499,7 +523,7 @@ namespace Mist{ | |||
|       H.SetHeader("Content-Type", "application/dash+xml"); | ||||
|       H.SetHeader("Cache-Control", "no-cache"); | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|  | @ -510,8 +534,8 @@ namespace Mist{ | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     //Not a manifest - either an init segment or data segment
 | ||||
|     size_t pos = url.find("chunk_") + 6;//find the track ID position
 | ||||
|     // Not a manifest - either an init segment or data segment
 | ||||
|     size_t pos = url.find("chunk_") + 6; // find the track ID position
 | ||||
|     uint32_t tid = atoi(url.substr(pos).c_str()); | ||||
|     if (!myMeta.tracks.count(tid)){ | ||||
|       H.Clean(); | ||||
|  | @ -523,7 +547,7 @@ namespace Mist{ | |||
|     H.SetHeader("Content-Type", "video/mp4"); | ||||
|     H.SetHeader("Cache-Control", "no-cache"); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); | ||||
|       return; | ||||
|  | @ -531,7 +555,7 @@ namespace Mist{ | |||
|     H.StartResponse(H, myConn, config->getBool("nonchunked")); | ||||
| 
 | ||||
|     if (url.find("init.m4s") != std::string::npos){ | ||||
|       //init segment
 | ||||
|       // init segment
 | ||||
|       if (myMeta.tracks[tid].type == "video"){ | ||||
|         H.Chunkify("\000\000\000\040ftypisom\000\000\000\000isomavc1mp42dash", 32, myConn); | ||||
|       }else{ | ||||
|  | @ -543,19 +567,19 @@ namespace Mist{ | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     //data segment
 | ||||
|     // data segment
 | ||||
|     pos = url.find("_", pos + 1) + 1; | ||||
|     uint64_t timeStamp = atoll(url.substr(pos).c_str()); | ||||
|     uint32_t fragIndice = myMeta.tracks[tid].timeToFragnum(timeStamp); | ||||
|     uint32_t fragNum = myMeta.tracks[tid].fragments[fragIndice].getNumber(); | ||||
|     HIGH_MSG("Getting T%llu for track %lu, indice %lu, number %lu", timeStamp, tid, fragIndice, fragNum); | ||||
|     if (myMeta.live && !myMeta.tracks[tid].fragments[fragIndice].getDuration()){  | ||||
|     if (myMeta.live && !myMeta.tracks[tid].fragments[fragIndice].getDuration()){ | ||||
|       size_t ctr = 0; | ||||
|       do { | ||||
|       do{ | ||||
|         if (ctr){Util::sleep(250);} | ||||
|         updateMeta(); | ||||
|         stats(); | ||||
|       }while(!myMeta.tracks[tid].fragments[fragIndice].getDuration() && ++ctr < 120); | ||||
|       }while (!myMeta.tracks[tid].fragments[fragIndice].getDuration() && ++ctr < 120); | ||||
|       if (!myMeta.tracks[tid].fragments[fragIndice].getDuration()){ | ||||
|         WARN_MSG("Sending zero-length segment. This should never happen."); | ||||
|         H.SendResponse("404", "Segment download error", myConn); | ||||
|  | @ -563,7 +587,7 @@ namespace Mist{ | |||
|         return; | ||||
|       } | ||||
|     } | ||||
|     DTSC::Track & Trk = myMeta.tracks[tid]; | ||||
|     DTSC::Track &Trk = myMeta.tracks[tid]; | ||||
|     H.Chunkify("\000\000\000\030stypmsdh\000\000\000\000msdhmsix", 24, myConn); | ||||
|     MP4::SIDX sidxBox; | ||||
|     sidxBox.setReferenceID(1); | ||||
|  | @ -585,6 +609,5 @@ namespace Mist{ | |||
|     sendMoof(tid, fragIndice); | ||||
|     sendMdat(tid, fragIndice); | ||||
|   } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,29 +1,30 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutDashMP4 : public HTTPOutput { | ||||
|     public: | ||||
|       OutDashMP4(Socket::Connection & conn); | ||||
|       ~OutDashMP4(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(){}; | ||||
|     protected: | ||||
|       void addSegmentTimeline(std::stringstream & r, DTSC::Track & Trk, bool live); | ||||
|       std::string makeTime(uint64_t time); | ||||
|       std::string buildManifest(); | ||||
|       void sendMoov(uint32_t trackid); | ||||
|       void sendMoof(uint32_t trackid, uint32_t fragIndice); | ||||
|       void sendMdat(uint32_t trackid, uint32_t fragIndice); | ||||
|       std::string buildNalUnit(unsigned int len, const char * data); | ||||
|       uint64_t targetTime; | ||||
| namespace Mist{ | ||||
|   class OutDashMP4 : public HTTPOutput{ | ||||
|   public: | ||||
|     OutDashMP4(Socket::Connection &conn); | ||||
|     ~OutDashMP4(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(){}; | ||||
| 
 | ||||
|       std::string h264init(const std::string & initData); | ||||
|       std::string h265init(const std::string & initData); | ||||
|   protected: | ||||
|     void addSegmentTimeline(std::stringstream &r, DTSC::Track &Trk, bool live); | ||||
|     std::string makeTime(uint64_t time); | ||||
|     std::string buildManifest(); | ||||
|     void sendMoov(uint32_t trackid); | ||||
|     void sendMoof(uint32_t trackid, uint32_t fragIndice); | ||||
|     void sendMdat(uint32_t trackid, uint32_t fragIndice); | ||||
|     std::string buildNalUnit(unsigned int len, const char *data); | ||||
|     uint64_t targetTime; | ||||
| 
 | ||||
|     std::string h264init(const std::string &initData); | ||||
|     std::string h265init(const std::string &initData); | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutDashMP4 mistOut; | ||||
|  |  | |||
|  | @ -1,29 +1,28 @@ | |||
| #include "output_dtsc.h" | ||||
| #include <cstdlib> | ||||
| #include <cstring> | ||||
| #include <mist/auth.h> | ||||
| #include <mist/bitfields.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/stream.h> | ||||
| #include <mist/triggers.h> | ||||
| #include <mist/auth.h> | ||||
| #include <mist/bitfields.h> | ||||
| #include <sys/stat.h> | ||||
| #include <cstring> | ||||
| #include <cstdlib> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutDTSC::OutDTSC(Socket::Connection & conn) : Output(conn) { | ||||
| namespace Mist{ | ||||
|   OutDTSC::OutDTSC(Socket::Connection &conn) : Output(conn){ | ||||
|     setBlocking(true); | ||||
|     JSON::Value prep; | ||||
|     prep["cmd"] = "hi"; | ||||
|     prep["version"] = "MistServer " PACKAGE_VERSION; | ||||
|     prep["pack_method"] = 2; | ||||
|     salt = Secure::md5("mehstuff"+JSON::Value((uint64_t)time(0)).asString()); | ||||
|     salt = Secure::md5("mehstuff" + JSON::Value((uint64_t)time(0)).asString()); | ||||
|     prep["salt"] = salt; | ||||
|     /// \todo Make this securererer.
 | ||||
|     sendCmd(prep); | ||||
|     lastActive = Util::epoch(); | ||||
|   } | ||||
| 
 | ||||
|   OutDTSC::~OutDTSC() {} | ||||
| 
 | ||||
|   OutDTSC::~OutDTSC(){} | ||||
| 
 | ||||
|   void OutDTSC::stats(bool force){ | ||||
|     unsigned long long int now = Util::epoch(); | ||||
|  | @ -40,7 +39,7 @@ namespace Mist { | |||
|     MEDIUM_MSG("Sending DTCM: %s", data.toString().c_str()); | ||||
|     unsigned long sendSize = data.packedSize(); | ||||
|     myConn.SendNow("DTCM"); | ||||
|     char sSize[4] = {0, 0, 0, 0}; | ||||
|     char sSize[4] ={0, 0, 0, 0}; | ||||
|     Bit::htobl(sSize, data.packedSize()); | ||||
|     myConn.SendNow(sSize, 4); | ||||
|     data.sendTo(myConn); | ||||
|  | @ -53,17 +52,18 @@ namespace Mist { | |||
|     sendCmd(err); | ||||
|   } | ||||
| 
 | ||||
|   void OutDTSC::init(Util::Config * cfg){ | ||||
|   void OutDTSC::init(Util::Config *cfg){ | ||||
|     Output::init(cfg); | ||||
|     capa["name"] = "DTSC"; | ||||
|     capa["friendly"] = "DTSC"; | ||||
|     capa["desc"] = "Real time streaming over DTSC (MistServer proprietary protocol, for efficient inter-server streaming)"; | ||||
|     capa["desc"] = "Real time streaming over DTSC (MistServer proprietary protocol, for efficient " | ||||
|                    "inter-server streaming)"; | ||||
|     capa["deps"] = ""; | ||||
|     capa["codecs"][0u][0u].append("+*"); | ||||
|     cfg->addConnectorOptions(4200, capa); | ||||
|     config = cfg; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   std::string OutDTSC::getStatsName(){ | ||||
|     if (pushing){ | ||||
|       return "INPUT"; | ||||
|  | @ -78,27 +78,31 @@ namespace Mist { | |||
|     unsigned long long seekPos = 0; | ||||
|     if (myMeta.live){ | ||||
|       long unsigned int mainTrack = getMainSelectedTrack(); | ||||
|       //cancel if there are no keys in the main track
 | ||||
|       // cancel if there are no keys in the main track
 | ||||
|       if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){return;} | ||||
|       //seek to the oldest keyframe
 | ||||
|       for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[mainTrack].keys.begin(); it != myMeta.tracks[mainTrack].keys.end(); ++it){ | ||||
|       // seek to the oldest keyframe
 | ||||
|       for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[mainTrack].keys.begin(); | ||||
|            it != myMeta.tracks[mainTrack].keys.end(); ++it){ | ||||
|         seekPos = it->getTime(); | ||||
|         bool good = true; | ||||
|         //check if all tracks have data for this point in time
 | ||||
|         // check if all tracks have data for this point in time
 | ||||
|         for (std::set<unsigned long>::iterator ti = selectedTracks.begin(); ti != selectedTracks.end(); ++ti){ | ||||
|           if (mainTrack == *ti){continue;}//skip self
 | ||||
|           if (mainTrack == *ti){continue;}// skip self
 | ||||
|           if (!myMeta.tracks.count(*ti)){ | ||||
|             HIGH_MSG("Skipping track %lu, not in tracks", *ti); | ||||
|             continue; | ||||
|           }//ignore missing tracks
 | ||||
|           }// ignore missing tracks
 | ||||
|           if (myMeta.tracks[*ti].lastms == myMeta.tracks[*ti].firstms){ | ||||
|             HIGH_MSG("Skipping track %lu, last equals first", *ti); | ||||
|             continue; | ||||
|           }//ignore point-tracks
 | ||||
|           if (myMeta.tracks[*ti].firstms > seekPos){good = false; break;} | ||||
|           }// ignore point-tracks
 | ||||
|           if (myMeta.tracks[*ti].firstms > seekPos){ | ||||
|             good = false; | ||||
|             break; | ||||
|           } | ||||
|           HIGH_MSG("Track %lu is good", *ti); | ||||
|         } | ||||
|         //if yes, seek here
 | ||||
|         // if yes, seek here
 | ||||
|         if (good){break;} | ||||
|       } | ||||
|     } | ||||
|  | @ -107,8 +111,8 @@ namespace Mist { | |||
|   } | ||||
| 
 | ||||
|   void OutDTSC::sendNext(){ | ||||
|     //If there are now more selectable tracks, select the new track and do a seek to the current timestamp
 | ||||
|     //Set sentHeader to false to force it to send init data
 | ||||
|     // If there are now more selectable tracks, select the new track and do a seek to the current
 | ||||
|     // timestamp Set sentHeader to false to force it to send init data
 | ||||
|     if (selectedTracks.size() < 2){ | ||||
|       static unsigned long long lastMeta = 0; | ||||
|       if (Util::epoch() > lastMeta + 5){ | ||||
|  | @ -130,18 +134,17 @@ namespace Mist { | |||
|   void OutDTSC::sendHeader(){ | ||||
|     sentHeader = true; | ||||
|     selectedTracks.clear(); | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       if (it->second.type == "video" || it->second.type == "audio"){ | ||||
|         selectedTracks.insert(it->first); | ||||
|       } | ||||
|     } | ||||
|     myMeta.send(myConn, true, selectedTracks); | ||||
|     if (myMeta.live){ | ||||
|       realTime = 0; | ||||
|     } | ||||
|     if (myMeta.live){realTime = 0;} | ||||
|   } | ||||
| 
 | ||||
|   void OutDTSC::onFail(const std::string & msg, bool critical){ | ||||
|   void OutDTSC::onFail(const std::string &msg, bool critical){ | ||||
|     JSON::Value err; | ||||
|     err["cmd"] = "error"; | ||||
|     err["msg"] = msg; | ||||
|  | @ -154,29 +157,48 @@ namespace Mist { | |||
|       if (myConn.Received().copy(4) == "DTCM"){ | ||||
|         // Command message
 | ||||
|         std::string toRec = myConn.Received().copy(8); | ||||
|         unsigned long rSize = Bit::btohl(toRec.c_str()+4); | ||||
|         if (!myConn.Received().available(8+rSize)){return;}//abort - not enough data yet
 | ||||
|         unsigned long rSize = Bit::btohl(toRec.c_str() + 4); | ||||
|         if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet
 | ||||
|         myConn.Received().remove(8); | ||||
|         std::string dataPacket = myConn.Received().remove(rSize); | ||||
|         DTSC::Scan dScan((char*)dataPacket.data(), rSize); | ||||
|         DTSC::Scan dScan((char *)dataPacket.data(), rSize); | ||||
|         INFO_MSG("Received DTCM: %s", dScan.asJSON().toString().c_str()); | ||||
|         if (dScan.getMember("cmd").asString() == "push"){handlePush(dScan); continue;} | ||||
|         if (dScan.getMember("cmd").asString() == "play"){handlePlay(dScan); continue;} | ||||
|         if (dScan.getMember("cmd").asString() == "ping"){sendOk("Pong!"); continue;} | ||||
|         if (dScan.getMember("cmd").asString() == "ok"){INFO_MSG("Ok: %s", dScan.getMember("msg").asString().c_str()); continue;} | ||||
|         if (dScan.getMember("cmd").asString() == "error"){ERROR_MSG("%s", dScan.getMember("msg").asString().c_str()); continue;} | ||||
|         if (dScan.getMember("cmd").asString() == "reset"){myMeta.reset(); sendOk("Internal state reset"); continue;} | ||||
|         if (dScan.getMember("cmd").asString() == "push"){ | ||||
|           handlePush(dScan); | ||||
|           continue; | ||||
|         } | ||||
|         if (dScan.getMember("cmd").asString() == "play"){ | ||||
|           handlePlay(dScan); | ||||
|           continue; | ||||
|         } | ||||
|         if (dScan.getMember("cmd").asString() == "ping"){ | ||||
|           sendOk("Pong!"); | ||||
|           continue; | ||||
|         } | ||||
|         if (dScan.getMember("cmd").asString() == "ok"){ | ||||
|           INFO_MSG("Ok: %s", dScan.getMember("msg").asString().c_str()); | ||||
|           continue; | ||||
|         } | ||||
|         if (dScan.getMember("cmd").asString() == "error"){ | ||||
|           ERROR_MSG("%s", dScan.getMember("msg").asString().c_str()); | ||||
|           continue; | ||||
|         } | ||||
|         if (dScan.getMember("cmd").asString() == "reset"){ | ||||
|           myMeta.reset(); | ||||
|           sendOk("Internal state reset"); | ||||
|           continue; | ||||
|         } | ||||
|         WARN_MSG("Unhandled DTCM command: '%s'", dScan.getMember("cmd").asString().c_str()); | ||||
|       }else if (myConn.Received().copy(4) == "DTSC"){ | ||||
|         //Header packet
 | ||||
|         // Header packet
 | ||||
|         if (!isPushing()){ | ||||
|           onFail("DTSC_HEAD ignored: you are not cleared for pushing data!", true); | ||||
|           return; | ||||
|         } | ||||
|         std::string toRec = myConn.Received().copy(8); | ||||
|         unsigned long rSize = Bit::btohl(toRec.c_str()+4); | ||||
|         if (!myConn.Received().available(8+rSize)){return;}//abort - not enough data yet
 | ||||
|         std::string dataPacket = myConn.Received().remove(8+rSize); | ||||
|         unsigned long rSize = Bit::btohl(toRec.c_str() + 4); | ||||
|         if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet
 | ||||
|         std::string dataPacket = myConn.Received().remove(8 + rSize); | ||||
|         DTSC::Packet metaPack(dataPacket.data(), dataPacket.size()); | ||||
|         myMeta.reinit(metaPack); | ||||
|         std::stringstream rep; | ||||
|  | @ -189,9 +211,9 @@ namespace Mist { | |||
|         } | ||||
|         // Data packet
 | ||||
|         std::string toRec = myConn.Received().copy(8); | ||||
|         unsigned long rSize = Bit::btohl(toRec.c_str()+4); | ||||
|         if (!myConn.Received().available(8+rSize)){return;}//abort - not enough data yet
 | ||||
|         std::string dataPacket = myConn.Received().remove(8+rSize); | ||||
|         unsigned long rSize = Bit::btohl(toRec.c_str() + 4); | ||||
|         if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet
 | ||||
|         std::string dataPacket = myConn.Received().remove(8 + rSize); | ||||
|         DTSC::Packet inPack(dataPacket.data(), dataPacket.size(), true); | ||||
|         if (!myMeta.tracks.count(inPack.getTrackId())){ | ||||
|           onFail("DTSC_V2 received for a track that was not announced in the DTSC_HEAD!", true); | ||||
|  | @ -199,14 +221,14 @@ namespace Mist { | |||
|         } | ||||
|         bufferLivePacket(inPack); | ||||
|       }else{ | ||||
|         //Invalid
 | ||||
|         // Invalid
 | ||||
|         onFail("Invalid packet header received. Aborting.", true); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void OutDTSC::handlePlay(DTSC::Scan & dScan){ | ||||
|   void OutDTSC::handlePlay(DTSC::Scan &dScan){ | ||||
|     streamName = dScan.getMember("stream").asString(); | ||||
|     Util::sanitizeName(streamName); | ||||
|     Util::Config::streamName = streamName; | ||||
|  | @ -215,7 +237,7 @@ namespace Mist { | |||
|     setBlocking(false); | ||||
|   } | ||||
| 
 | ||||
|   void OutDTSC::handlePush(DTSC::Scan & dScan){ | ||||
|   void OutDTSC::handlePush(DTSC::Scan &dScan){ | ||||
|     streamName = dScan.getMember("stream").asString(); | ||||
|     std::string passString = dScan.getMember("password").asString(); | ||||
|     Util::sanitizeName(streamName); | ||||
|  | @ -227,6 +249,4 @@ namespace Mist { | |||
|     sendOk("You're cleared for pushing! DTSC_HEAD please?"); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,29 +1,29 @@ | |||
| #include "output.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
| 
 | ||||
|  class OutDTSC : public Output { | ||||
|     public: | ||||
|       OutDTSC(Socket::Connection & conn); | ||||
|       ~OutDTSC(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onRequest(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|       void initialSeek(); | ||||
|       void onFail(const std::string & msg, bool critical = false); | ||||
|       void stats(bool force = false); | ||||
|       void sendCmd(const JSON::Value &data); | ||||
|       void sendOk(const std::string &msg); | ||||
|     private: | ||||
|       unsigned int lastActive;///<Time of last sending of data.
 | ||||
|       std::string getStatsName(); | ||||
|       std::string salt; | ||||
|       void handlePush(DTSC::Scan & dScan); | ||||
|       void handlePlay(DTSC::Scan & dScan); | ||||
|       unsigned long long fastAsPossibleTime; | ||||
|   class OutDTSC : public Output{ | ||||
|   public: | ||||
|     OutDTSC(Socket::Connection &conn); | ||||
|     ~OutDTSC(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onRequest(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
|     void initialSeek(); | ||||
|     void onFail(const std::string &msg, bool critical = false); | ||||
|     void stats(bool force = false); | ||||
|     void sendCmd(const JSON::Value &data); | ||||
|     void sendOk(const std::string &msg); | ||||
| 
 | ||||
|   private: | ||||
|     unsigned int lastActive; ///< Time of last sending of data.
 | ||||
|     std::string getStatsName(); | ||||
|     std::string salt; | ||||
|     void handlePush(DTSC::Scan &dScan); | ||||
|     void handlePlay(DTSC::Scan &dScan); | ||||
|     unsigned long long fastAsPossibleTime; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutDTSC mistOut; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| #include "output_ebml.h" | ||||
| #include <mist/ebml_socketglue.h> | ||||
| #include <mist/riff.h> | ||||
| #include <mist/opus.h> | ||||
| #include <mist/riff.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|  | @ -16,9 +16,7 @@ namespace Mist{ | |||
|     if (config->getString("target").size()){ | ||||
|       if (config->getString("target").find(".webm") != std::string::npos){doctype = "webm";} | ||||
|       initialize(); | ||||
|       if (myMeta.vod){ | ||||
|         calcVodSizes(); | ||||
|       } | ||||
|       if (myMeta.vod){calcVodSizes();} | ||||
|       if (!streamName.size()){ | ||||
|         WARN_MSG("Recording unconnected EBML output to file! Cancelled."); | ||||
|         conn.close(); | ||||
|  | @ -76,8 +74,10 @@ namespace Mist{ | |||
|     capa["methods"][0u]["handler"] = "http"; | ||||
|     capa["methods"][0u]["type"] = "html5/video/webm"; | ||||
|     capa["methods"][0u]["priority"] = 9; | ||||
|     //Browsers only support VP8/VP9/Opus codecs, except Chrome which is more lenient.
 | ||||
|     JSON::Value blacklistNonChrome = JSON::fromString("[[\"blacklist\", [\"Mozilla/\"]], [\"whitelist\",[\"Chrome\",\"Chromium\"]], [\"blacklist\",[\"Edge\",\"OPR/\"]], [\"blacklist\",[\"Android\"]]]"); | ||||
|     // Browsers only support VP8/VP9/Opus codecs, except Chrome which is more lenient.
 | ||||
|     JSON::Value blacklistNonChrome = JSON::fromString( | ||||
|         "[[\"blacklist\", [\"Mozilla/\"]], [\"whitelist\",[\"Chrome\",\"Chromium\"]], " | ||||
|         "[\"blacklist\",[\"Edge\",\"OPR/\"]], [\"blacklist\",[\"Android\"]]]"); | ||||
|     capa["exceptions"]["codec:H264"] = blacklistNonChrome; | ||||
|     capa["exceptions"]["codec:HEVC"] = blacklistNonChrome; | ||||
|     capa["exceptions"]["codec:theora"] = blacklistNonChrome; | ||||
|  | @ -109,14 +109,12 @@ namespace Mist{ | |||
|   /// Bases the calculation on the currently selected tracks and the given start/end time for the cluster.
 | ||||
|   uint32_t OutEBML::clusterSize(uint64_t start, uint64_t end){ | ||||
|     uint32_t sendLen = EBML::sizeElemUInt(EBML::EID_TIMECODE, start); | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); | ||||
|          it != selectedTracks.end(); it++){ | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       DTSC::Track &thisTrack = myMeta.tracks[*it]; | ||||
|       uint32_t firstPart = 0; | ||||
|       unsigned long long int prevParts = 0; | ||||
|       uint64_t curMS = 0; | ||||
|       for (std::deque<DTSC::Key>::iterator it2 = thisTrack.keys.begin(); | ||||
|            it2 != thisTrack.keys.end(); it2++){ | ||||
|       for (std::deque<DTSC::Key>::iterator it2 = thisTrack.keys.begin(); it2 != thisTrack.keys.end(); it2++){ | ||||
|         if (it2->getTime() > start && it2 != thisTrack.keys.begin()){break;} | ||||
|         firstPart += prevParts; | ||||
|         prevParts = it2->getParts(); | ||||
|  | @ -140,21 +138,23 @@ namespace Mist{ | |||
|       if (liveSeek()){return;} | ||||
|       currentClusterTime = thisPacket.getTime(); | ||||
|       if (myMeta.vod){ | ||||
|         //In case of VoD, clusters are aligned with the main track fragments
 | ||||
|         //EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds.
 | ||||
|         // In case of VoD, clusters are aligned with the main track fragments
 | ||||
|         // EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds.
 | ||||
|         DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; | ||||
|         uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime); | ||||
|         newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration(); | ||||
|         //Limit clusters to 30s, and the last fragment should always be 30s, just in case.
 | ||||
|         newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + | ||||
|                          Trk.fragments[fragIndice].getDuration(); | ||||
|         // Limit clusters to 30s, and the last fragment should always be 30s, just in case.
 | ||||
|         if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){ | ||||
|           newClusterTime = currentClusterTime + 30000; | ||||
|         } | ||||
|         EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime)); | ||||
|         EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, | ||||
|                     fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime)); | ||||
|       }else{ | ||||
|         //In live, clusters are aligned with the lookAhead time
 | ||||
|         newClusterTime = currentClusterTime+(needsLookAhead?needsLookAhead:1); | ||||
|         //EXCEPT if there's a keyframe within the lookAhead window, then align to that keyframe instead
 | ||||
|         //This makes sure that inlineRestartCapable works as intended
 | ||||
|         // In live, clusters are aligned with the lookAhead time
 | ||||
|         newClusterTime = currentClusterTime + (needsLookAhead ? needsLookAhead : 1); | ||||
|         // EXCEPT if there's a keyframe within the lookAhead window, then align to that keyframe
 | ||||
|         // instead This makes sure that inlineRestartCapable works as intended
 | ||||
|         uint64_t nxtKTime = nextKeyTime(); | ||||
|         if (nxtKTime && nxtKTime < newClusterTime){newClusterTime = nxtKTime;} | ||||
|       } | ||||
|  | @ -204,7 +204,7 @@ namespace Mist{ | |||
|       if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);} | ||||
|     } | ||||
|     if (Trk.codec == "opus" && Trk.init.size() > 11){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48); | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48); | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000); | ||||
|     } | ||||
|     if (Trk.type == "video"){ | ||||
|  | @ -222,9 +222,7 @@ namespace Mist{ | |||
|       subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); | ||||
|       sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); | ||||
|     } | ||||
|     if (Trk.type == "meta"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3); | ||||
|     } | ||||
|     if (Trk.type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);} | ||||
|     sendLen += subLen; | ||||
| 
 | ||||
|     // Now actually send.
 | ||||
|  | @ -235,15 +233,14 @@ namespace Mist{ | |||
|     EBML::sendElemStr(myConn, EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und"); | ||||
|     EBML::sendElemUInt(myConn, EBML::EID_FLAGLACING, 0); | ||||
|     if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){ | ||||
|       std::string init = | ||||
|           RIFF::fmt::generate(((Trk.codec == "ALAW") ? 6 : 7), Trk.channels, Trk.rate, Trk.bps, | ||||
|                               Trk.channels * (Trk.size << 3), Trk.size); | ||||
|       std::string init = RIFF::fmt::generate(((Trk.codec == "ALAW") ? 6 : 7), Trk.channels, Trk.rate, | ||||
|                                              Trk.bps, Trk.channels * (Trk.size << 3), Trk.size); | ||||
|       EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, init.substr(8)); | ||||
|     }else{ | ||||
|       if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);} | ||||
|     } | ||||
|     if (Trk.codec == "opus"){ | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_SEEKPREROLL, 80000000); | ||||
|     } | ||||
|     if (Trk.type == "video"){ | ||||
|  | @ -261,9 +258,7 @@ namespace Mist{ | |||
|       EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size); | ||||
|     } | ||||
|     if (Trk.type == "meta"){ | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3); | ||||
|     } | ||||
|     if (Trk.type == "meta"){EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);} | ||||
|   } | ||||
| 
 | ||||
|   uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){ | ||||
|  | @ -281,7 +276,7 @@ namespace Mist{ | |||
|       if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);} | ||||
|     } | ||||
|     if (Trk.codec == "opus"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48); | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48); | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000); | ||||
|     } | ||||
|     if (Trk.type == "video"){ | ||||
|  | @ -299,9 +294,7 @@ namespace Mist{ | |||
|       subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); | ||||
|       sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); | ||||
|     } | ||||
|     if (Trk.type == "meta"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3); | ||||
|     } | ||||
|     if (Trk.type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);} | ||||
|     sendLen += subLen; | ||||
|     return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen; | ||||
|   } | ||||
|  | @ -309,38 +302,33 @@ namespace Mist{ | |||
|   void OutEBML::sendHeader(){ | ||||
|     double duration = 0; | ||||
|     DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; | ||||
|     if (myMeta.vod){ | ||||
|       duration = Trk.lastms - Trk.firstms; | ||||
|     } | ||||
|     if (myMeta.live){ | ||||
|       needsLookAhead = 420; | ||||
|     } | ||||
|     //EBML header and Segment
 | ||||
|     if (myMeta.vod){duration = Trk.lastms - Trk.firstms;} | ||||
|     if (myMeta.live){needsLookAhead = 420;} | ||||
|     // EBML header and Segment
 | ||||
|     EBML::sendElemEBML(myConn, doctype); | ||||
|     EBML::sendElemHead(myConn, EBML::EID_SEGMENT, segmentSize); // Default = Unknown size
 | ||||
|     if (myMeta.vod){ | ||||
|       //SeekHead
 | ||||
|       // SeekHead
 | ||||
|       EBML::sendElemHead(myConn, EBML::EID_SEEKHEAD, seekSize); | ||||
|       EBML::sendElemSeek(myConn, EBML::EID_INFO, seekheadSize); | ||||
|       EBML::sendElemSeek(myConn, EBML::EID_TRACKS, seekheadSize + infoSize); | ||||
|       EBML::sendElemSeek(myConn, EBML::EID_CUES, seekheadSize + infoSize + tracksSize); | ||||
|     } | ||||
|     //Info
 | ||||
|     // Info
 | ||||
|     EBML::sendElemInfo(myConn, "MistServer " PACKAGE_VERSION, duration); | ||||
|     //Tracks
 | ||||
|     // Tracks
 | ||||
|     uint32_t trackSizes = 0; | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); | ||||
|          it != selectedTracks.end(); it++){ | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       trackSizes += sizeElemTrackEntry(myMeta.tracks[*it]); | ||||
|     } | ||||
|     EBML::sendElemHead(myConn, EBML::EID_TRACKS, trackSizes); | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); | ||||
|          it != selectedTracks.end(); it++){ | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       sendElemTrackEntry(myMeta.tracks[*it]); | ||||
|     } | ||||
|     if (myMeta.vod){ | ||||
|       EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize); | ||||
|       uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize); | ||||
|       uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + | ||||
|                             EBML::sizeElemHead(EBML::EID_CUES, cuesSize); | ||||
|       for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ | ||||
|         EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0); | ||||
|         tmpsegSize += it->second; | ||||
|  | @ -358,7 +346,9 @@ namespace Mist{ | |||
|       seek(0); | ||||
|       return; | ||||
|     } | ||||
|     uint64_t headerSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + seekheadSize + infoSize + tracksSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize) + cuesSize; | ||||
|     uint64_t headerSize = EBML::sizeElemEBML(doctype) + | ||||
|                           EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + seekheadSize + infoSize + | ||||
|                           tracksSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize) + cuesSize; | ||||
|     if (startPos < headerSize){ | ||||
|       HIGH_MSG("Seek went into or before header"); | ||||
|       seek(0); | ||||
|  | @ -366,7 +356,7 @@ namespace Mist{ | |||
|       return; | ||||
|     } | ||||
|     startPos -= headerSize; | ||||
|     sentHeader = true;//skip the header
 | ||||
|     sentHeader = true; // skip the header
 | ||||
|     DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; | ||||
|     for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ | ||||
|       VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos); | ||||
|  | @ -379,12 +369,12 @@ namespace Mist{ | |||
|       } | ||||
|       startPos -= it->second; | ||||
|     } | ||||
|     //End of file. This probably won't work right, but who cares, it's the end of the file.
 | ||||
|     // End of file. This probably won't work right, but who cares, it's the end of the file.
 | ||||
|   } | ||||
| 
 | ||||
|   void OutEBML::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.Clean(); | ||||
|       H.setCORSHeaders(); | ||||
|       H.SetHeader("Content-Type", "video/MP4"); | ||||
|  | @ -398,23 +388,23 @@ namespace Mist{ | |||
|       doctype = "matroska"; | ||||
|     } | ||||
| 
 | ||||
|     //Calculate the sizes of various parts, if we're VoD.
 | ||||
|     // Calculate the sizes of various parts, if we're VoD.
 | ||||
|     uint64_t totalSize = 0; | ||||
|     if (myMeta.vod){ | ||||
|       calcVodSizes(); | ||||
|       //We now know the full size of the segment, thus can calculate the total size
 | ||||
|       // We now know the full size of the segment, thus can calculate the total size
 | ||||
|       totalSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + segmentSize; | ||||
|     } | ||||
| 
 | ||||
|     uint64_t byteEnd = totalSize-1; | ||||
|     uint64_t byteEnd = totalSize - 1; | ||||
|     uint64_t byteStart = 0; | ||||
|      | ||||
| 
 | ||||
|     /*LTS-START*/ | ||||
|     // allow setting of max lead time through buffer variable.
 | ||||
|     // max lead time is set in MS, but the variable is in integer seconds for simplicity.
 | ||||
|     if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;} | ||||
|     //allow setting of play back rate through buffer variable.
 | ||||
|     //play back rate is set in MS per second, but the variable is a simple multiplier.
 | ||||
|     // allow setting of play back rate through buffer variable.
 | ||||
|     // play back rate is set in MS per second, but the variable is a simple multiplier.
 | ||||
|     if (H.GetVar("rate") != ""){ | ||||
|       long long int multiplier = JSON::Value(H.GetVar("rate")).asInt(); | ||||
|       if (multiplier){ | ||||
|  | @ -445,12 +435,10 @@ namespace Mist{ | |||
|         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.SetHeader("Content-Type", "video/webm"); | ||||
|     if (myMeta.vod){ | ||||
|       H.SetHeader("Accept-Ranges", "bytes, parsec"); | ||||
|     } | ||||
|     if (myMeta.vod){H.SetHeader("Accept-Ranges", "bytes, parsec");} | ||||
|     if (rangeType != ' '){ | ||||
|       if (!byteEnd){ | ||||
|         if (rangeType == 'p'){ | ||||
|  | @ -469,16 +457,14 @@ namespace Mist{ | |||
|         H.SetHeader("Content-Range", rangeReply.str()); | ||||
|         /// \todo Switch to chunked?
 | ||||
|         H.SendResponse("206", "Partial content", myConn); | ||||
|         //H.StartResponse("206", "Partial content", HTTP_R, conn);
 | ||||
|         // H.StartResponse("206", "Partial content", HTTP_R, conn);
 | ||||
|         byteSeek(byteStart); | ||||
|       } | ||||
|     }else{ | ||||
|       if (myMeta.vod){ | ||||
|         H.SetHeader("Content-Length", byteEnd - byteStart + 1); | ||||
|       } | ||||
|       if (myMeta.vod){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} | ||||
|       /// \todo Switch to chunked?
 | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       //HTTP_S.StartResponse(HTTP_R, conn);
 | ||||
|       // HTTP_S.StartResponse(HTTP_R, conn);
 | ||||
|     } | ||||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|  | @ -486,74 +472,73 @@ namespace Mist{ | |||
| 
 | ||||
|   void OutEBML::calcVodSizes(){ | ||||
|     if (segmentSize != 0xFFFFFFFFFFFFFFFFull){ | ||||
|       //Already calculated
 | ||||
|       // Already calculated
 | ||||
|       return; | ||||
|     } | ||||
|     DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; | ||||
|     double duration = Trk.lastms - Trk.firstms; | ||||
|     //Calculate the segment size
 | ||||
|     //Segment contains SeekHead, Info, Tracks, Cues (in that order)
 | ||||
|     //Howeveer, SeekHead is dependent on Info/Tracks sizes, so we calculate those first.
 | ||||
|     //Calculating Info size
 | ||||
|     // Calculate the segment size
 | ||||
|     // Segment contains SeekHead, Info, Tracks, Cues (in that order)
 | ||||
|     // Howeveer, SeekHead is dependent on Info/Tracks sizes, so we calculate those first.
 | ||||
|     // Calculating Info size
 | ||||
|     infoSize = EBML::sizeElemInfo("MistServer " PACKAGE_VERSION, duration); | ||||
|     //Calculating Tracks size
 | ||||
|     // Calculating Tracks size
 | ||||
|     tracksSize = 0; | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); | ||||
|          it != selectedTracks.end(); it++){ | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       tracksSize += sizeElemTrackEntry(myMeta.tracks[*it]); | ||||
|     } | ||||
|     tracksSize += EBML::sizeElemHead(EBML::EID_TRACKS, tracksSize); | ||||
|     //Calculating SeekHead size
 | ||||
|     //Positions are relative to the first Segment, byte 0 = first byte of contents of Segment.
 | ||||
|     //Tricky starts here: the size of the SeekHead element is dependent on the seek offsets contained inside,
 | ||||
|     //which are in turn dependent on the size of the SeekHead element. Fun times! We loop until it stabilizes.
 | ||||
|     // Calculating SeekHead size
 | ||||
|     // Positions are relative to the first Segment, byte 0 = first byte of contents of Segment.
 | ||||
|     // Tricky starts here: the size of the SeekHead element is dependent on the seek offsets contained inside,
 | ||||
|     // which are in turn dependent on the size of the SeekHead element. Fun times! We loop until it stabilizes.
 | ||||
|     uint32_t oldseekSize = 0; | ||||
|     do { | ||||
|     do{ | ||||
|       oldseekSize = seekSize; | ||||
|       seekSize = EBML::sizeElemSeek(EBML::EID_INFO, seekheadSize) + | ||||
|                  EBML::sizeElemSeek(EBML::EID_TRACKS, seekheadSize + infoSize) +  | ||||
|                  EBML::sizeElemSeek(EBML::EID_TRACKS, seekheadSize + infoSize) + | ||||
|                  EBML::sizeElemSeek(EBML::EID_CUES, seekheadSize + infoSize + tracksSize); | ||||
|       seekheadSize = EBML::sizeElemHead(EBML::EID_SEEKHEAD, seekSize) + seekSize; | ||||
|     }while(seekSize != oldseekSize); | ||||
|     //The Cues are tricky: the Cluster offsets are dependent on the size of Cues itself.
 | ||||
|     //Which, in turn, is dependent on the Cluster offsets.
 | ||||
|     //We make this a bit easier by pre-calculating the sizes of all clusters first
 | ||||
|     }while (seekSize != oldseekSize); | ||||
|     // The Cues are tricky: the Cluster offsets are dependent on the size of Cues itself.
 | ||||
|     // Which, in turn, is dependent on the Cluster offsets.
 | ||||
|     // We make this a bit easier by pre-calculating the sizes of all clusters first
 | ||||
|     uint64_t fragNo = 0; | ||||
|     for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){ | ||||
|       uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime(); | ||||
|       uint64_t clusterEnd = clusterStart + it->getDuration(); | ||||
|       //The first fragment always starts at time 0, even if the main track does not.
 | ||||
|       // The first fragment always starts at time 0, even if the main track does not.
 | ||||
|       if (!fragNo){clusterStart = 0;} | ||||
|       uint64_t clusterTmpEnd = clusterEnd; | ||||
|       do { | ||||
|       do{ | ||||
|         clusterTmpEnd = clusterEnd; | ||||
|         //The last fragment always ends at the end, even if the main track does not.
 | ||||
|         // The last fragment always ends at the end, even if the main track does not.
 | ||||
|         if (fragNo == Trk.fragments.size() - 1){clusterTmpEnd = clusterStart + 30000;} | ||||
|         //Limit clusters to 30 seconds.
 | ||||
|         // Limit clusters to 30 seconds.
 | ||||
|         if (clusterTmpEnd - clusterStart > 30000){clusterTmpEnd = clusterStart + 30000;} | ||||
|         uint64_t cSize = clusterSize(clusterStart, clusterTmpEnd); | ||||
|         clusterSizes[clusterStart] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize); | ||||
|         clusterStart = clusterTmpEnd;//Continue at the end of this cluster, if continuing.
 | ||||
|       }while(clusterTmpEnd < clusterEnd); | ||||
|         clusterStart = clusterTmpEnd; // Continue at the end of this cluster, if continuing.
 | ||||
|       }while (clusterTmpEnd < clusterEnd); | ||||
|       ++fragNo; | ||||
|     } | ||||
|     //Calculating Cues size
 | ||||
|     //We also calculate Clusters here: Clusters are grouped by fragments of the main track.
 | ||||
|     //CueClusterPosition uses the same offsets as SeekPosition.
 | ||||
|     //CueRelativePosition is the offset from that Cluster's first content byte.
 | ||||
|     //All this uses the same technique as above. More fun times!
 | ||||
|     // Calculating Cues size
 | ||||
|     // We also calculate Clusters here: Clusters are grouped by fragments of the main track.
 | ||||
|     // CueClusterPosition uses the same offsets as SeekPosition.
 | ||||
|     // CueRelativePosition is the offset from that Cluster's first content byte.
 | ||||
|     // All this uses the same technique as above. More fun times!
 | ||||
|     uint32_t oldcuesSize = 0; | ||||
|     do { | ||||
|     do{ | ||||
|       oldcuesSize = cuesSize; | ||||
|       segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize); | ||||
|       segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + | ||||
|                     EBML::sizeElemHead(EBML::EID_CUES, cuesSize); | ||||
|       uint32_t cuesInside = 0; | ||||
|       for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ | ||||
|         cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0); | ||||
|         segmentSize += it->second; | ||||
|       } | ||||
|       cuesSize = cuesInside; | ||||
|     }while(cuesSize != oldcuesSize); | ||||
|     }while (cuesSize != oldcuesSize); | ||||
|   } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,28 +10,29 @@ namespace Mist{ | |||
|     void sendNext(); | ||||
|     virtual void sendHeader(); | ||||
|     uint32_t clusterSize(uint64_t start, uint64_t end); | ||||
| 
 | ||||
|   protected: | ||||
|     virtual bool inlineRestartCapable() const{return true;} | ||||
| 
 | ||||
|   private: | ||||
|     bool isRecording(); | ||||
|     std::string doctype; | ||||
|     void sendElemTrackEntry(const DTSC::Track & Trk); | ||||
|     uint32_t sizeElemTrackEntry(const DTSC::Track & Trk); | ||||
|     std::string trackCodecID(const DTSC::Track & Trk); | ||||
|     void sendElemTrackEntry(const DTSC::Track &Trk); | ||||
|     uint32_t sizeElemTrackEntry(const DTSC::Track &Trk); | ||||
|     std::string trackCodecID(const DTSC::Track &Trk); | ||||
|     uint64_t currentClusterTime; | ||||
|     uint64_t newClusterTime; | ||||
|     //VoD-only
 | ||||
|     // VoD-only
 | ||||
|     void calcVodSizes(); | ||||
|     uint64_t segmentSize;//size of complete segment contents (excl. header)
 | ||||
|     uint32_t tracksSize;//size of Tracks (incl. header)
 | ||||
|     uint32_t infoSize;//size of Info (incl. header)
 | ||||
|     uint32_t cuesSize;//size of Cues (excl. header)
 | ||||
|     uint32_t seekheadSize;//size of SeekHead (incl. header)
 | ||||
|     uint32_t seekSize;//size of contents of SeekHead (excl. header)
 | ||||
|     std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters by start time (incl. header)
 | ||||
|     uint64_t segmentSize;                      // size of complete segment contents (excl. header)
 | ||||
|     uint32_t tracksSize;                       // size of Tracks (incl. header)
 | ||||
|     uint32_t infoSize;                         // size of Info (incl. header)
 | ||||
|     uint32_t cuesSize;                         // size of Cues (excl. header)
 | ||||
|     uint32_t seekheadSize;                     // size of SeekHead (incl. header)
 | ||||
|     uint32_t seekSize;                         // size of contents of SeekHead (excl. header)
 | ||||
|     std::map<uint64_t, uint64_t> clusterSizes; // sizes of Clusters by start time (incl. header)
 | ||||
|     void byteSeek(uint64_t startPos); | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutEBML mistOut; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,12 +1,10 @@ | |||
| #include "output_h264.h" | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/bitfields.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   OutH264::OutH264(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     if (targetParams.count("keysonly")){ | ||||
|       keysOnly = 1; | ||||
|     } | ||||
|     if (targetParams.count("keysonly")){keysOnly = 1;} | ||||
|     if (config->getString("target").size()){ | ||||
|       if (!streamName.size()){ | ||||
|         WARN_MSG("Recording unconnected H264 output to file! Cancelled."); | ||||
|  | @ -22,7 +20,8 @@ namespace Mist{ | |||
|       if (connectToFile(config->getString("target"))){ | ||||
|         parseData = true; | ||||
|         wantRequest = false; | ||||
|         INFO_MSG("Recording %s to %s in H264 format", streamName.c_str(), config->getString("target").c_str()); | ||||
|         INFO_MSG("Recording %s to %s in H264 format", streamName.c_str(), | ||||
|                  config->getString("target").c_str()); | ||||
|       }else{ | ||||
|         conn.close(); | ||||
|       } | ||||
|  | @ -56,10 +55,10 @@ namespace Mist{ | |||
| 
 | ||||
|     unsigned int i = 0; | ||||
|     while (i + 4 < len){ | ||||
|       uint32_t ThisNaluSize = Bit::btohl(dataPointer+i); | ||||
|       uint32_t ThisNaluSize = Bit::btohl(dataPointer + i); | ||||
|       myConn.SendNow("\000\000\000\001", 4); | ||||
|       myConn.SendNow(dataPointer + i + 4, ThisNaluSize); | ||||
|       i += ThisNaluSize+4; | ||||
|       i += ThisNaluSize + 4; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -75,7 +74,7 @@ namespace Mist{ | |||
| 
 | ||||
|   void OutH264::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|     //Set mode to key frames only
 | ||||
|     // Set mode to key frames only
 | ||||
|     keysOnly = (H.GetVar("keysonly") != ""); | ||||
|     H.Clean(); | ||||
|     H.SetHeader("Content-Type", "video/H264"); | ||||
|  | @ -89,5 +88,4 @@ namespace Mist{ | |||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -13,6 +13,6 @@ namespace Mist{ | |||
|     bool isRecording(); | ||||
|     bool keysOnly; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutH264 mistOut; | ||||
|  |  | |||
|  | @ -1,26 +1,27 @@ | |||
| #include "output_hds.h" | ||||
| #include <mist/stream.h> | ||||
| #include <unistd.h> | ||||
| #include <mist/amf.h> | ||||
| #include <mist/mp4_adobe.h> | ||||
| #include <mist/stream.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
| 
 | ||||
| namespace Mist { | ||||
|    | ||||
|   void OutHDS::getTracks(){ | ||||
|     /// \todo Why do we have only one audio track option?
 | ||||
|     videoTracks.clear(); | ||||
|     audioTrack = 0; | ||||
|     JSON::Value & vidCapa = capa["codecs"][0u][0u]; | ||||
|     JSON::Value & audCapa = capa["codecs"][0u][1u]; | ||||
|     for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ | ||||
|       jsonForEach(vidCapa, itb) { | ||||
|     JSON::Value &vidCapa = capa["codecs"][0u][0u]; | ||||
|     JSON::Value &audCapa = capa["codecs"][0u][1u]; | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       jsonForEach(vidCapa, itb){ | ||||
|         if (it->second.codec == (*itb).asStringRef()){ | ||||
|           videoTracks.insert(it->first); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       if (!audioTrack){ | ||||
|         jsonForEach(audCapa, itb) {  | ||||
|         jsonForEach(audCapa, itb){ | ||||
|           if (it->second.codec == (*itb).asStringRef()){ | ||||
|             audioTrack = it->first; | ||||
|             break; | ||||
|  | @ -29,29 +30,29 @@ namespace Mist { | |||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   ///\brief Builds a bootstrap for use in HTTP Dynamic streaming.
 | ||||
|   ///\param tid The track this bootstrap is generated for.
 | ||||
|   ///\return The generated bootstrap.
 | ||||
|   std::string OutHDS::dynamicBootstrap(int tid){ | ||||
|     updateMeta(); | ||||
|     std::string empty; | ||||
|      | ||||
| 
 | ||||
|     MP4::ASRT asrt; | ||||
|     asrt.setUpdate(false); | ||||
|     asrt.setVersion(1); | ||||
|     //asrt.setQualityEntry(empty, 0);
 | ||||
|     // asrt.setQualityEntry(empty, 0);
 | ||||
|     if (myMeta.live){ | ||||
|       asrt.setSegmentRun(1, 4294967295ul, 0); | ||||
|     }else{ | ||||
|       asrt.setSegmentRun(1, myMeta.tracks[tid].fragments.size(), 0); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     MP4::AFRT afrt; | ||||
|     afrt.setUpdate(false); | ||||
|     afrt.setVersion(1); | ||||
|     afrt.setTimeScale(1000); | ||||
|     //afrt.setQualityEntry(empty, 0);
 | ||||
|     // afrt.setQualityEntry(empty, 0);
 | ||||
|     MP4::afrt_runtable afrtrun; | ||||
|     int i = 0; | ||||
|     int j = 0; | ||||
|  | @ -74,7 +75,7 @@ namespace Mist { | |||
|         ++fragIt; | ||||
|       } | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     MP4::ABST abst; | ||||
|     abst.setVersion(1); | ||||
|     abst.setBootstrapinfoVersion(1); | ||||
|  | @ -87,11 +88,11 @@ namespace Mist { | |||
|     abst.setMovieIdentifier(streamName); | ||||
|     abst.setSegmentRunTable(asrt, 0); | ||||
|     abst.setFragmentRunTable(afrt, 0); | ||||
|      | ||||
| 
 | ||||
|     DEBUG_MSG(DLVL_VERYHIGH, "Sending bootstrap: %s", abst.toPrettyString(0).c_str()); | ||||
|     return std::string((char*)abst.asBox(), (int)abst.boxedSize()); | ||||
|     return std::string((char *)abst.asBox(), (int)abst.boxedSize()); | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   ///\brief Builds an index file for HTTP Dynamic streaming.
 | ||||
|   ///\return The index file for HTTP Dynamic Streaming.
 | ||||
|   std::string OutHDS::dynamicIndex(){ | ||||
|  | @ -103,7 +104,8 @@ namespace Mist { | |||
|     Result << "  <mimeType>video/mp4</mimeType>" << std::endl; | ||||
|     Result << "  <deliveryType>streaming</deliveryType>" << std::endl; | ||||
|     if (myMeta.vod){ | ||||
|       Result << "  <duration>" << myMeta.tracks[*videoTracks.begin()].lastms / 1000 << ".000</duration>" << std::endl; | ||||
|       Result << "  <duration>" << myMeta.tracks[*videoTracks.begin()].lastms / 1000 | ||||
|              << ".000</duration>" << std::endl; | ||||
|       Result << "  <streamType>recorded</streamType>" << std::endl; | ||||
|     }else{ | ||||
|       Result << "  <duration>0.00</duration>" << std::endl; | ||||
|  | @ -111,39 +113,54 @@ namespace Mist { | |||
|     } | ||||
|     for (std::set<int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){ | ||||
|       Result << "  <bootstrapInfo " | ||||
|       "profile=\"named\" " | ||||
|       "id=\"boot" << (*it) << "\" " | ||||
|       "url=\"" << (*it) << ".abst\">" | ||||
|       "</bootstrapInfo>" << std::endl; | ||||
|                 "profile=\"named\" " | ||||
|                 "id=\"boot" | ||||
|              << (*it) | ||||
|              << "\" " | ||||
|                 "url=\"" | ||||
|              << (*it) | ||||
|              << ".abst\">" | ||||
|                 "</bootstrapInfo>" | ||||
|              << std::endl; | ||||
|       Result << "  <media " | ||||
|       "url=\"" << (*it) << "-\" " | ||||
|       //bitrate in kbit/s, we have bps so divide by 128
 | ||||
|       "bitrate=\"" << (myMeta.tracks[(*it)].bps / 128) << "\" " | ||||
|       "bootstrapInfoId=\"boot" << (*it) << "\" " | ||||
|       "width=\"" << myMeta.tracks[(*it)].width << "\" " | ||||
|       "height=\"" << myMeta.tracks[(*it)].height << "\">" << std::endl; | ||||
|                 "url=\"" | ||||
|              << (*it) | ||||
|              << "-\" " | ||||
|                 // bitrate in kbit/s, we have bps so divide by 128
 | ||||
|                 "bitrate=\"" | ||||
|              << (myMeta.tracks[(*it)].bps / 128) | ||||
|              << "\" " | ||||
|                 "bootstrapInfoId=\"boot" | ||||
|              << (*it) | ||||
|              << "\" " | ||||
|                 "width=\"" | ||||
|              << myMeta.tracks[(*it)].width | ||||
|              << "\" " | ||||
|                 "height=\"" | ||||
|              << myMeta.tracks[(*it)].height << "\">" << std::endl; | ||||
|       Result << "    <metadata>AgAKb25NZXRhRGF0YQMAAAk=</metadata>" << std::endl; | ||||
|       Result << "  </media>" << std::endl; | ||||
|     } | ||||
|     Result << "</manifest>" << std::endl; | ||||
|     DEBUG_MSG(DLVL_HIGH, "Sending manifest: %s", Result.str().c_str()); | ||||
|     return Result.str(); | ||||
|   } //BuildManifest
 | ||||
|    | ||||
|   OutHDS::OutHDS(Socket::Connection & conn) : HTTPOutput(conn) { | ||||
|   }// BuildManifest
 | ||||
| 
 | ||||
|   OutHDS::OutHDS(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     uaDelay = 0; | ||||
|     realTime = 0; | ||||
|     audioTrack = 0; | ||||
|     playUntil = 0; | ||||
|   } | ||||
| 
 | ||||
|   OutHDS::~OutHDS() {} | ||||
|    | ||||
|   void OutHDS::init(Util::Config * cfg){ | ||||
|   OutHDS::~OutHDS(){} | ||||
| 
 | ||||
|   void OutHDS::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "HDS"; | ||||
|     capa["friendly"] = "Flash segmented over HTTP (HDS)"; | ||||
|     capa["desc"] = "Segmented streaming in Adobe/Flash (FLV-based) format over HTTP ( = HTTP Dynamic Streaming)"; | ||||
|     capa["desc"] = "Segmented streaming in Adobe/Flash (FLV-based) format over HTTP ( = HTTP " | ||||
|                    "Dynamic Streaming)"; | ||||
|     capa["url_rel"] = "/dynamic/$/manifest.f4m"; | ||||
|     capa["url_prefix"] = "/dynamic/$/"; | ||||
|     capa["codecs"][0u][0u].append("H264"); | ||||
|  | @ -166,7 +183,7 @@ namespace Mist { | |||
|     capa["methods"][0u]["priority"] = 6; | ||||
|     capa["methods"][0u]["player_url"] = "/flashplayer.swf"; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   void OutHDS::sendNext(){ | ||||
|     if (thisPacket.getTime() >= playUntil){ | ||||
|       VERYHIGH_MSG("Done sending fragment (%llu >= %llu)", thisPacket.getTime(), playUntil); | ||||
|  | @ -175,20 +192,18 @@ namespace Mist { | |||
|       H.Chunkify("", 0, myConn); | ||||
|       return; | ||||
|     } | ||||
|     DTSC::Track & trk = myMeta.tracks[thisPacket.getTrackId()]; | ||||
|     DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()]; | ||||
|     tag.DTSCLoader(thisPacket, trk); | ||||
|     if (trk.codec == "PCM" && trk.size == 16){ | ||||
|       char * ptr = tag.getData(); | ||||
|       char *ptr = tag.getData(); | ||||
|       uint32_t ptrSize = tag.getDataLen(); | ||||
|       for (uint32_t i = 0; i < ptrSize; i+=2){ | ||||
|       for (uint32_t i = 0; i < ptrSize; i += 2){ | ||||
|         char tmpchar = ptr[i]; | ||||
|         ptr[i] = ptr[i+1]; | ||||
|         ptr[i+1] = tmpchar; | ||||
|         ptr[i] = ptr[i + 1]; | ||||
|         ptr[i + 1] = tmpchar; | ||||
|       } | ||||
|     } | ||||
|     if (tag.len){ | ||||
|       H.Chunkify(tag.data, tag.len, myConn); | ||||
|     } | ||||
|     if (tag.len){H.Chunkify(tag.data, tag.len, myConn);} | ||||
|   } | ||||
| 
 | ||||
|   void OutHDS::onHTTP(){ | ||||
|  | @ -202,14 +217,14 @@ namespace Mist { | |||
|       H.SetHeader("Content-Type", "binary/octet"); | ||||
|       H.SetHeader("Cache-Control", "no-cache"); | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|       } | ||||
|       H.SetBody(dynamicBootstrap(atoll(streamID.c_str()))); | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); //clean for any possible next requests
 | ||||
|       H.Clean(); // clean for any possible next requests
 | ||||
|       return; | ||||
|     } | ||||
|     if (H.url.find("f4m") == std::string::npos){ | ||||
|  | @ -229,16 +244,17 @@ namespace Mist { | |||
|       if (fragNum < (unsigned int)myMeta.tracks[tid].missedFrags){ | ||||
|         H.Clean(); | ||||
|         H.setCORSHeaders(); | ||||
|         H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n"); | ||||
|         H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be " | ||||
|                   "served.\n"); | ||||
|         H.SendResponse("412", "Fragment out of range", myConn); | ||||
|         H.Clean(); //clean for any possible next requests
 | ||||
|         H.Clean(); // clean for any possible next requests
 | ||||
|         std::cout << "Fragment " << fragNum << " too old" << std::endl; | ||||
|         return; | ||||
|       } | ||||
|       //delay if we don't have the next fragment available yet
 | ||||
|       // delay if we don't have the next fragment available yet
 | ||||
|       unsigned int timeout = 0; | ||||
|       while (myConn && fragNum >= myMeta.tracks[tid].missedFrags + myMeta.tracks[tid].fragments.size() - 1){ | ||||
|         //time out after 21 seconds
 | ||||
|         // time out after 21 seconds
 | ||||
|         if (++timeout > 42){ | ||||
|           myConn.close(); | ||||
|           break; | ||||
|  | @ -246,33 +262,33 @@ namespace Mist { | |||
|         Util::wait(500); | ||||
|         updateMeta(); | ||||
|       } | ||||
|       mstime = myMeta.tracks[tid].getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()).getTime(); | ||||
|       mstime = myMeta.tracks[tid] | ||||
|                    .getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()) | ||||
|                    .getTime(); | ||||
|       mslen = myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getDuration(); | ||||
|       VERYHIGH_MSG("Playing from %llu for %llu ms", mstime, mslen); | ||||
|        | ||||
| 
 | ||||
|       selectedTracks.clear(); | ||||
|       selectedTracks.insert(tid); | ||||
|       if (audioTrack){ | ||||
|         selectedTracks.insert(audioTrack); | ||||
|       } | ||||
|       if (audioTrack){selectedTracks.insert(audioTrack);} | ||||
|       seek(mstime); | ||||
|       playUntil = mstime + mslen; | ||||
|        | ||||
| 
 | ||||
|       H.Clean(); | ||||
|       H.SetHeader("Content-Type", "video/mp4"); | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|       } | ||||
|       H.StartResponse(H, myConn); | ||||
|       //send the bootstrap
 | ||||
|       // send the bootstrap
 | ||||
|       std::string bootstrap = dynamicBootstrap(tid); | ||||
|       H.Chunkify(bootstrap, myConn); | ||||
|       //send a zero-size mdat, meaning it stretches until end of file.
 | ||||
|       // send a zero-size mdat, meaning it stretches until end of file.
 | ||||
|       H.Chunkify("\000\000\000\000mdat", 8, myConn); | ||||
|       //send init data, if needed.
 | ||||
|       // send init data, if needed.
 | ||||
|       if (audioTrack > 0 && myMeta.tracks[audioTrack].init != ""){ | ||||
|         if (tag.DTSCAudioInit(myMeta.tracks[audioTrack])){ | ||||
|           tag.tagTime(mstime); | ||||
|  | @ -295,7 +311,7 @@ namespace Mist { | |||
|       H.SetHeader("Content-Type", "text/xml"); | ||||
|       H.SetHeader("Cache-Control", "no-cache"); | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|  | @ -304,4 +320,4 @@ namespace Mist { | |||
|       H.SendResponse("200", "OK", myConn); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,25 +1,26 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/ts_packet.h> | ||||
| #include <mist/mp4.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/ts_packet.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutHDS : public HTTPOutput { | ||||
|     public: | ||||
|       OutHDS(Socket::Connection & conn); | ||||
|       ~OutHDS(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|     protected: | ||||
|       void getTracks(); | ||||
|       std::string dynamicBootstrap(int tid); | ||||
|       std::string dynamicIndex(); | ||||
|       std::set<int> videoTracks;///<< Holds valid video tracks for playback
 | ||||
|       long long int audioTrack;///<< Holds audio track ID for playback
 | ||||
|       long long unsigned int playUntil; | ||||
|       FLV::Tag tag; | ||||
| namespace Mist{ | ||||
|   class OutHDS : public HTTPOutput{ | ||||
|   public: | ||||
|     OutHDS(Socket::Connection &conn); | ||||
|     ~OutHDS(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
| 
 | ||||
|   protected: | ||||
|     void getTracks(); | ||||
|     std::string dynamicBootstrap(int tid); | ||||
|     std::string dynamicIndex(); | ||||
|     std::set<int> videoTracks; ///<< Holds valid video tracks for playback
 | ||||
|     long long int audioTrack;  ///<< Holds audio track ID for playback
 | ||||
|     long long unsigned int playUntil; | ||||
|     FLV::Tag tag; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHDS mistOut; | ||||
|  |  | |||
|  | @ -1,22 +1,20 @@ | |||
| #include "output_hls.h" | ||||
| #include <mist/langcodes.h> /*LTS*/ | ||||
| #include <mist/stream.h> | ||||
| #include <mist/url.h> | ||||
| #include <mist/langcodes.h> /*LTS*/ | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   bool OutHLS::isReadyForPlay() { | ||||
| namespace Mist{ | ||||
|   bool OutHLS::isReadyForPlay(){ | ||||
|     if (myMeta.tracks.size()){ | ||||
|       if (myMeta.mainTrack().fragments.size() > 4){ | ||||
|         return true; | ||||
|       } | ||||
|       if (myMeta.mainTrack().fragments.size() > 4){return true;} | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   ///\brief Builds an index file for HTTP Live streaming.
 | ||||
|   ///\return The index file for HTTP Live Streaming.
 | ||||
|   std::string OutHLS::liveIndex() { | ||||
|   std::string OutHLS::liveIndex(){ | ||||
|     std::stringstream result; | ||||
|     selectDefaultTracks(); | ||||
|     result << "#EXTM3U\r\n"; | ||||
|  | @ -28,51 +26,45 @@ namespace Mist { | |||
|       if (!hasSubs && myMeta.tracks[*it].codec == "subtitle"){hasSubs = true;} | ||||
|     } | ||||
|     for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ | ||||
|       if (myMeta.tracks[*it].type == "video") { | ||||
|       if (myMeta.tracks[*it].type == "video"){ | ||||
|         vidTracks++; | ||||
|         int bWidth = myMeta.tracks[*it].bps; | ||||
|         if (bWidth < 5) { | ||||
|           bWidth = 5; | ||||
|         } | ||||
|         if (audioId != -1) { | ||||
|           bWidth += myMeta.tracks[audioId].bps; | ||||
|         } | ||||
|         if (bWidth < 5){bWidth = 5;} | ||||
|         if (audioId != -1){bWidth += myMeta.tracks[audioId].bps;} | ||||
|         result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8); | ||||
|         result << ",RESOLUTION=" << myMeta.tracks[*it].width << "x" << myMeta.tracks[*it].height; | ||||
|         if (myMeta.tracks[*it].fpks){ | ||||
|           result << ",FRAME-RATE=" << (float)myMeta.tracks[*it].fpks / 1000;  | ||||
|         } | ||||
|         if (hasSubs){ | ||||
|           result << ",SUBTITLES=\"sub1\""; | ||||
|           result << ",FRAME-RATE=" << (float)myMeta.tracks[*it].fpks / 1000; | ||||
|         } | ||||
|         if (hasSubs){result << ",SUBTITLES=\"sub1\"";} | ||||
|         result << ",CODECS=\""; | ||||
|         result << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init); | ||||
|         if (audioId != -1){ | ||||
|           result << "," << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init); | ||||
|         } | ||||
|         result << "\""; | ||||
|         result <<"\r\n"; | ||||
|         result << "\r\n"; | ||||
|         result << *it; | ||||
|         if (audioId != -1) { | ||||
|           result << "_" << audioId; | ||||
|         } | ||||
|         if (audioId != -1){result << "_" << audioId;} | ||||
|         if (hasSessionIDs()){ | ||||
|           result << "/index.m3u8?sessId=" << getpid() << "\r\n"; | ||||
|         }else{ | ||||
|           result << "/index.m3u8\r\n"; | ||||
|         } | ||||
|       }else if(myMeta.tracks[*it].codec == "subtitle"){ | ||||
|       }else if (myMeta.tracks[*it].codec == "subtitle"){ | ||||
| 
 | ||||
|         if(myMeta.tracks[*it].lang.empty()){ | ||||
|           myMeta.tracks[*it].lang = "und"; | ||||
|         } | ||||
|         if (myMeta.tracks[*it].lang.empty()){myMeta.tracks[*it].lang = "und";} | ||||
| 
 | ||||
|         result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << myMeta.tracks[*it].lang << "\",NAME=\"" << Encodings::ISO639::decode(myMeta.tracks[*it].lang) << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\"" << "\r\n"; | ||||
|         result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << myMeta.tracks[*it].lang | ||||
|                << "\",NAME=\"" << Encodings::ISO639::decode(myMeta.tracks[*it].lang) | ||||
|                << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\"" | ||||
|                << "\r\n"; | ||||
|       } | ||||
|     } | ||||
|     if (!vidTracks && audioId) { | ||||
|     if (!vidTracks && audioId){ | ||||
|       result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8); | ||||
|       result << ",CODECS=\"" << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init) << "\""; | ||||
|       result << ",CODECS=\"" | ||||
|              << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init) << "\""; | ||||
|       result << "\r\n"; | ||||
|       result << audioId << "/index.m3u8\r\n"; | ||||
|     } | ||||
|  | @ -84,37 +76,33 @@ namespace Mist { | |||
|     std::stringstream result; | ||||
|     result << "#EXTM3U\r\n"; | ||||
|     std::set<unsigned int> audioTracks; | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { | ||||
|       if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3" || it->second.codec == "MP2") { | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3" || | ||||
|           it->second.codec == "MP2"){ | ||||
|         audioTracks.insert(it->first); | ||||
|       } | ||||
|     } | ||||
|     if (!audioTracks.size()){ | ||||
|       audioTracks.insert(-1); | ||||
|     } | ||||
|     if (!audioTracks.size()){audioTracks.insert(-1);} | ||||
|     unsigned int vidTracks = 0; | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { | ||||
|       if (it->second.codec == "H264" || it->second.codec == "HEVC" || it->second.codec == "MPEG2") { | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       if (it->second.codec == "H264" || it->second.codec == "HEVC" || it->second.codec == "MPEG2"){ | ||||
|         for (std::set<unsigned int>::iterator audIt = audioTracks.begin(); audIt != audioTracks.end(); audIt++){ | ||||
|           vidTracks++; | ||||
|           int bWidth = it->second.bps; | ||||
|           if (bWidth < 5) { | ||||
|             bWidth = 5; | ||||
|           } | ||||
|           if (*audIt != -1) { | ||||
|             bWidth += myMeta.tracks[*audIt].bps; | ||||
|           } | ||||
|           if (bWidth < 5){bWidth = 5;} | ||||
|           if (*audIt != -1){bWidth += myMeta.tracks[*audIt].bps;} | ||||
|           result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n"; | ||||
|           result << it->first; | ||||
|           if (*audIt != -1) { | ||||
|             result << "_" << *audIt; | ||||
|           } | ||||
|           if (*audIt != -1){result << "_" << *audIt;} | ||||
|           result << "/index.m3u8\r\n"; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (!vidTracks && audioTracks.size()) { | ||||
|       result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[*audioTracks.begin()].bps * 8) << "\r\n"; | ||||
|     if (!vidTracks && audioTracks.size()){ | ||||
|       result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[*audioTracks.begin()].bps * 8) | ||||
|              << "\r\n"; | ||||
|       result << *audioTracks.begin() << "/index.m3u8\r\n"; | ||||
|     } | ||||
|     return result.str(); | ||||
|  | @ -123,65 +111,62 @@ namespace Mist { | |||
|   std::string OutHLS::pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime){ | ||||
|     updateMeta(); | ||||
|     std::stringstream result; | ||||
|     //parse single track
 | ||||
|     // parse single track
 | ||||
|     result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (myMeta.tracks[tid].biggestFragment() / 1000) + 1 << "\r\n"; | ||||
| 
 | ||||
|     std::deque<std::string> lines; | ||||
|     unsigned int skippedLines = 0; | ||||
|     for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++) { | ||||
|     for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); | ||||
|          it != myMeta.tracks[tid].fragments.end(); it++){ | ||||
|       long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime(); | ||||
|       long long duration = it->getDuration(); | ||||
|       if (duration <= 0) { | ||||
|         duration = myMeta.tracks[tid].lastms - starttime; | ||||
|       } | ||||
|       if (starttime < bTime){ | ||||
|         skippedLines++; | ||||
|       } | ||||
|       if (duration <= 0){duration = myMeta.tracks[tid].lastms - starttime;} | ||||
|       if (starttime < bTime){skippedLines++;} | ||||
|       if (starttime >= bTime && (starttime + duration) <= eTime){ | ||||
|         char lineBuf[400]; | ||||
|         snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration); | ||||
|         snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", | ||||
|                  ((duration + 500) / 1000), starttime, starttime + duration); | ||||
|         lines.push_back(lineBuf); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n"; | ||||
| 
 | ||||
|     while (lines.size()) { | ||||
|     while (lines.size()){ | ||||
|       result << lines.front(); | ||||
|       lines.pop_front(); | ||||
|     } | ||||
|     if (!myMeta.live && eTime >= myMeta.tracks[tid].lastms) { | ||||
|       result << "#EXT-X-ENDLIST\r\n"; | ||||
|     } | ||||
|     if (!myMeta.live && eTime >= myMeta.tracks[tid].lastms){result << "#EXT-X-ENDLIST\r\n";} | ||||
|     return result.str(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   std::string OutHLS::liveIndex(int tid, std::string & sessId) { | ||||
|   std::string OutHLS::liveIndex(int tid, std::string &sessId){ | ||||
|     updateMeta(); | ||||
|     std::stringstream result; | ||||
|     //parse single track
 | ||||
|     // parse single track
 | ||||
|     uint32_t target_dur = (myMeta.tracks[tid].biggestFragment() / 1000) + 1; | ||||
|     result << "#EXTM3U\r\n#EXT-X-VERSION:3\r\n#EXT-X-TARGETDURATION:" << target_dur << "\r\n"; | ||||
| 
 | ||||
|     std::deque<std::string> lines; | ||||
|     std::deque<uint16_t> durs; | ||||
|     uint32_t total_dur = 0; | ||||
|     for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++) { | ||||
|     for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); | ||||
|          it != myMeta.tracks[tid].fragments.end(); it++){ | ||||
|       long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime(); | ||||
|       long long duration = it->getDuration(); | ||||
|       if (duration <= 0) { | ||||
|         duration = myMeta.tracks[tid].lastms - starttime; | ||||
|       } | ||||
|       if (duration <= 0){duration = myMeta.tracks[tid].lastms - starttime;} | ||||
|       char lineBuf[400]; | ||||
| 
 | ||||
|       if(myMeta.tracks[tid].codec == "subtitle"){ | ||||
|           snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%d&from=%lld&to=%lld\r\n", (double)duration/1000,streamName.c_str(),tid, starttime, starttime + duration); | ||||
|       if (myMeta.tracks[tid].codec == "subtitle"){ | ||||
|         snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%d&from=%lld&to=%lld\r\n", | ||||
|                  (double)duration / 1000, streamName.c_str(), tid, starttime, starttime + duration); | ||||
|       }else{ | ||||
|         if (sessId.size()){ | ||||
|           snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts?sessId=%s\r\n", (double)duration/1000, starttime, starttime + duration, sessId.c_str()); | ||||
|           snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts?sessId=%s\r\n", | ||||
|                    (double)duration / 1000, starttime, starttime + duration, sessId.c_str()); | ||||
|         }else{ | ||||
|           snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration/1000, starttime, starttime + duration); | ||||
|           snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration / 1000, | ||||
|                    starttime, starttime + duration); | ||||
|         } | ||||
|       } | ||||
|       durs.push_back(duration); | ||||
|  | @ -189,23 +174,23 @@ namespace Mist { | |||
|       lines.push_back(lineBuf); | ||||
|     } | ||||
|     unsigned int skippedLines = 0; | ||||
|     if (myMeta.live && lines.size()) { | ||||
|       //only print the last segment when VoD
 | ||||
|     if (myMeta.live && lines.size()){ | ||||
|       // only print the last segment when VoD
 | ||||
|       lines.pop_back(); | ||||
|       total_dur -= durs.back(); | ||||
|       durs.pop_back(); | ||||
|       //skip the first two segments when live, unless that brings us under 4 target durations
 | ||||
|       while ((total_dur-durs.front()) > (target_dur * 4000) && skippedLines < 2) { | ||||
|       // skip the first two segments when live, unless that brings us under 4 target durations
 | ||||
|       while ((total_dur - durs.front()) > (target_dur * 4000) && skippedLines < 2){ | ||||
|         lines.pop_front(); | ||||
|         total_dur -= durs.front(); | ||||
|         durs.pop_front(); | ||||
|         ++skippedLines; | ||||
|       } | ||||
|       /*LTS-START*/ | ||||
|       //remove lines to reduce size towards listlimit setting - but keep at least 4X target duration available
 | ||||
|       if (config->getInteger("listlimit")) { | ||||
|       // remove lines to reduce size towards listlimit setting - but keep at least 4X target duration available
 | ||||
|       if (config->getInteger("listlimit")){ | ||||
|         unsigned long listlimit = config->getInteger("listlimit"); | ||||
|         while (lines.size() > listlimit && (total_dur-durs.front()) > (target_dur * 4000)) { | ||||
|         while (lines.size() > listlimit && (total_dur - durs.front()) > (target_dur * 4000)){ | ||||
|           lines.pop_front(); | ||||
|           total_dur -= durs.front(); | ||||
|           durs.pop_front(); | ||||
|  | @ -217,23 +202,22 @@ namespace Mist { | |||
| 
 | ||||
|     result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n"; | ||||
| 
 | ||||
|     while (lines.size()) { | ||||
|     while (lines.size()){ | ||||
|       result << lines.front(); | ||||
|       lines.pop_front(); | ||||
|     } | ||||
|     if (!myMeta.live || total_dur == 0) { | ||||
|       result << "#EXT-X-ENDLIST\r\n"; | ||||
|     } | ||||
|     if (!myMeta.live || total_dur == 0){result << "#EXT-X-ENDLIST\r\n";} | ||||
|     DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str()); | ||||
|     return result.str(); | ||||
|   } //liveIndex
 | ||||
|   }// liveIndex
 | ||||
| 
 | ||||
|   std::string OutHLS::generatePushList() { | ||||
|   std::string OutHLS::generatePushList(){ | ||||
|     updateMeta(); | ||||
|     std::set<unsigned int> videoTracks; | ||||
|     std::set<unsigned int> audioTracks; | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { | ||||
|       if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") { | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){ | ||||
|         audioTracks.insert(it->first); | ||||
|       } | ||||
|       if (it->second.codec == "H264" || it->second.codec == "HEVC"){ | ||||
|  | @ -241,42 +225,39 @@ namespace Mist { | |||
|       } | ||||
|     } | ||||
|     JSON::Value result; | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       std::stringstream tid; | ||||
|       tid << it->second.trackID; | ||||
|       result["tracks"][tid.str()] = it->second.toJSON(true); | ||||
|     } | ||||
|     for(std::set<unsigned int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){ | ||||
|       for(std::set<unsigned int>::iterator it2 = audioTracks.begin(); it2 != audioTracks.end(); it2++){ | ||||
|     for (std::set<unsigned int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){ | ||||
|       for (std::set<unsigned int>::iterator it2 = audioTracks.begin(); it2 != audioTracks.end(); it2++){ | ||||
|         JSON::Value quality; | ||||
|         std::stringstream identifier; | ||||
|         identifier << "/" << *it << "_" << *it2; | ||||
|         quality["index"] = "/push" + identifier.str() + "/index_\%llu_\%llu.m3u8";  | ||||
|         quality["segment"] = identifier.str() + "/\%llu_\%llu.ts";  | ||||
|         quality["index"] = "/push" + identifier.str() + "/index_\%llu_\%llu.m3u8"; | ||||
|         quality["segment"] = identifier.str() + "/\%llu_\%llu.ts"; | ||||
|         quality["video"] = *it; | ||||
|         quality["audio"] = *it2; | ||||
|         quality["id"] = identifier.str(); | ||||
|         std::deque<DTSC::Fragment>::iterator it3 = myMeta.tracks[*it].fragments.begin(); | ||||
|         for (int i = 0; i < 2; i++){ | ||||
|           if (it3 != myMeta.tracks[*it].fragments.end()){ | ||||
|             ++it3; | ||||
|           } | ||||
|           if (it3 != myMeta.tracks[*it].fragments.end()){++it3;} | ||||
|         } | ||||
|         for (; it3 != myMeta.tracks[*it].fragments.end(); it3++) { | ||||
|         for (; it3 != myMeta.tracks[*it].fragments.end(); it3++){ | ||||
|           if (myMeta.live && it3 == (myMeta.tracks[*it].fragments.end() - 1)){ | ||||
|             //Skip the current last fragment if we are live
 | ||||
|             // Skip the current last fragment if we are live
 | ||||
|             continue; | ||||
|           } | ||||
|           uint64_t starttime = myMeta.tracks[*it].getKey(it3->getNumber()).getTime(); | ||||
|           std::stringstream line; | ||||
|           uint64_t duration = it3->getDuration(); | ||||
|           if (duration <= 0) { | ||||
|             duration = myMeta.tracks[*it].lastms - starttime; | ||||
|           } | ||||
|           if (duration <= 0){duration = myMeta.tracks[*it].lastms - starttime;} | ||||
|           std::stringstream segmenturl; | ||||
|           segmenturl << identifier.str() << "/" << starttime << "_" << duration + starttime << ".ts"; | ||||
|           JSON::Value segment; | ||||
|           //segment["url"] = segmenturl.str();
 | ||||
|           // segment["url"] = segmenturl.str();
 | ||||
|           segment["time"] = starttime; | ||||
|           segment["duration"] = duration; | ||||
|           segment["number"] = (uint64_t)it3->getNumber(); | ||||
|  | @ -285,25 +266,24 @@ namespace Mist { | |||
|         result["qualities"].append(quality); | ||||
|       } | ||||
|     } | ||||
|     return result.toString();; | ||||
|     return result.toString(); | ||||
|     ; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   OutHLS::OutHLS(Socket::Connection & conn) : TSOutput(conn) { | ||||
|   OutHLS::OutHLS(Socket::Connection &conn) : TSOutput(conn){ | ||||
|     uaDelay = 0; | ||||
|     realTime = 0; | ||||
|     until=0xFFFFFFFFFFFFFFFFull; | ||||
|     until = 0xFFFFFFFFFFFFFFFFull; | ||||
|   } | ||||
| 
 | ||||
|   OutHLS::~OutHLS() {} | ||||
|   OutHLS::~OutHLS(){} | ||||
| 
 | ||||
|   void OutHLS::init(Util::Config * cfg) { | ||||
|   void OutHLS::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "HLS"; | ||||
|     capa["friendly"] = "Apple segmented over HTTP (HLS)"; | ||||
|     capa["desc"] = "Segmented streaming in Apple (TS-based) format over HTTP ( = HTTP Live Streaming)"; | ||||
|     capa["desc"] = | ||||
|         "Segmented streaming in Apple (TS-based) format over HTTP ( = HTTP Live Streaming)"; | ||||
|     capa["url_rel"] = "/hls/$/index.m3u8"; | ||||
|     capa["url_prefix"] = "/hls/$/"; | ||||
|     capa["url_pushlist"] = "/hls/$/push/list"; | ||||
|  | @ -317,50 +297,67 @@ namespace Mist { | |||
|     capa["methods"][0u]["handler"] = "http"; | ||||
|     capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; | ||||
|     capa["methods"][0u]["priority"] = 9; | ||||
|     //MP3 only works on Edge/Apple
 | ||||
|     capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); | ||||
|     // MP3 only works on Edge/Apple
 | ||||
|     capa["exceptions"]["codec:MP3"] = JSON::fromString( | ||||
|         "[[\"blacklist\",[\"Mozilla/" | ||||
|         "\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); | ||||
|     capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\"]]"); | ||||
|     /*LTS-START*/ | ||||
|     cfg->addOption("listlimit", JSON::fromString("{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\",\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}")); | ||||
|     cfg->addOption("listlimit", | ||||
|                    JSON::fromString( | ||||
|                        "{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\"," | ||||
|                        "\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}")); | ||||
|     capa["optional"]["listlimit"]["name"] = "Live playlist limit"; | ||||
|     capa["optional"]["listlimit"]["help"] = "Maximum number of parts in live playlists. (0 = infinite)"; | ||||
|     capa["optional"]["listlimit"]["help"] = | ||||
|         "Maximum number of parts in live playlists. (0 = infinite)"; | ||||
|     capa["optional"]["listlimit"]["default"] = 0; | ||||
|     capa["optional"]["listlimit"]["type"] = "uint"; | ||||
|     capa["optional"]["listlimit"]["option"] = "--list-limit"; | ||||
|      | ||||
|     cfg->addOption("nonchunked", JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not send chunked, but buffer whole segments.\"}")); | ||||
| 
 | ||||
|     cfg->addOption("nonchunked", | ||||
|                    JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not " | ||||
|                                     "send chunked, but buffer whole segments.\"}")); | ||||
|     capa["optional"]["nonchunked"]["name"] = "Send whole segments"; | ||||
|     capa["optional"]["nonchunked"]["help"] = "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance significantly, but increases compatibility somewhat."; | ||||
|     capa["optional"]["nonchunked"]["help"] = | ||||
|         "Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance " | ||||
|         "significantly, but increases compatibility somewhat."; | ||||
|     capa["optional"]["nonchunked"]["option"] = "--nonchunked"; | ||||
| 
 | ||||
|     cfg->addOption("mergesessions", JSON::fromString("{\"short\":\"M\",\"long\":\"mergesessions\",\"help\":\"Merge together sessions from one user into a single session.\"}")); | ||||
|     cfg->addOption("mergesessions", | ||||
|                    JSON::fromString("{\"short\":\"M\",\"long\":\"mergesessions\",\"help\":\"Merge " | ||||
|                                     "together sessions from one user into a single session.\"}")); | ||||
|     capa["optional"]["mergesessions"]["name"] = "Merge sessions"; | ||||
|     capa["optional"]["mergesessions"]["help"] = "If enabled, merges together all views from a single user into a single combined session. If disabled, each view (main playlist request) is a separate session."; | ||||
|     capa["optional"]["mergesessions"]["help"] = | ||||
|         "If enabled, merges together all views from a single user into a single combined session. " | ||||
|         "If disabled, each view (main playlist request) is a separate session."; | ||||
|     capa["optional"]["mergesessions"]["option"] = "--mergesessions"; | ||||
|     /*LTS-END*/ | ||||
|   } | ||||
| 
 | ||||
|   void OutHLS::onHTTP() { | ||||
|   void OutHLS::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|     std::string sessId = H.GetVar("sessId"); | ||||
| 
 | ||||
|     if (H.url == "/crossdomain.xml") { | ||||
|     if (H.url == "/crossdomain.xml"){ | ||||
|       H.Clean(); | ||||
|       H.SetHeader("Content-Type", "text/xml"); | ||||
|       H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|       } | ||||
|       H.SetBody("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>"); | ||||
|       H.SetBody("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM " | ||||
|                 "\"http://www.adobe.com/xml/dtds/" | ||||
|                 "cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" " | ||||
|                 "/><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>"); | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); //clean for any possible next requests
 | ||||
|       H.Clean(); // clean for any possible next requests
 | ||||
|       return; | ||||
|     } //crossdomain.xml
 | ||||
|     }// crossdomain.xml
 | ||||
| 
 | ||||
|     if (H.method == "OPTIONS") { | ||||
|     if (H.method == "OPTIONS"){ | ||||
|       bool isTS = (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"); | ||||
|       H.Clean(); | ||||
|       H.setCORSHeaders(); | ||||
|  | @ -377,16 +374,16 @@ namespace Mist { | |||
|       H.Clean(); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     if (H.url.find("hls") == std::string::npos){ | ||||
|       onFail("HLS handler active, but this is not a HLS URL. Eh... What...?"); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     bool VLCworkaround = false; | ||||
|     if (H.GetHeader("User-Agent").substr(0, 3) == "VLC") { | ||||
|     if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){ | ||||
|       std::string vlcver = H.GetHeader("User-Agent").substr(4); | ||||
|       if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')) { | ||||
|       if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')){ | ||||
|         DEBUG_MSG(DLVL_INFO, "Enabling VLC version < 2.2.0 bug workaround."); | ||||
|         VLCworkaround = true; | ||||
|       } | ||||
|  | @ -409,67 +406,65 @@ namespace Mist { | |||
|         H.setCORSHeaders(); | ||||
|         H.SetBody(pushLiveIndex()); | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); //clean for any possible next requests
 | ||||
|         H.Clean(); // clean for any possible next requests
 | ||||
|         return; | ||||
|       }else { | ||||
|       }else{ | ||||
|         unsigned int vTrack; | ||||
|         unsigned int aTrack; | ||||
|         unsigned long long bTime; | ||||
|         unsigned long long eTime; | ||||
|         if (sscanf(relPushUrl.c_str(), "/%u_%u/index_%llu_%llu.m3u", &vTrack, &aTrack, &bTime, &eTime) == 4) { | ||||
|           if (eTime < bTime){ | ||||
|             eTime = bTime; | ||||
|           } | ||||
|         if (sscanf(relPushUrl.c_str(), "/%u_%u/index_%llu_%llu.m3u", &vTrack, &aTrack, &bTime, &eTime) == 4){ | ||||
|           if (eTime < bTime){eTime = bTime;} | ||||
|           H.setCORSHeaders(); | ||||
|           H.SetBody(pushLiveIndex(vTrack, bTime, eTime)); | ||||
|           H.SendResponse("200", "OK", myConn); | ||||
|           H.Clean(); //clean for any possible next requests
 | ||||
|           H.Clean(); // clean for any possible next requests
 | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|       H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n"); | ||||
|       myConn.SendNow(H.BuildResponse("404", "URL mismatch")); | ||||
|       H.Clean(); //clean for any possible next requests
 | ||||
|       H.Clean(); // clean for any possible next requests
 | ||||
|       return; | ||||
|     }else if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u") { | ||||
|     }else if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){ | ||||
|       size_t slashPos = H.getUrl().find('/', 5); | ||||
|       std::string tmpStr = H.getUrl().substr(slashPos); | ||||
|       long long unsigned int from; | ||||
|       if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4) { | ||||
|         if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3) { | ||||
|       if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4){ | ||||
|         if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3){ | ||||
|           DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str()); | ||||
|           H.Clean(); | ||||
|           H.setCORSHeaders(); | ||||
|           H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n"); | ||||
|           myConn.SendNow(H.BuildResponse("404", "URL mismatch")); | ||||
|           H.Clean(); //clean for any possible next requests
 | ||||
|           H.Clean(); // clean for any possible next requests
 | ||||
|           return; | ||||
|         } else { | ||||
|         }else{ | ||||
|           selectedTracks.clear(); | ||||
|           selectedTracks.insert(vidTrack); | ||||
|         } | ||||
|       } else { | ||||
|       }else{ | ||||
|         selectedTracks.clear(); | ||||
|         selectedTracks.insert(vidTrack); | ||||
|         selectedTracks.insert(audTrack); | ||||
|       } | ||||
|       for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ | ||||
|         if (it->second.codec == "ID3"){ | ||||
|           selectedTracks.insert(it->first); | ||||
|         } | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|            it != myMeta.tracks.end(); it++){ | ||||
|         if (it->second.codec == "ID3"){selectedTracks.insert(it->first);} | ||||
|       } | ||||
| 
 | ||||
|       //Keep a reference to the main track
 | ||||
|       //This is called vidTrack, even for audio-only streams
 | ||||
|       DTSC::Track & Trk = myMeta.tracks[vidTrack]; | ||||
|       // Keep a reference to the main track
 | ||||
|       // This is called vidTrack, even for audio-only streams
 | ||||
|       DTSC::Track &Trk = myMeta.tracks[vidTrack]; | ||||
| 
 | ||||
|       if (myMeta.live) { | ||||
|       if (myMeta.live){ | ||||
|         if (from < Trk.firstms){ | ||||
|           H.Clean(); | ||||
|           H.setCORSHeaders(); | ||||
|           H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n"); | ||||
|           H.SetBody("The requested fragment is no longer kept in memory on the server and cannot " | ||||
|                     "be served.\n"); | ||||
|           myConn.SendNow(H.BuildResponse("404", "Fragment out of range")); | ||||
|           H.Clean(); //clean for any possible next requests
 | ||||
|           H.Clean(); // clean for any possible next requests
 | ||||
|           WARN_MSG("Fragment @ %llu too old", from); | ||||
|           return; | ||||
|         } | ||||
|  | @ -484,24 +479,24 @@ namespace Mist { | |||
|         H.SetHeader("Pragma", ""); | ||||
|         H.SetHeader("Expires", ""); | ||||
|       } | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked")); | ||||
|       //we assume whole fragments - but timestamps may be altered at will
 | ||||
|       // we assume whole fragments - but timestamps may be altered at will
 | ||||
|       uint32_t fragIndice = Trk.timeToFragnum(from); | ||||
|       contPAT = Trk.missedFrags + fragIndice; //PAT continuity counter
 | ||||
|       contPMT = Trk.missedFrags + fragIndice; //PMT continuity counter
 | ||||
|       contSDT = Trk.missedFrags + fragIndice; //SDT continuity counter
 | ||||
|       contPAT = Trk.missedFrags + fragIndice; // PAT continuity counter
 | ||||
|       contPMT = Trk.missedFrags + fragIndice; // PMT continuity counter
 | ||||
|       contSDT = Trk.missedFrags + fragIndice; // SDT continuity counter
 | ||||
|       packCounter = 0; | ||||
|       parseData = true; | ||||
|       wantRequest = false; | ||||
|       seek(from); | ||||
|       ts_from = from; | ||||
|     } else { | ||||
|     }else{ | ||||
|       initialize(); | ||||
|       std::string request = H.url.substr(H.url.find("/", 5) + 1); | ||||
|       H.Clean(); | ||||
|  | @ -512,15 +507,15 @@ namespace Mist { | |||
|         H.Clean(); | ||||
|         return; | ||||
|       } | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         H.Clean(); | ||||
|         return; | ||||
|       } | ||||
|       std::string manifest; | ||||
|       if (request.find("/") == std::string::npos) { | ||||
|       if (request.find("/") == std::string::npos){ | ||||
|         manifest = liveIndex(); | ||||
|       } else { | ||||
|       }else{ | ||||
|         int selectId = atoi(request.substr(0, request.find("/")).c_str()); | ||||
|         manifest = liveIndex(selectId, sessId); | ||||
|       } | ||||
|  | @ -530,17 +525,17 @@ namespace Mist { | |||
|   } | ||||
| 
 | ||||
|   void OutHLS::sendNext(){ | ||||
|     //First check if we need to stop.
 | ||||
|     // First check if we need to stop.
 | ||||
|     if (thisPacket.getTime() >= until){ | ||||
|       stop(); | ||||
|       wantRequest = true; | ||||
|       parseData = false; | ||||
| 
 | ||||
|       //Ensure alignment of contCounters for selected tracks, to prevent discontinuities.
 | ||||
|       // Ensure alignment of contCounters for selected tracks, to prevent discontinuities.
 | ||||
|       for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ | ||||
|         DTSC::Track & Trk = myMeta.tracks[*it]; | ||||
|         DTSC::Track &Trk = myMeta.tracks[*it]; | ||||
|         uint32_t pkgPid = 255 + *it; | ||||
|         int & contPkg = contCounters[pkgPid]; | ||||
|         int &contPkg = contCounters[pkgPid]; | ||||
|         if (contPkg % 16 != 0){ | ||||
|           packData.clear(); | ||||
|           packData.setPID(pkgPid); | ||||
|  | @ -553,30 +548,28 @@ namespace Mist { | |||
|         } | ||||
|       } | ||||
| 
 | ||||
|       //Signal end of data
 | ||||
|       // Signal end of data
 | ||||
|       H.Chunkify("", 0, myConn); | ||||
|       return; | ||||
|     } | ||||
|     //Invoke the generic TS output sendNext handler
 | ||||
|     // Invoke the generic TS output sendNext handler
 | ||||
|     TSOutput::sendNext(); | ||||
|   } | ||||
| 
 | ||||
|   void OutHLS::sendTS(const char * tsData, unsigned int len) { | ||||
|     H.Chunkify(tsData, len, myConn); | ||||
|   } | ||||
|   void OutHLS::sendTS(const char *tsData, unsigned int len){H.Chunkify(tsData, len, myConn);} | ||||
| 
 | ||||
|   void OutHLS::onFail(const std::string & msg, bool critical){ | ||||
|   void OutHLS::onFail(const std::string &msg, bool critical){ | ||||
|     if (H.url.find(".m3u") == std::string::npos){ | ||||
|       HTTPOutput::onFail(msg, critical); | ||||
|       return; | ||||
|     } | ||||
|     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.SetHeader("Server", "MistServer/" PACKAGE_VERSION); | ||||
|     H.setCORSHeaders(); | ||||
|     H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); | ||||
|     H.SetHeader("Cache-Control", "no-cache"); | ||||
|     H.SetBody("#EXTM3U\r\n#EXT-X-ERROR: "+msg+"\r\n#EXT-X-ENDLIST\r\n"); | ||||
|     H.SetBody("#EXTM3U\r\n#EXT-X-ERROR: " + msg + "\r\n#EXT-X-ENDLIST\r\n"); | ||||
|     H.SendResponse("200", "OK", myConn); | ||||
|     Output::onFail(msg, critical); | ||||
|   } | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,37 +1,36 @@ | |||
| #include "output_ts_base.h" | ||||
| #include "output_http.h" | ||||
| #include "output_ts_base.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
|   class OutHLS : public TSOutput{ | ||||
|     public: | ||||
|       OutHLS(Socket::Connection & conn); | ||||
|       ~OutHLS(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void sendTS(const char * tsData, unsigned int len=188); | ||||
|       void sendNext(); | ||||
|       void onHTTP();       | ||||
|       bool isReadyForPlay(); | ||||
|       virtual void onFail(const std::string & msg, bool critical = false); | ||||
|     protected: | ||||
|       std::string h264init(const std::string & initData); | ||||
|       std::string h265init(const std::string & initData); | ||||
|   public: | ||||
|     OutHLS(Socket::Connection &conn); | ||||
|     ~OutHLS(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void sendTS(const char *tsData, unsigned int len = 188); | ||||
|     void sendNext(); | ||||
|     void onHTTP(); | ||||
|     bool isReadyForPlay(); | ||||
|     virtual void onFail(const std::string &msg, bool critical = false); | ||||
| 
 | ||||
|       bool hasSessionIDs(){return !config->getBool("mergesessions");} | ||||
|       std::string liveIndex(); | ||||
|       std::string liveIndex(int tid, std::string & sessId); | ||||
|   protected: | ||||
|     std::string h264init(const std::string &initData); | ||||
|     std::string h265init(const std::string &initData); | ||||
| 
 | ||||
|        | ||||
|       std::string pushLiveIndex(); | ||||
|       std::string pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime); | ||||
|     bool hasSessionIDs(){return !config->getBool("mergesessions");} | ||||
|     std::string liveIndex(); | ||||
|     std::string liveIndex(int tid, std::string &sessId); | ||||
| 
 | ||||
|     std::string pushLiveIndex(); | ||||
|     std::string pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime); | ||||
| 
 | ||||
|       std::string generatePushList(); | ||||
|       int canSeekms(unsigned int ms); | ||||
|       int keysToSend;       | ||||
|       unsigned int vidTrack; | ||||
|       unsigned int audTrack; | ||||
|       long long unsigned int until; | ||||
|     std::string generatePushList(); | ||||
|     int canSeekms(unsigned int ms); | ||||
|     int keysToSend; | ||||
|     unsigned int vidTrack; | ||||
|     unsigned int audTrack; | ||||
|     long long unsigned int until; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHLS mistOut; | ||||
|  |  | |||
|  | @ -1,66 +1,67 @@ | |||
| #include "output_hss.h" | ||||
| #include <mist/defines.h> | ||||
| #include <mist/mp4.h> | ||||
| #include <mist/mp4_ms.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/mp4_encryption.h> /*LTS*/ | ||||
| #include <mist/encode.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/stream.h> | ||||
| #include <mist/bitfields.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <unistd.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/encode.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/mp4.h> | ||||
| #include <mist/mp4_encryption.h> /*LTS*/ | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/mp4_ms.h> | ||||
| #include <mist/nal.h>/*LTS*/ | ||||
| #include <mist/stream.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| ///\todo Maybe move to util?
 | ||||
| long long unsigned int binToInt(std::string & binary) { | ||||
| long long unsigned int binToInt(std::string &binary){ | ||||
|   long long int result = 0; | ||||
|   for (int i = 0; i < 8; i++) { | ||||
|   for (int i = 0; i < 8; i++){ | ||||
|     result <<= 8; | ||||
|     result += binary[i]; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| std::string intToBin(long long unsigned int number) { | ||||
| std::string intToBin(long long unsigned int number){ | ||||
|   std::string result; | ||||
|   result.resize(8); | ||||
|   for (int i = 7; i >= 0; i--) { | ||||
|   for (int i = 7; i >= 0; i--){ | ||||
|     result[i] = number & 0xFF; | ||||
|     number >>= 8; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| std::string toUTF16(std::string original) { | ||||
| std::string toUTF16(std::string original){ | ||||
|   std::string result; | ||||
|   result += (char)0xFF; | ||||
|   result += (char)0xFE; | ||||
|   for (std::string::iterator it = original.begin(); it != original.end(); it++) { | ||||
|   for (std::string::iterator it = original.begin(); it != original.end(); it++){ | ||||
|     result += (*it); | ||||
|     result += (char)0x00; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| /// Converts bytes per second and track ID into a single bits per second value, where the last two digits are the track ID.
 | ||||
| /// Breaks for track IDs > 99. But really, this is MS-SS, so who cares..?
 | ||||
| /// Converts bytes per second and track ID into a single bits per second value, where the last two
 | ||||
| /// digits are the track ID. Breaks for track IDs > 99. But really, this is MS-SS, so who cares..?
 | ||||
| uint64_t bpsAndIdToBitrate(uint32_t bps, uint64_t tid){ | ||||
|   return ((uint64_t)((bps*8)/100))*100+tid; | ||||
|   return ((uint64_t)((bps * 8) / 100)) * 100 + tid; | ||||
| } | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutHSS::OutHSS(Socket::Connection & conn) : HTTPOutput(conn){ | ||||
| namespace Mist{ | ||||
|   OutHSS::OutHSS(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     uaDelay = 0; | ||||
|     realTime = 0; | ||||
|   } | ||||
|   OutHSS::~OutHSS(){} | ||||
| 
 | ||||
|   void OutHSS::init(Util::Config * cfg) { | ||||
|   void OutHSS::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "HSS"; | ||||
|     capa["friendly"] = "Microsoft segmented over HTTP (HSS)"; | ||||
|     capa["desc"] = "Segmented streaming in Microsoft Silverlight (fMP4-based) format over HTTP ( = HTTP Smooth Streaming)"; | ||||
|     capa["desc"] = "Segmented streaming in Microsoft Silverlight (fMP4-based) format over HTTP ( = " | ||||
|                    "HTTP Smooth Streaming)"; | ||||
|     capa["url_rel"] = "/smooth/$.ism/Manifest"; | ||||
|     capa["url_prefix"] = "/smooth/$.ism/"; | ||||
|     capa["codecs"][0u][0u].append("H264"); | ||||
|  | @ -70,44 +71,46 @@ namespace Mist { | |||
|     capa["methods"][0u]["priority"] = 1; | ||||
|   } | ||||
| 
 | ||||
|   void OutHSS::sendNext() { | ||||
|     if (thisPacket.getTime() >= playUntil) { | ||||
|   void OutHSS::sendNext(){ | ||||
|     if (thisPacket.getTime() >= playUntil){ | ||||
|       stop(); | ||||
|       wantRequest = true; | ||||
|       H.Chunkify("", 0, myConn); | ||||
|       H.Clean(); | ||||
|       return; | ||||
|     } | ||||
|     char * dataPointer = 0; | ||||
|     char *dataPointer = 0; | ||||
|     size_t len = 0; | ||||
|     thisPacket.getString("data", dataPointer, len); | ||||
|     H.Chunkify(dataPointer, len, myConn); | ||||
|   } | ||||
| 
 | ||||
|   int OutHSS::canSeekms(unsigned int ms) { | ||||
|     //no tracks? Frame too new by definition.
 | ||||
|     if (!myMeta.tracks.size()) { | ||||
|   int OutHSS::canSeekms(unsigned int ms){ | ||||
|     // no tracks? Frame too new by definition.
 | ||||
|     if (!myMeta.tracks.size()){ | ||||
|       DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because no tracks", ms); | ||||
|       return 1; | ||||
|     } | ||||
|     //loop trough all selected tracks
 | ||||
|     for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { | ||||
|       //return "too late" if one track is past this point
 | ||||
|       if (ms < myMeta.tracks[*it].firstms) { | ||||
|         DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns -1 because track %lu firstms == %llu", ms, *it, myMeta.tracks[*it].firstms); | ||||
|     // loop trough all selected tracks
 | ||||
|     for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       // return "too late" if one track is past this point
 | ||||
|       if (ms < myMeta.tracks[*it].firstms){ | ||||
|         DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns -1 because track %lu firstms == %llu", | ||||
|                   ms, *it, myMeta.tracks[*it].firstms); | ||||
|         return -1; | ||||
|       } | ||||
|       //return "too early" if one track is not yet at this point
 | ||||
|       if (ms > myMeta.tracks[*it].lastms) { | ||||
|         DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because track %lu lastms == %llu", ms, *it, myMeta.tracks[*it].lastms); | ||||
|       // return "too early" if one track is not yet at this point
 | ||||
|       if (ms > myMeta.tracks[*it].lastms){ | ||||
|         DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because track %lu lastms == %llu", ms, | ||||
|                   *it, myMeta.tracks[*it].lastms); | ||||
|         return 1; | ||||
|       } | ||||
|     } | ||||
|     return 0; | ||||
|   } | ||||
| 
 | ||||
|   void OutHSS::sendHeader() { | ||||
|     //We have a non-manifest request, parse it.
 | ||||
|   void OutHSS::sendHeader(){ | ||||
|     // We have a non-manifest request, parse it.
 | ||||
|     std::string Quality = H.url.substr(H.url.find("Q(", 2) + 2); | ||||
|     Quality = Quality.substr(0, Quality.find(")")); | ||||
|     std::string parseString = H.url.substr(H.url.find(")/") + 2); | ||||
|  | @ -116,25 +119,24 @@ namespace Mist { | |||
|     unsigned int tid = atoll(Quality.c_str()) % 100; | ||||
|     selectedTracks.clear(); | ||||
|     selectedTracks.insert(tid); | ||||
|     if (myMeta.live) { | ||||
|     if (myMeta.live){ | ||||
|       updateMeta(); | ||||
|       unsigned int timeout = 0; | ||||
|       int seekable; | ||||
|       do { | ||||
|       do{ | ||||
|         seekable = canSeekms(seekTime); | ||||
|         if (seekable == 0){ | ||||
|           // iff the fragment in question is available, check if the next is available too
 | ||||
|           for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin(); it != myMeta.tracks[tid].keys.end(); it++){ | ||||
|           for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin(); | ||||
|                it != myMeta.tracks[tid].keys.end(); it++){ | ||||
|             if (it->getTime() >= seekTime){ | ||||
|               if ((it + 1) == myMeta.tracks[tid].keys.end()){ | ||||
|                 seekable = 1; | ||||
|               } | ||||
|               if ((it + 1) == myMeta.tracks[tid].keys.end()){seekable = 1;} | ||||
|               break; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         if (seekable > 0){ | ||||
|           //time out after 21 seconds
 | ||||
|           // time out after 21 seconds
 | ||||
|           if (++timeout > 42){ | ||||
|             myConn.close(); | ||||
|             break; | ||||
|  | @ -145,10 +147,12 @@ namespace Mist { | |||
|       }while (myConn && seekable > 0); | ||||
|       if (seekable < 0){ | ||||
|         H.Clean(); | ||||
|         H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n"); | ||||
|         H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be " | ||||
|                   "served.\n"); | ||||
|         myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); | ||||
|         H.Clean(); //clean for any possible next requests
 | ||||
|         std::cout << "Fragment @ " << seekTime << "ms too old (" << myMeta.tracks[tid].firstms << " - " << myMeta.tracks[tid].lastms << " ms)" << std::endl; | ||||
|         H.Clean(); // clean for any possible next requests
 | ||||
|         std::cout << "Fragment @ " << seekTime << "ms too old (" << myMeta.tracks[tid].firstms | ||||
|                   << " - " << myMeta.tracks[tid].lastms << " ms)" << std::endl; | ||||
|         stop(); | ||||
|         wantRequest = true; | ||||
|         return; | ||||
|  | @ -156,7 +160,8 @@ namespace Mist { | |||
|     } | ||||
|     seek(seekTime); | ||||
|     ///\todo Rewrite to fragments
 | ||||
|     for (std::deque<DTSC::Key>::iterator it2 = myMeta.tracks[tid].keys.begin(); it2 != myMeta.tracks[tid].keys.end(); it2++) { | ||||
|     for (std::deque<DTSC::Key>::iterator it2 = myMeta.tracks[tid].keys.begin(); | ||||
|          it2 != myMeta.tracks[tid].keys.end(); it2++){ | ||||
|       if (it2->getTime() > seekTime){ | ||||
|         playUntil = it2->getTime(); | ||||
|         break; | ||||
|  | @ -165,22 +170,23 @@ namespace Mist { | |||
|     myTrackStor = tid; | ||||
|     myKeyStor = seekTime; | ||||
|     keysToSend = 1; | ||||
|     //Seek to the right place and send a play-once for a single fragment.
 | ||||
|     // Seek to the right place and send a play-once for a single fragment.
 | ||||
|     std::stringstream sstream; | ||||
| 
 | ||||
|     int partOffset = 0; | ||||
|     DTSC::Key keyObj; | ||||
|     for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin(); it != myMeta.tracks[tid].keys.end(); it++) { | ||||
|       if (it->getTime() >= seekTime) { | ||||
|     for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin(); | ||||
|          it != myMeta.tracks[tid].keys.end(); it++){ | ||||
|       if (it->getTime() >= seekTime){ | ||||
|         keyObj = (*it); | ||||
|         std::deque<DTSC::Key>::iterator nextIt = it; | ||||
|         nextIt++; | ||||
|         if (nextIt == myMeta.tracks[tid].keys.end()) { | ||||
|           if (myMeta.live) { | ||||
|         if (nextIt == myMeta.tracks[tid].keys.end()){ | ||||
|           if (myMeta.live){ | ||||
|             H.Clean(); | ||||
|             H.SetBody("Proxy, re-request this in a second or two.\n"); | ||||
|             myConn.SendNow(H.BuildResponse("208", "Ask again later")); | ||||
|             H.Clean(); //clean for any possible next requests
 | ||||
|             H.Clean(); // clean for any possible next requests
 | ||||
|             std::cout << "Fragment after fragment @ " << seekTime << " not available yet" << std::endl; | ||||
|           } | ||||
|         } | ||||
|  | @ -188,52 +194,52 @@ namespace Mist { | |||
|       } | ||||
|       partOffset += it->getParts(); | ||||
|     } | ||||
|     if (H.url == "/") { | ||||
|       return; //Don't continue, but continue instead.
 | ||||
|     if (H.url == "/"){ | ||||
|       return; // Don't continue, but continue instead.
 | ||||
|     } | ||||
|     /*
 | ||||
|     if (myMeta.live) { | ||||
|     if (myMeta.live){ | ||||
|       if (mstime == 0 && seekTime > 1){ | ||||
|         H.Clean(); | ||||
|         H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n"); | ||||
|         myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); | ||||
|         H.Clean(); //clean for any possible next requests
 | ||||
|         std::cout << "Fragment @ " << seekTime << " too old" << std::endl; | ||||
|         continue; | ||||
|         H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be | ||||
|     served.\n"); myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); H.Clean(); //clean | ||||
|     for any possible next requests std::cout << "Fragment @ " << seekTime << " too old" << | ||||
|     std::endl; continue; | ||||
|       } | ||||
|     } | ||||
|     */ | ||||
| 
 | ||||
|     ///\todo Select correct track (tid);
 | ||||
| 
 | ||||
|     //Wrap everything in mp4 boxes
 | ||||
|     // Wrap everything in mp4 boxes
 | ||||
|     MP4::MFHD mfhd_box; | ||||
|     mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + (myMeta.tracks[tid].type == "video" ? 1 : 2)); | ||||
| 
 | ||||
|     MP4::TFHD tfhd_box; | ||||
|     tfhd_box.setFlags(MP4::tfhdSampleFlag); | ||||
|     tfhd_box.setTrackID((myMeta.tracks[tid].type == "video" ? 1 : 2)); | ||||
|     if (myMeta.tracks[tid].type == "video") { | ||||
|     if (myMeta.tracks[tid].type == "video"){ | ||||
|       tfhd_box.setDefaultSampleFlags(0x00004001); | ||||
|     } else { | ||||
|     }else{ | ||||
|       tfhd_box.setDefaultSampleFlags(0x00008002); | ||||
|     } | ||||
| 
 | ||||
|     MP4::TRUN trun_box; | ||||
|     trun_box.setDataOffset(42);///\todo Check if this is a placeholder, or an actually correct number
 | ||||
|     trun_box.setDataOffset(42); ///\todo Check if this is a placeholder, or an actually correct number
 | ||||
|     unsigned int keySize = 0; | ||||
|     if (myMeta.tracks[tid].type == "video") { | ||||
|       trun_box.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize | MP4::trunsampleOffsets); | ||||
|     } else { | ||||
|     if (myMeta.tracks[tid].type == "video"){ | ||||
|       trun_box.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | | ||||
|                         MP4::trunsampleSize | MP4::trunsampleOffsets); | ||||
|     }else{ | ||||
|       trun_box.setFlags(MP4::trundataOffset | MP4::trunsampleDuration | MP4::trunsampleSize); | ||||
|     } | ||||
|     trun_box.setFirstSampleFlags(0x00004002); | ||||
|     for (int i = 0; i < keyObj.getParts(); i++) { | ||||
|     for (int i = 0; i < keyObj.getParts(); i++){ | ||||
|       MP4::trunSampleInformation trunSample; | ||||
|       trunSample.sampleSize = myMeta.tracks[tid].parts[i + partOffset].getSize(); | ||||
|       keySize += myMeta.tracks[tid].parts[i + partOffset].getSize(); | ||||
|       trunSample.sampleDuration = myMeta.tracks[tid].parts[i + partOffset].getDuration() * 10000; | ||||
|       if (myMeta.tracks[tid].type == "video") { | ||||
|       if (myMeta.tracks[tid].type == "video"){ | ||||
|         trunSample.sampleOffset = myMeta.tracks[tid].parts[i + partOffset].getOffset() * 10000; | ||||
|       } | ||||
|       trun_box.setSampleInformation(trunSample, i); | ||||
|  | @ -241,16 +247,12 @@ namespace Mist { | |||
| 
 | ||||
|     MP4::SDTP sdtp_box; | ||||
|     sdtp_box.setVersion(0); | ||||
|     if (myMeta.tracks[tid].type == "video") { | ||||
|     if (myMeta.tracks[tid].type == "video"){ | ||||
|       sdtp_box.setValue(36, 4); | ||||
|       for (int i = 1; i < keyObj.getParts(); i++) { | ||||
|         sdtp_box.setValue(20, 4 + i); | ||||
|       } | ||||
|     } else { | ||||
|       for (int i = 1; i < keyObj.getParts(); i++){sdtp_box.setValue(20, 4 + i);} | ||||
|     }else{ | ||||
|       sdtp_box.setValue(40, 4); | ||||
|       for (int i = 1; i < keyObj.getParts(); i++) { | ||||
|         sdtp_box.setValue(40, 4 + i); | ||||
|       } | ||||
|       for (int i = 1; i < keyObj.getParts(); i++){sdtp_box.setValue(40, 4 + i);} | ||||
|     } | ||||
| 
 | ||||
|     MP4::TRAF traf_box; | ||||
|  | @ -258,9 +260,9 @@ namespace Mist { | |||
|     traf_box.setContent(trun_box, 1); | ||||
|     traf_box.setContent(sdtp_box, 2); | ||||
| 
 | ||||
|     //If the stream is live, we want to have a fragref box if possible
 | ||||
|     // If the stream is live, we want to have a fragref box if possible
 | ||||
|     //////HEREHEREHERE
 | ||||
|     if (myMeta.live) { | ||||
|     if (myMeta.live){ | ||||
|       MP4::UUID_TFXD tfxd_box; | ||||
|       tfxd_box.setTime(keyObj.getTime()); | ||||
|       tfxd_box.setDuration(keyObj.getLength()); | ||||
|  | @ -270,9 +272,10 @@ namespace Mist { | |||
|       fragref_box.setVersion(1); | ||||
|       fragref_box.setFragmentCount(0); | ||||
|       int fragCount = 0; | ||||
|       for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) { | ||||
|         if (myMeta.tracks[tid].keys[i].getTime() > seekTime) { | ||||
|           DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime); | ||||
|       for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++){ | ||||
|         if (myMeta.tracks[tid].keys[i].getTime() > seekTime){ | ||||
|           DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i, | ||||
|                     myMeta.tracks[tid].keys[i].getTime(), seekTime); | ||||
|           fragref_box.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000); | ||||
|           fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000); | ||||
|           fragref_box.setFragmentCount(++fragCount); | ||||
|  | @ -289,29 +292,29 @@ namespace Mist { | |||
|     if (nProxy.encrypt){ | ||||
|       MP4::UUID_SampleEncryption sEnc; | ||||
|       sEnc.setVersion(0); | ||||
|       if (myMeta.tracks[tid].type == "audio") { | ||||
|       if (myMeta.tracks[tid].type == "audio"){ | ||||
|         sEnc.setFlags(0); | ||||
|         for (int i = 0; i < keyObj.getParts(); i++) { | ||||
|         for (int i = 0; i < keyObj.getParts(); i++){ | ||||
|           MP4::UUID_SampleEncryption_Sample newSample; | ||||
|           prepareNext(); | ||||
|           thisPacket.getString("ivec", newSample.InitializationVector); | ||||
|           sEnc.setSample(newSample, i); | ||||
|         } | ||||
|       } else { | ||||
|       }else{ | ||||
|         sEnc.setFlags(2); | ||||
|         std::deque<long long int> tmpParts; | ||||
|         for (int i = 0; i < keyObj.getParts(); i++) { | ||||
|           //Get the correct packet
 | ||||
|         for (int i = 0; i < keyObj.getParts(); i++){ | ||||
|           // Get the correct packet
 | ||||
|           prepareNext(); | ||||
|           MP4::UUID_SampleEncryption_Sample newSample; | ||||
|           thisPacket.getString("ivec", newSample.InitializationVector); | ||||
| 
 | ||||
|           std::deque<int> nalSizes = nalu::parseNalSizes(thisPacket); | ||||
|           for(std::deque<int>::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){ | ||||
|             int encrypted = (*it - 5) & ~0xF;//Bitmask to a multiple of 16
 | ||||
|           for (std::deque<int>::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){ | ||||
|             int encrypted = (*it - 5) & ~0xF; // Bitmask to a multiple of 16
 | ||||
|             MP4::UUID_SampleEncryption_Sample_Entry newEntry; | ||||
|             newEntry.BytesClear = *it - encrypted;//Size + nal_unit_type
 | ||||
|             newEntry.BytesEncrypted = encrypted;//Entire NAL except nal_unit_type;
 | ||||
|             newEntry.BytesClear = *it - encrypted; // Size + nal_unit_type
 | ||||
|             newEntry.BytesEncrypted = encrypted;   // Entire NAL except nal_unit_type;
 | ||||
|             newSample.Entries.push_back(newEntry); | ||||
|           } | ||||
|           sEnc.setSample(newSample, i); | ||||
|  | @ -321,7 +324,7 @@ namespace Mist { | |||
|     } | ||||
|     seek(seekTime); | ||||
|     /*LTS-END*/ | ||||
|     //Setting the correct offsets.
 | ||||
|     // Setting the correct offsets.
 | ||||
|     moof_box.setContent(traf_box, 1); | ||||
|     trun_box.setDataOffset(moof_box.boxedSize() + 8); | ||||
|     traf_box.setContent(trun_box, 1); | ||||
|  | @ -343,11 +346,11 @@ namespace Mist { | |||
|   void OutHSS::loadEncryption(){ | ||||
|     static bool encryptionLoaded = false; | ||||
|     if (!encryptionLoaded){ | ||||
|       //Load the encryption data page
 | ||||
|       // Load the encryption data page
 | ||||
|       char pageName[NAME_BUFFER_SIZE]; | ||||
|       snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str()); | ||||
|       nProxy.encryptionPage.init(pageName, 8 * 1024 * 1024, false, false); | ||||
|       if (nProxy.encryptionPage.mapped) { | ||||
|       if (nProxy.encryptionPage.mapped){ | ||||
|         nProxy.vmData.read(nProxy.encryptionPage.mapped); | ||||
|         nProxy.encrypt = true; | ||||
|       } | ||||
|  | @ -355,9 +358,12 @@ namespace Mist { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   std::string OutHSS::protectionHeader() { | ||||
|   std::string OutHSS::protectionHeader(){ | ||||
|     loadEncryption(); | ||||
|     std::string xmlGen = "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>"; | ||||
|     std::string xmlGen = | ||||
|         "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" " | ||||
|         "version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></" | ||||
|         "PROTECTINFO><KID>"; | ||||
|     xmlGen += nProxy.vmData.keyid; | ||||
|     xmlGen += "</KID><LA_URL>"; | ||||
|     xmlGen += nProxy.vmData.laurl; | ||||
|  | @ -378,89 +384,96 @@ namespace Mist { | |||
|   } | ||||
|   /*LTS-END*/ | ||||
| 
 | ||||
| 
 | ||||
|   ///\brief Builds an index file for HTTP Smooth streaming.
 | ||||
|   ///\param encParams The encryption parameters. /*LTS*/
 | ||||
|   ///\return The index file for HTTP Smooth Streaming.
 | ||||
|   std::string OutHSS::smoothIndex(){ | ||||
|     loadEncryption();//LTS
 | ||||
|     loadEncryption(); // LTS
 | ||||
|     updateMeta(); | ||||
|     std::stringstream Result; | ||||
|     Result << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n"; | ||||
|     Result << "<SmoothStreamingMedia " | ||||
|            "MajorVersion=\"2\" " | ||||
|            "MinorVersion=\"0\" " | ||||
|            "TimeScale=\"10000000\" "; | ||||
|               "MajorVersion=\"2\" " | ||||
|               "MinorVersion=\"0\" " | ||||
|               "TimeScale=\"10000000\" "; | ||||
|     std::deque<std::map<unsigned int, DTSC::Track>::iterator> audioIters; | ||||
|     std::deque<std::map<unsigned int, DTSC::Track>::iterator> videoIters; | ||||
|     long long int maxWidth = 0; | ||||
|     long long int maxHeight = 0; | ||||
|     long long int minWidth = 99999999; | ||||
|     long long int minHeight = 99999999; | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { | ||||
|       if (it->second.codec == "AAC") { | ||||
|         audioIters.push_back(it); | ||||
|       } | ||||
|       if (it->second.codec == "H264") { | ||||
|     for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|          it != myMeta.tracks.end(); it++){ | ||||
|       if (it->second.codec == "AAC"){audioIters.push_back(it);} | ||||
|       if (it->second.codec == "H264"){ | ||||
|         videoIters.push_back(it); | ||||
|         if (it->second.width > maxWidth) { | ||||
|           maxWidth = it->second.width; | ||||
|         } | ||||
|         if (it->second.width < minWidth) { | ||||
|           minWidth = it->second.width; | ||||
|         } | ||||
|         if (it->second.height > maxHeight) { | ||||
|           maxHeight = it->second.height; | ||||
|         } | ||||
|         if (it->second.height < minHeight) { | ||||
|           minHeight = it->second.height; | ||||
|         } | ||||
|         if (it->second.width > maxWidth){maxWidth = it->second.width;} | ||||
|         if (it->second.width < minWidth){minWidth = it->second.width;} | ||||
|         if (it->second.height > maxHeight){maxHeight = it->second.height;} | ||||
|         if (it->second.height < minHeight){minHeight = it->second.height;} | ||||
|       } | ||||
|     } | ||||
|     DEBUG_MSG(DLVL_DONTEVEN, "Buffer window here %lld", myMeta.bufferWindow); | ||||
|     if (myMeta.vod) { | ||||
|       Result << "Duration=\"" << ((*videoIters.begin())->second.lastms - (*videoIters.begin())->second.firstms) << "0000\""; | ||||
|     } else { | ||||
|     if (myMeta.vod){ | ||||
|       Result << "Duration=\"" | ||||
|              << ((*videoIters.begin())->second.lastms - (*videoIters.begin())->second.firstms) << "0000\""; | ||||
|     }else{ | ||||
|       Result << "Duration=\"0\" " | ||||
|              "IsLive=\"TRUE\" " | ||||
|              "LookAheadFragmentCount=\"2\" " | ||||
|              "DVRWindowLength=\"" << myMeta.bufferWindow << "0000\" " | ||||
|              "CanSeek=\"TRUE\" " | ||||
|              "CanPause=\"TRUE\" "; | ||||
|                 "IsLive=\"TRUE\" " | ||||
|                 "LookAheadFragmentCount=\"2\" " | ||||
|                 "DVRWindowLength=\"" | ||||
|              << myMeta.bufferWindow | ||||
|              << "0000\" " | ||||
|                 "CanSeek=\"TRUE\" " | ||||
|                 "CanPause=\"TRUE\" "; | ||||
|     } | ||||
|     Result << ">\n"; | ||||
| 
 | ||||
|     //Add audio entries
 | ||||
|     if (audioIters.size()) { | ||||
|     // Add audio entries
 | ||||
|     if (audioIters.size()){ | ||||
|       Result << "<StreamIndex " | ||||
|              "Type=\"audio\" " | ||||
|              "QualityLevels=\"" << audioIters.size() << "\" " | ||||
|              "Name=\"audio\" " | ||||
|              "Chunks=\"" << (*audioIters.begin())->second.keys.size() << "\" " | ||||
|              "Url=\"Q({bitrate})/A({start time})\">\n"; | ||||
|                 "Type=\"audio\" " | ||||
|                 "QualityLevels=\"" | ||||
|              << audioIters.size() | ||||
|              << "\" " | ||||
|                 "Name=\"audio\" " | ||||
|                 "Chunks=\"" | ||||
|              << (*audioIters.begin())->second.keys.size() | ||||
|              << "\" " | ||||
|                 "Url=\"Q({bitrate})/A({start time})\">\n"; | ||||
|       int index = 0; | ||||
|       for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin(); it != audioIters.end(); it++) { | ||||
|       for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin(); | ||||
|            it != audioIters.end(); it++){ | ||||
|         Result << "<QualityLevel " | ||||
|                "Index=\"" << index << "\" " | ||||
|                "Bitrate=\"" << bpsAndIdToBitrate((*it)->second.bps, (*it)->first) << "\" " | ||||
|                "CodecPrivateData=\"" << std::hex; | ||||
|         for (unsigned int i = 0; i < (*it)->second.init.size(); i++) { | ||||
|                   "Index=\"" | ||||
|                << index | ||||
|                << "\" " | ||||
|                   "Bitrate=\"" | ||||
|                << bpsAndIdToBitrate((*it)->second.bps, (*it)->first) | ||||
|                << "\" " | ||||
|                   "CodecPrivateData=\"" | ||||
|                << std::hex; | ||||
|         for (unsigned int i = 0; i < (*it)->second.init.size(); i++){ | ||||
|           Result << std::setfill('0') << std::setw(2) << std::right << (int)(*it)->second.init[i]; | ||||
|         } | ||||
|         Result << std::dec << "\" " | ||||
|                "SamplingRate=\"" << (*it)->second.rate << "\" " | ||||
|                "Channels=\"2\" " | ||||
|                "BitsPerSample=\"16\" " | ||||
|                "PacketSize=\"4\" " | ||||
|                "AudioTag=\"255\" " | ||||
|                "FourCC=\"AACL\" >\n"; | ||||
|         Result << std::dec | ||||
|                << "\" " | ||||
|                   "SamplingRate=\"" | ||||
|                << (*it)->second.rate | ||||
|                << "\" " | ||||
|                   "Channels=\"2\" " | ||||
|                   "BitsPerSample=\"16\" " | ||||
|                   "PacketSize=\"4\" " | ||||
|                   "AudioTag=\"255\" " | ||||
|                   "FourCC=\"AACL\" >\n"; | ||||
|         Result << "</QualityLevel>\n"; | ||||
|         index++; | ||||
|       } | ||||
|       if ((*audioIters.begin())->second.keys.size()) { | ||||
|         for (std::deque<DTSC::Key>::iterator it = (*audioIters.begin())->second.keys.begin(); it != (((*audioIters.begin())->second.keys.end()) - 1); it++) { | ||||
|       if ((*audioIters.begin())->second.keys.size()){ | ||||
|         for (std::deque<DTSC::Key>::iterator it = (*audioIters.begin())->second.keys.begin(); | ||||
|              it != (((*audioIters.begin())->second.keys.end()) - 1); it++){ | ||||
|           Result << "<c "; | ||||
|           if (it == (*audioIters.begin())->second.keys.begin()) { | ||||
|           if (it == (*audioIters.begin())->second.keys.begin()){ | ||||
|             Result << "t=\"" << it->getTime() * 10000 << "\" "; | ||||
|           } | ||||
|           Result << "d=\"" << it->getLength() * 10000 << "\" />\n"; | ||||
|  | @ -468,42 +481,65 @@ namespace Mist { | |||
|       } | ||||
|       Result << "</StreamIndex>\n"; | ||||
|     } | ||||
|     //Add video entries
 | ||||
|     if (videoIters.size()) { | ||||
|     // Add video entries
 | ||||
|     if (videoIters.size()){ | ||||
|       Result << "<StreamIndex " | ||||
|              "Type=\"video\" " | ||||
|              "QualityLevels=\"" << videoIters.size() << "\" " | ||||
|              "Name=\"video\" " | ||||
|              "Chunks=\"" << (*videoIters.begin())->second.keys.size() << "\" " | ||||
|              "Url=\"Q({bitrate})/V({start time})\" " | ||||
|              "MaxWidth=\"" << maxWidth << "\" " | ||||
|              "MaxHeight=\"" << maxHeight << "\" " | ||||
|              "DisplayWidth=\"" << maxWidth << "\" " | ||||
|              "DisplayHeight=\"" << maxHeight << "\">\n"; | ||||
|                 "Type=\"video\" " | ||||
|                 "QualityLevels=\"" | ||||
|              << videoIters.size() | ||||
|              << "\" " | ||||
|                 "Name=\"video\" " | ||||
|                 "Chunks=\"" | ||||
|              << (*videoIters.begin())->second.keys.size() | ||||
|              << "\" " | ||||
|                 "Url=\"Q({bitrate})/V({start time})\" " | ||||
|                 "MaxWidth=\"" | ||||
|              << maxWidth | ||||
|              << "\" " | ||||
|                 "MaxHeight=\"" | ||||
|              << maxHeight | ||||
|              << "\" " | ||||
|                 "DisplayWidth=\"" | ||||
|              << maxWidth | ||||
|              << "\" " | ||||
|                 "DisplayHeight=\"" | ||||
|              << maxHeight << "\">\n"; | ||||
|       int index = 0; | ||||
|       for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin(); it != videoIters.end(); it++) { | ||||
|         //Add video qualities
 | ||||
|       for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin(); | ||||
|            it != videoIters.end(); it++){ | ||||
|         // Add video qualities
 | ||||
|         Result << "<QualityLevel " | ||||
|                "Index=\"" << index << "\" " | ||||
|                "Bitrate=\"" << bpsAndIdToBitrate((*it)->second.bps, (*it)->first) << "\" " | ||||
|                "CodecPrivateData=\"" << std::hex; | ||||
|                   "Index=\"" | ||||
|                << index | ||||
|                << "\" " | ||||
|                   "Bitrate=\"" | ||||
|                << bpsAndIdToBitrate((*it)->second.bps, (*it)->first) | ||||
|                << "\" " | ||||
|                   "CodecPrivateData=\"" | ||||
|                << std::hex; | ||||
|         MP4::AVCC avccbox; | ||||
|         avccbox.setPayload((*it)->second.init); | ||||
|         std::string tmpString = avccbox.asAnnexB(); | ||||
|         for (unsigned int i = 0; i < tmpString.size(); i++) { | ||||
|         for (unsigned int i = 0; i < tmpString.size(); i++){ | ||||
|           Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i]; | ||||
|         } | ||||
|         Result << std::dec << "\" " | ||||
|                "MaxWidth=\"" << (*it)->second.width << "\" " | ||||
|                "MaxHeight=\"" << (*it)->second.height << "\" " | ||||
|                "FourCC=\"AVC1\" >\n"; | ||||
|         Result << std::dec | ||||
|                << "\" " | ||||
|                   "MaxWidth=\"" | ||||
|                << (*it)->second.width | ||||
|                << "\" " | ||||
|                   "MaxHeight=\"" | ||||
|                << (*it)->second.height | ||||
|                << "\" " | ||||
|                   "FourCC=\"AVC1\" >\n"; | ||||
|         Result << "</QualityLevel>\n"; | ||||
|         index++; | ||||
|       } | ||||
|       if ((*videoIters.begin())->second.keys.size()) { | ||||
|         for (std::deque<DTSC::Key>::iterator it = (*videoIters.begin())->second.keys.begin(); it != (((*videoIters.begin())->second.keys.end()) - 1); it++) { | ||||
|       if ((*videoIters.begin())->second.keys.size()){ | ||||
|         for (std::deque<DTSC::Key>::iterator it = (*videoIters.begin())->second.keys.begin(); | ||||
|              it != (((*videoIters.begin())->second.keys.end()) - 1); it++){ | ||||
|           Result << "<c "; | ||||
|           if (it == (*videoIters.begin())->second.keys.begin()) { | ||||
|           if (it == (*videoIters.begin())->second.keys.begin()){ | ||||
|             Result << "t=\"" << it->getTime() * 10000 << "\" "; | ||||
|           } | ||||
|           Result << "d=\"" << it->getLength() * 10000 << "\" />\n"; | ||||
|  | @ -512,7 +548,7 @@ namespace Mist { | |||
|       Result << "</StreamIndex>\n"; | ||||
|     } | ||||
|     /*LTS-START*/ | ||||
|     if (nProxy.encrypt) { | ||||
|     if (nProxy.encrypt){ | ||||
|       Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">"; | ||||
|       Result << protectionHeader(); | ||||
|       Result << "</ProtectionHeader></Protection>"; | ||||
|  | @ -524,10 +560,9 @@ namespace Mist { | |||
|     std::cerr << "Sending this manifest:" << std::endl << Result << std::endl; | ||||
| #endif | ||||
|     return toUTF16(Result.str()); | ||||
|   } //smoothIndex
 | ||||
|   }// smoothIndex
 | ||||
| 
 | ||||
| 
 | ||||
|   void OutHSS::onHTTP() { | ||||
|   void OutHSS::onHTTP(){ | ||||
|     if ((H.method == "OPTIONS" || H.method == "HEAD") && H.url.find("Manifest") == std::string::npos){ | ||||
|       H.Clean(); | ||||
|       H.SetHeader("Content-Type", "application/octet-stream"); | ||||
|  | @ -538,14 +573,14 @@ namespace Mist { | |||
|       return; | ||||
|     } | ||||
|     initialize(); | ||||
|     loadEncryption();//LTS
 | ||||
|     if (H.url.find("Manifest") != std::string::npos) { | ||||
|       //Manifest, direct reply
 | ||||
|     loadEncryption(); // LTS
 | ||||
|     if (H.url.find("Manifest") != std::string::npos){ | ||||
|       // Manifest, direct reply
 | ||||
|       H.Clean(); | ||||
|       H.SetHeader("Content-Type", "text/xml"); | ||||
|       H.SetHeader("Cache-Control", "no-cache"); | ||||
|       H.setCORSHeaders(); | ||||
|       if(H.method == "OPTIONS" || H.method == "HEAD"){ | ||||
|       if (H.method == "OPTIONS" || H.method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         return; | ||||
|       } | ||||
|  | @ -553,10 +588,10 @@ namespace Mist { | |||
|       H.SetBody(manifest); | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); | ||||
|     } else { | ||||
|     }else{ | ||||
|       parseData = true; | ||||
|       wantRequest = false; | ||||
|       sendHeader(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,25 +1,26 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/http_parser.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutHSS : public HTTPOutput { | ||||
|     public: | ||||
|       OutHSS(Socket::Connection & conn); | ||||
|       ~OutHSS(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|     protected: | ||||
|       std::string protectionHeader();/*LTS*/ | ||||
|       std::string smoothIndex(); | ||||
|       void loadEncryption();/*LTS*/ | ||||
|       int canSeekms(unsigned int ms); | ||||
|       int keysToSend; | ||||
|       int myTrackStor; | ||||
|       int myKeyStor; | ||||
|       unsigned long long playUntil; | ||||
| namespace Mist{ | ||||
|   class OutHSS : public HTTPOutput{ | ||||
|   public: | ||||
|     OutHSS(Socket::Connection &conn); | ||||
|     ~OutHSS(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
| 
 | ||||
|   protected: | ||||
|     std::string protectionHeader(); /*LTS*/ | ||||
|     std::string smoothIndex(); | ||||
|     void loadEncryption(); /*LTS*/ | ||||
|     int canSeekms(unsigned int ms); | ||||
|     int keysToSend; | ||||
|     int myTrackStor; | ||||
|     int myKeyStor; | ||||
|     unsigned long long playUntil; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHSS mistOut; | ||||
|  |  | |||
|  | @ -1,19 +1,17 @@ | |||
| #include <sys/stat.h> | ||||
| #include "output_http.h" | ||||
| #include <mist/stream.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/util.h> | ||||
| #include <mist/langcodes.h> | ||||
| #include <mist/stream.h> | ||||
| #include <mist/util.h> | ||||
| #include <set> | ||||
| #include <sys/stat.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   HTTPOutput::HTTPOutput(Socket::Connection & conn) : Output(conn) { | ||||
| namespace Mist{ | ||||
|   HTTPOutput::HTTPOutput(Socket::Connection &conn) : Output(conn){ | ||||
|     webSock = 0; | ||||
|     idleInterval = 0; | ||||
|     idleLast = 0; | ||||
|     if (config->getString("ip").size()){ | ||||
|       myConn.setHost(config->getString("ip")); | ||||
|     } | ||||
|     if (config->getString("ip").size()){myConn.setHost(config->getString("ip"));} | ||||
|     firstRun = true; | ||||
|     if (config->getString("prequest").size()){ | ||||
|       myConn.Received().prepend(config->getString("prequest")); | ||||
|  | @ -21,14 +19,14 @@ namespace Mist { | |||
|     config->activate(); | ||||
|   } | ||||
| 
 | ||||
|   HTTPOutput::~HTTPOutput() { | ||||
|   HTTPOutput::~HTTPOutput(){ | ||||
|     if (webSock){ | ||||
|       delete webSock; | ||||
|       webSock = 0; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   void HTTPOutput::init(Util::Config * cfg){ | ||||
| 
 | ||||
|   void HTTPOutput::init(Util::Config *cfg){ | ||||
|     Output::init(cfg); | ||||
|     capa["deps"] = "HTTP"; | ||||
|     capa["forward"]["streamname"]["name"] = "Stream"; | ||||
|  | @ -40,33 +38,40 @@ namespace Mist { | |||
|     capa["forward"]["ip"]["type"] = "str"; | ||||
|     capa["forward"]["ip"]["option"] = "--ip"; | ||||
|     capa["forward"]["ip"]["name"] = "Previous request"; | ||||
|     capa["forward"]["ip"]["help"] = "Data to pretend arrived on the socket before parsing the socket."; | ||||
|     capa["forward"]["ip"]["help"] = | ||||
|         "Data to pretend arrived on the socket before parsing the socket."; | ||||
|     capa["forward"]["ip"]["type"] = "str"; | ||||
|     capa["forward"]["ip"]["option"] = "--prequest"; | ||||
|     cfg->addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); | ||||
|     cfg->addOption("ip", JSON::fromString("{\"arg\":\"string\",\"short\":\"I\",\"long\":\"ip\",\"help\":\"IP address of connection on stdio.\"}")); | ||||
|     cfg->addOption("prequest", JSON::fromString("{\"arg\":\"string\",\"short\":\"R\",\"long\":\"prequest\",\"help\":\"Data to pretend arrived on the socket before parsing the socket.\"}")); | ||||
|     cfg->addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":" | ||||
|                                                   "\"stream\",\"help\":\"The name of the stream " | ||||
|                                                   "that this connector will transmit.\"}")); | ||||
|     cfg->addOption("ip", JSON::fromString("{\"arg\":\"string\",\"short\":\"I\",\"long\":\"ip\"," | ||||
|                                           "\"help\":\"IP address of connection on stdio.\"}")); | ||||
|     cfg->addOption("prequest", JSON::fromString("{\"arg\":\"string\",\"short\":\"R\",\"long\":" | ||||
|                                                 "\"prequest\",\"help\":\"Data to pretend arrived " | ||||
|                                                 "on the socket before parsing the socket.\"}")); | ||||
|     cfg->addBasicConnectorOptions(capa); | ||||
|     config = cfg; | ||||
|   } | ||||
|    | ||||
|   void HTTPOutput::onFail(const std::string & msg, bool critical){ | ||||
| 
 | ||||
|   void HTTPOutput::onFail(const std::string &msg, bool critical){ | ||||
|     INFO_MSG("Failing '%s': %s", H.url.c_str(), msg.c_str()); | ||||
|     if (!webSock && !isRecording()){ | ||||
|       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.SetHeader("Server", "MistServer/" PACKAGE_VERSION); | ||||
|       H.setCORSHeaders(); | ||||
|       H.SetBody("Could not retrieve stream: "+msg); | ||||
|       H.SetBody("Could not retrieve stream: " + msg); | ||||
|       H.SendResponse("404", "Error opening stream", myConn); | ||||
|     } | ||||
|     Output::onFail(msg, critical); | ||||
|   } | ||||
|    | ||||
|   bool isMatch(const std::string & url, const std::string & m, std::string & streamname){ | ||||
| 
 | ||||
|   bool isMatch(const std::string &url, const std::string &m, std::string &streamname){ | ||||
|     size_t found = m.find('$'); | ||||
|     if (found != std::string::npos){ | ||||
|       if (url.size() < m.size()){return false;} | ||||
|       if (m.substr(0, found) == url.substr(0, found) && m.substr(found+1) == url.substr(url.size() - (m.size() - found) + 1)){ | ||||
|       if (m.substr(0, found) == url.substr(0, found) && | ||||
|           m.substr(found + 1) == url.substr(url.size() - (m.size() - found) + 1)){ | ||||
|         if (url.substr(found, url.size() - m.size() + 1).find('/') != std::string::npos){ | ||||
|           return false; | ||||
|         } | ||||
|  | @ -76,16 +81,14 @@ namespace Mist { | |||
|     } | ||||
|     return (url == m); | ||||
|   } | ||||
|    | ||||
|   bool isPrefix(const std::string & url, const std::string & m, std::string & streamname){ | ||||
| 
 | ||||
|   bool isPrefix(const std::string &url, const std::string &m, std::string &streamname){ | ||||
|     size_t found = m.find('$'); | ||||
|     if (found != std::string::npos){ | ||||
|       if (url.size() < m.size()){return false;} | ||||
|       size_t found_suf = url.find(m.substr(found+1), found); | ||||
|       size_t found_suf = url.find(m.substr(found + 1), found); | ||||
|       if (m.substr(0, found) == url.substr(0, found) && found_suf != std::string::npos){ | ||||
|         if (url.substr(found, found_suf - found).find('/') != std::string::npos){ | ||||
|           return false; | ||||
|         } | ||||
|         if (url.substr(found, found_suf - found).find('/') != std::string::npos){return false;} | ||||
|         streamname = url.substr(found, found_suf - found); | ||||
|         return true; | ||||
|       } | ||||
|  | @ -94,18 +97,18 @@ namespace Mist { | |||
|     } | ||||
|     return false; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   /// - anything else: The request should be dispatched to a connector on the named socket.
 | ||||
|   std::string HTTPOutput::getHandler(){ | ||||
|     std::string url = H.getUrl(); | ||||
|     //check the current output first, the most common case
 | ||||
|     // check the current output first, the most common case
 | ||||
|     if (capa.isMember("url_match") || capa.isMember("url_prefix")){ | ||||
|       bool match = false; | ||||
|       std::string streamname; | ||||
|       //if there is a matcher, try to match
 | ||||
|       // if there is a matcher, try to match
 | ||||
|       if (capa.isMember("url_match")){ | ||||
|         if (capa["url_match"].isArray()){ | ||||
|           jsonForEach(capa["url_match"], it) { | ||||
|           jsonForEach(capa["url_match"], it){ | ||||
|             match |= isMatch(url, it->asStringRef(), streamname); | ||||
|           } | ||||
|         } | ||||
|  | @ -113,10 +116,10 @@ namespace Mist { | |||
|           match |= isMatch(url, capa["url_match"].asStringRef(), streamname); | ||||
|         } | ||||
|       } | ||||
|       //if there is a prefix, try to match
 | ||||
|       // if there is a prefix, try to match
 | ||||
|       if (capa.isMember("url_prefix")){ | ||||
|         if (capa["url_prefix"].isArray()){ | ||||
|           jsonForEach(capa["url_prefix"], it) { | ||||
|           jsonForEach(capa["url_prefix"], it){ | ||||
|             match |= isPrefix(url, it->asStringRef(), streamname); | ||||
|           } | ||||
|         } | ||||
|  | @ -132,18 +135,19 @@ namespace Mist { | |||
|         return capa["name"].asStringRef(); | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     //loop over the connectors
 | ||||
| 
 | ||||
|     // loop over the connectors
 | ||||
|     Util::DTSCShmReader rCapa(SHM_CAPA); | ||||
|     DTSC::Scan capa = rCapa.getMember("connectors"); | ||||
|     unsigned int capa_ctr = capa.getSize(); | ||||
|     for (unsigned int i = 0; i < capa_ctr; ++i){ | ||||
|       DTSC::Scan c = capa.getIndice(i); | ||||
|       //if it depends on HTTP and has a match or prefix...
 | ||||
|       if ((c.getMember("name").asString() == "HTTP" || c.getMember("deps").asString() == "HTTP") && (c.getMember("url_match") || c.getMember("url_prefix"))){ | ||||
|       // if it depends on HTTP and has a match or prefix...
 | ||||
|       if ((c.getMember("name").asString() == "HTTP" || c.getMember("deps").asString() == "HTTP") && | ||||
|           (c.getMember("url_match") || c.getMember("url_prefix"))){ | ||||
|         bool match = false; | ||||
|         std::string streamname; | ||||
|         //if there is a matcher, try to match
 | ||||
|         // if there is a matcher, try to match
 | ||||
|         if (c.getMember("url_match")){ | ||||
|           if (c.getMember("url_match").getSize()){ | ||||
|             for (unsigned int j = 0; j < c.getMember("url_match").getSize(); ++j){ | ||||
|  | @ -153,7 +157,7 @@ namespace Mist { | |||
|             match |= isMatch(url, c.getMember("url_match").asString(), streamname); | ||||
|           } | ||||
|         } | ||||
|         //if there is a prefix, try to match
 | ||||
|         // if there is a prefix, try to match
 | ||||
|         if (c.getMember("url_prefix")){ | ||||
|           if (c.getMember("url_prefix").getSize()){ | ||||
|             for (unsigned int j = 0; j < c.getMember("url_prefix").getSize(); ++j){ | ||||
|  | @ -174,14 +178,14 @@ namespace Mist { | |||
|     } | ||||
|     return ""; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   void HTTPOutput::requestHandler(){ | ||||
|     //Handle onIdle function caller, if needed
 | ||||
|     // Handle onIdle function caller, if needed
 | ||||
|     if (idleInterval && (Util::bootMS() > idleLast + idleInterval)){ | ||||
|       onIdle(); | ||||
|       idleLast = Util::bootMS(); | ||||
|     } | ||||
|     //Handle websockets
 | ||||
|     // Handle websockets
 | ||||
|     if (webSock){ | ||||
|       if (webSock->readFrame()){ | ||||
|         onWebsocketFrame(); | ||||
|  | @ -191,7 +195,7 @@ namespace Mist { | |||
|       if (!isBlocking && !parseData){Util::sleep(100);} | ||||
|       return; | ||||
|     } | ||||
|     //If we can't read anything more and we're non-blocking, sleep some.
 | ||||
|     // If we can't read anything more and we're non-blocking, sleep some.
 | ||||
|     if (!firstRun && !myConn.spool()){ | ||||
|       if (!isBlocking && !parseData){Util::sleep(100);} | ||||
|       return; | ||||
|  | @ -200,20 +204,24 @@ namespace Mist { | |||
| 
 | ||||
|     while (H.Read(myConn)){ | ||||
|       std::string handler = getHandler(); | ||||
|       INFO_MSG("Received request: %s => %s (%s)", H.getUrl().c_str(), handler.c_str(), H.GetVar("stream").c_str()); | ||||
|       INFO_MSG("Received request: %s => %s (%s)", H.getUrl().c_str(), handler.c_str(), | ||||
|                H.GetVar("stream").c_str()); | ||||
|       if (!handler.size()){ | ||||
|         H.Clean(); | ||||
|         H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); | ||||
|         H.setCORSHeaders(); | ||||
|         H.SetBody("<!DOCTYPE html><html><head><title>Unsupported Media Type</title></head><body><h1>Unsupported Media Type</h1>The server isn't quite sure what you wanted to receive from it.</body></html>"); | ||||
|         H.SetBody("<!DOCTYPE html><html><head><title>Unsupported Media " | ||||
|                   "Type</title></head><body><h1>Unsupported Media Type</h1>The server isn't quite " | ||||
|                   "sure what you wanted to receive from it.</body></html>"); | ||||
|         H.SendResponse("415", "Unsupported Media Type", myConn); | ||||
|         myConn.close(); | ||||
|         return; | ||||
|       } | ||||
|     std::string connHeader = H.GetHeader("Connection"); | ||||
|     Util::stringToLower(connHeader); | ||||
|       std::string connHeader = H.GetHeader("Connection"); | ||||
|       Util::stringToLower(connHeader); | ||||
|       if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){ | ||||
|         MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str()); | ||||
|         MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), | ||||
|                    streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str()); | ||||
|         streamName = H.GetVar("stream"); | ||||
|         nProxy.userClient.finish(); | ||||
|         statsPage.finish(); | ||||
|  | @ -225,9 +233,7 @@ namespace Mist { | |||
|       /*LTS-START*/ | ||||
|       reqUrl = H.url + H.allVars(); | ||||
|       /*LTS-END*/ | ||||
|       if (H.hasHeader("User-Agent")){ | ||||
|         UA = H.GetHeader("User-Agent"); | ||||
|       } | ||||
|       if (H.hasHeader("User-Agent")){UA = H.GetHeader("User-Agent");} | ||||
|       if (hasSessionIDs()){ | ||||
|         if (H.GetVar("sessId").size()){ | ||||
|           std::string ua = H.GetVar("sessId"); | ||||
|  | @ -248,8 +254,8 @@ namespace Mist { | |||
|       if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");} | ||||
|       if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");} | ||||
|       if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");} | ||||
|       //allow setting of play back rate through buffer variable.
 | ||||
|       //play back rate is set in MS per second, but the variable is a simple multiplier.
 | ||||
|       // allow setting of play back rate through buffer variable.
 | ||||
|       // play back rate is set in MS per second, but the variable is a simple multiplier.
 | ||||
|       if (H.GetVar("rate") != ""){ | ||||
|         long long int multiplier = JSON::Value(H.GetVar("rate")).asInt(); | ||||
|         if (multiplier){ | ||||
|  | @ -266,7 +272,7 @@ namespace Mist { | |||
|           realTime = 0; | ||||
|         } | ||||
|       } | ||||
|       //Handle upgrade to websocket if the output supports it
 | ||||
|       // Handle upgrade to websocket if the output supports it
 | ||||
|       std::string upgradeHeader = H.GetHeader("Upgrade"); | ||||
|       Util::stringToLower(upgradeHeader); | ||||
|       if (doesWebsockets() && upgradeHeader == "websocket"){ | ||||
|  | @ -287,9 +293,7 @@ namespace Mist { | |||
|       preHTTP(); | ||||
|       onHTTP(); | ||||
|       idleLast = Util::bootMS(); | ||||
|       if (!H.bufferChunks){ | ||||
|         H.Clean(); | ||||
|       } | ||||
|       if (!H.bufferChunks){H.Clean();} | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -298,54 +302,54 @@ namespace Mist { | |||
|     initialize(); | ||||
|     selectDefaultTracks(); | ||||
|   } | ||||
|    | ||||
|   static inline void builPipedPart(JSON::Value & p, char * argarr[], int & argnum, JSON::Value & argset){ | ||||
|     jsonForEach(argset, it) { | ||||
| 
 | ||||
|   static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, JSON::Value &argset){ | ||||
|     jsonForEach(argset, it){ | ||||
|       if (it->isMember("option") && p.isMember(it.key())){ | ||||
|         if (it->isMember("type")){ | ||||
|           if ((*it)["type"].asStringRef() == "str" && !p[it.key()].isString()){ | ||||
|             p[it.key()] = p[it.key()].asString(); | ||||
|           } | ||||
|           if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "debug"){ | ||||
|           if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" || | ||||
|               (*it)["type"].asStringRef() == "debug"){ | ||||
|             p[it.key()] = JSON::Value(p[it.key()].asInt()).asString(); | ||||
|           } | ||||
|           if ((*it)["type"].asStringRef() == "inputlist" && p[it.key()].isArray()){ | ||||
|             jsonForEach(p[it.key()], iVal){ | ||||
|               (*iVal) = iVal->asString(); | ||||
|               argarr[argnum++] = (char*)((*it)["option"].c_str()); | ||||
|               argarr[argnum++] = (char*)((*iVal).c_str()); | ||||
|               argarr[argnum++] = (char *)((*it)["option"].c_str()); | ||||
|               argarr[argnum++] = (char *)((*iVal).c_str()); | ||||
|             } | ||||
|             continue; | ||||
|           } | ||||
|         } | ||||
|         if (p[it.key()].asStringRef().size() > 0){ | ||||
|           argarr[argnum++] = (char*)((*it)["option"].c_str()); | ||||
|           argarr[argnum++] = (char*)(p[it.key()].c_str()); | ||||
|           argarr[argnum++] = (char *)((*it)["option"].c_str()); | ||||
|           argarr[argnum++] = (char *)(p[it.key()].c_str()); | ||||
|         }else{ | ||||
|           argarr[argnum++] = (char*)((*it)["option"].c_str()); | ||||
|           argarr[argnum++] = (char *)((*it)["option"].c_str()); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   ///\brief Handles requests by starting a corresponding output process.
 | ||||
|   ///\param connector The type of connector to be invoked.
 | ||||
|   void HTTPOutput::reConnector(std::string & connector){ | ||||
|     //taken from CheckProtocols (controller_connectors.cpp)
 | ||||
|     char * argarr[32]; | ||||
|     for (int i=0; i<32; i++){argarr[i] = 0;} | ||||
|   void HTTPOutput::reConnector(std::string &connector){ | ||||
|     // taken from CheckProtocols (controller_connectors.cpp)
 | ||||
|     char *argarr[32]; | ||||
|     for (int i = 0; i < 32; i++){argarr[i] = 0;} | ||||
|     int id = -1; | ||||
|     JSON::Value pipedCapa; | ||||
|     JSON::Value p;//properties of protocol
 | ||||
|      | ||||
|     JSON::Value p; // properties of protocol
 | ||||
| 
 | ||||
|     { | ||||
|       Util::DTSCShmReader rProto(SHM_PROTO); | ||||
|       DTSC::Scan prots = rProto.getScan(); | ||||
|       unsigned int prots_ctr = prots.getSize(); | ||||
|       | ||||
| 
 | ||||
|       if (connector == "HTTP" || connector == "HTTP.exe"){ | ||||
|         //restore from values in the environment, regardless of configged settings
 | ||||
|         // restore from values in the environment, regardless of configged settings
 | ||||
|         if (getenv("MIST_HTTP_nostreamtext")){ | ||||
|           p["nostreamtext"] = getenv("MIST_HTTP_nostreamtext"); | ||||
|         } | ||||
|  | @ -354,60 +358,60 @@ namespace Mist { | |||
|           p["pubaddr"] = JSON::fromString(pubAddrs); | ||||
|         } | ||||
|       }else{ | ||||
|         //find connector in config
 | ||||
|         for (unsigned int i=0; i < prots_ctr; ++i){ | ||||
|           if (prots.getIndice(i).getMember("connector").asString() == connector) { | ||||
|             id =  i; | ||||
|             break;    //pick the first protocol in the list that matches the connector 
 | ||||
|         // find connector in config
 | ||||
|         for (unsigned int i = 0; i < prots_ctr; ++i){ | ||||
|           if (prots.getIndice(i).getMember("connector").asString() == connector){ | ||||
|             id = i; | ||||
|             break; // pick the first protocol in the list that matches the connector
 | ||||
|           } | ||||
|         } | ||||
|         if (id == -1) { | ||||
|         if (id == -1){ | ||||
|           connector = connector + ".exe"; | ||||
|           for (unsigned int i=0; i < prots_ctr; ++i){ | ||||
|             if (prots.getIndice(i).getMember("connector").asString() == connector) { | ||||
|               id =  i; | ||||
|               break;    //pick the first protocol in the list that matches the connector 
 | ||||
|           for (unsigned int i = 0; i < prots_ctr; ++i){ | ||||
|             if (prots.getIndice(i).getMember("connector").asString() == connector){ | ||||
|               id = i; | ||||
|               break; // pick the first protocol in the list that matches the connector
 | ||||
|             } | ||||
|           } | ||||
|           if (id == -1) { | ||||
|           if (id == -1){ | ||||
|             connector = connector.substr(0, connector.size() - 4); | ||||
|             ERROR_MSG("No connector found for: %s", connector.c_str()); | ||||
|             return; | ||||
|           } | ||||
|         } | ||||
|         //read options from found connector
 | ||||
|         // read options from found connector
 | ||||
|         p = prots.getIndice(id).asJSON(); | ||||
|       } | ||||
|        | ||||
| 
 | ||||
|       HIGH_MSG("Connector found: %s", connector.c_str()); | ||||
|       Util::DTSCShmReader rCapa(SHM_CAPA); | ||||
|       DTSC::Scan capa = rCapa.getMember("connectors"); | ||||
|       pipedCapa = capa.getMember(connector).asJSON(); | ||||
|     } | ||||
| 
 | ||||
|     //build arguments for starting output process
 | ||||
|     // build arguments for starting output process
 | ||||
|     std::string tmparg = Util::getMyPath() + std::string("MistOut") + connector; | ||||
|     std::string tmpPrequest; | ||||
|     if (H.url.size()){tmpPrequest = H.BuildRequest();} | ||||
|     int argnum = 0; | ||||
|     argarr[argnum++] = (char*)tmparg.c_str(); | ||||
|     std::string temphost=getConnectedHost(); | ||||
|     argarr[argnum++] = (char *)tmparg.c_str(); | ||||
|     std::string temphost = getConnectedHost(); | ||||
|     std::string debuglevel = JSON::Value(Util::Config::printDebugLevel).asString(); | ||||
|     argarr[argnum++] = (char*)"--ip"; | ||||
|     argarr[argnum++] = (char*)(temphost.c_str()); | ||||
|     argarr[argnum++] = (char*)"--stream"; | ||||
|     argarr[argnum++] = (char*)(streamName.c_str()); | ||||
|     argarr[argnum++] = (char*)"--prequest"; | ||||
|     argarr[argnum++] = (char*)(tmpPrequest.c_str()); | ||||
|     //set the debug level if non-default
 | ||||
|     argarr[argnum++] = (char *)"--ip"; | ||||
|     argarr[argnum++] = (char *)(temphost.c_str()); | ||||
|     argarr[argnum++] = (char *)"--stream"; | ||||
|     argarr[argnum++] = (char *)(streamName.c_str()); | ||||
|     argarr[argnum++] = (char *)"--prequest"; | ||||
|     argarr[argnum++] = (char *)(tmpPrequest.c_str()); | ||||
|     // set the debug level if non-default
 | ||||
|     if (Util::Config::printDebugLevel != DEBUG){ | ||||
|       argarr[argnum++] = (char*)"--debug"; | ||||
|       argarr[argnum++] = (char*)(debuglevel.c_str()); | ||||
|       argarr[argnum++] = (char *)"--debug"; | ||||
|       argarr[argnum++] = (char *)(debuglevel.c_str()); | ||||
|     } | ||||
|     if (pipedCapa.isMember("required")){builPipedPart(p, argarr, argnum, pipedCapa["required"]);} | ||||
|     if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);} | ||||
|      | ||||
|     ///start new/better process
 | ||||
| 
 | ||||
|     /// start new/better process
 | ||||
|     execv(argarr[0], argarr); | ||||
|   } | ||||
| 
 | ||||
|  | @ -427,7 +431,7 @@ namespace Mist { | |||
|     return xRealIp; | ||||
|   } | ||||
|   std::string HTTPOutput::getConnectedBinHost(){ | ||||
|     //Do first check with connected host because of simplicity
 | ||||
|     // Do first check with connected host because of simplicity
 | ||||
|     std::string host = Output::getConnectedHost(); | ||||
|     std::string xRealIp = H.GetHeader("X-Real-IP"); | ||||
| 
 | ||||
|  | @ -439,13 +443,13 @@ namespace Mist { | |||
|       } | ||||
|       return Output::getConnectedBinHost(); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     Socket::Connection binConn; | ||||
|     binConn.setHost(xRealIp); | ||||
|     return binConn.getBinHost(); | ||||
|   } | ||||
| 
 | ||||
|   bool HTTPOutput::isTrustedProxy(const std::string & ip){ | ||||
|   bool HTTPOutput::isTrustedProxy(const std::string &ip){ | ||||
|     static std::set<std::string> trustedProxies; | ||||
|     if (!trustedProxies.size()){ | ||||
|       trustedProxies.insert("localhost"); | ||||
|  | @ -460,9 +464,7 @@ namespace Mist { | |||
|           endPos = trustedList.find(" ", pos); | ||||
|           trustedProxies.insert(trustedList.substr(pos, endPos - pos)); | ||||
|           pos = endPos; | ||||
|           if (pos != std::string::npos){ | ||||
|             pos++; | ||||
|           } | ||||
|           if (pos != std::string::npos){pos++;} | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | @ -473,11 +475,11 @@ namespace Mist { | |||
|     return false; | ||||
|   } | ||||
|   /*LTS-END*/ | ||||
|    | ||||
| 
 | ||||
|   /// Parses a "Range: " header, setting byteStart and byteEnd.
 | ||||
|   /// Assumes byteStart and byteEnd are initialized to their minimum respectively maximum values when the function is called.
 | ||||
|   /// On error, byteEnd is set to zero and the function return false.
 | ||||
|   bool HTTPOutput::parseRange(uint64_t & byteStart, uint64_t & byteEnd){ | ||||
|   /// Assumes byteStart and byteEnd are initialized to their minimum respectively maximum values
 | ||||
|   /// when the function is called. On error, byteEnd is set to zero and the function return false.
 | ||||
|   bool HTTPOutput::parseRange(uint64_t &byteStart, uint64_t &byteEnd){ | ||||
|     std::string header = H.GetHeader("Range"); | ||||
|     if (header.size() < 6 || header.substr(0, 6) != "bytes="){ | ||||
|       byteEnd = 0; | ||||
|  | @ -485,9 +487,9 @@ namespace Mist { | |||
|       return false; | ||||
|     } | ||||
|     header.erase(0, 6); | ||||
|     //Do parsing of the rest of the header...
 | ||||
|     // Do parsing of the rest of the header...
 | ||||
|     if (header.size() && header[0] == '-'){ | ||||
|       //negative range = count from end
 | ||||
|       // negative range = count from end
 | ||||
|       byteStart = 0; | ||||
|       for (unsigned int i = 1; i < header.size(); ++i){ | ||||
|         if (header[i] >= '0' && header[i] <= '9'){ | ||||
|  | @ -498,17 +500,17 @@ namespace Mist { | |||
|         break; | ||||
|       } | ||||
|       if (byteStart > byteEnd){ | ||||
|         //entire file if starting before byte zero
 | ||||
|         // entire file if starting before byte zero
 | ||||
|         byteStart = 0; | ||||
|       }else{ | ||||
|         //start byteStart bytes before byteEnd
 | ||||
|         // start byteStart bytes before byteEnd
 | ||||
|         byteStart = byteEnd - byteStart; | ||||
|       } | ||||
|       MEDIUM_MSG("Range request: %" PRIu64 "-%" PRIu64 " (%s)", byteStart, byteEnd, header.c_str()); | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     //Positive range
 | ||||
|     // Positive range
 | ||||
|     long long size = byteEnd; | ||||
|     byteEnd = 0; | ||||
|     byteStart = 0; | ||||
|  | @ -536,9 +538,7 @@ namespace Mist { | |||
|         } | ||||
|         break; | ||||
|       } | ||||
|       if (byteEnd > size){ | ||||
|         byteEnd = size; | ||||
|       } | ||||
|       if (byteEnd > size){byteEnd = size;} | ||||
|     }else{ | ||||
|       byteEnd = size; | ||||
|     } | ||||
|  | @ -546,5 +546,4 @@ namespace Mist { | |||
|     return true; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,37 +1,38 @@ | |||
| #pragma once | ||||
| #include "output.h" | ||||
| #include <mist/defines.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/websocket.h> | ||||
| #include "output.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
| 
 | ||||
|   class HTTPOutput : public Output{ | ||||
|   public: | ||||
|     HTTPOutput(Socket::Connection &conn); | ||||
|     virtual ~HTTPOutput(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     virtual void onFail(const std::string &msg, bool critical = false); | ||||
|     virtual void onHTTP(){}; | ||||
|     virtual void onIdle(){}; | ||||
|     virtual void onWebsocketFrame(){}; | ||||
|     virtual void onWebsocketConnect(){}; | ||||
|     virtual void preWebsocketConnect(){}; | ||||
|     virtual void requestHandler(); | ||||
|     virtual void preHTTP(); | ||||
|     static bool listenMode(){return false;} | ||||
|     virtual bool doesWebsockets(){return false;} | ||||
|     void reConnector(std::string &connector); | ||||
|     std::string getHandler(); | ||||
|     bool parseRange(uint64_t &byteStart, uint64_t &byteEnd); | ||||
| 
 | ||||
|   class HTTPOutput : public Output { | ||||
|     public: | ||||
|       HTTPOutput(Socket::Connection & conn); | ||||
|       virtual ~HTTPOutput(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       virtual void onFail(const std::string & msg, bool critical = false); | ||||
|       virtual void onHTTP(){}; | ||||
|       virtual void onIdle(){}; | ||||
|       virtual void onWebsocketFrame(){}; | ||||
|       virtual void onWebsocketConnect(){}; | ||||
|       virtual void preWebsocketConnect(){}; | ||||
|       virtual void requestHandler(); | ||||
|       virtual void preHTTP(); | ||||
|       static bool listenMode(){return false;} | ||||
|       virtual bool doesWebsockets(){return false;} | ||||
|       void reConnector(std::string & connector); | ||||
|       std::string getHandler(); | ||||
|       bool parseRange(uint64_t & byteStart, uint64_t & byteEnd); | ||||
|   protected: | ||||
|       bool firstRun; | ||||
|       HTTP::Parser H; | ||||
|       HTTP::Websocket * webSock; | ||||
|       uint32_t idleInterval; | ||||
|       uint64_t idleLast; | ||||
|       std::string getConnectedHost();//LTS
 | ||||
|       std::string getConnectedBinHost();//LTS
 | ||||
|       bool isTrustedProxy(const std::string & ip);//LTS
 | ||||
|     bool firstRun; | ||||
|     HTTP::Parser H; | ||||
|     HTTP::Websocket *webSock; | ||||
|     uint32_t idleInterval; | ||||
|     uint64_t idleLast; | ||||
|     std::string getConnectedHost();             // LTS
 | ||||
|     std::string getConnectedBinHost();          // LTS
 | ||||
|     bool isTrustedProxy(const std::string &ip); // LTS
 | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,29 +1,27 @@ | |||
| #include "output_http.h" | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   class OutHTTP : public HTTPOutput{ | ||||
|   public: | ||||
|     OutHTTP(Socket::Connection &conn); | ||||
|     ~OutHTTP(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     static bool listenMode(); | ||||
|     virtual void onFail(const std::string &msg, bool critical = false); | ||||
|     /// preHTTP is disabled in the internal HTTP output, since most don't need the stream alive to work
 | ||||
|     virtual void preHTTP(){}; | ||||
|     void HTMLResponse(); | ||||
|     void onHTTP(); | ||||
|     void sendIcon(); | ||||
|     bool websocketHandler(); | ||||
|     JSON::Value getStatusJSON(std::string &reqHost, const std::string &useragent = ""); | ||||
|     bool stayConnected; | ||||
|     virtual bool onFinish(){return stayConnected;} | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutHTTP : public HTTPOutput { | ||||
|     public: | ||||
|       OutHTTP(Socket::Connection & conn); | ||||
|       ~OutHTTP(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       static bool listenMode(); | ||||
|       virtual void onFail(const std::string & msg, bool critical = false); | ||||
|       ///preHTTP is disabled in the internal HTTP output, since most don't need the stream alive to work
 | ||||
|       virtual void preHTTP(){}; | ||||
|       void HTMLResponse(); | ||||
|       void onHTTP(); | ||||
|       void sendIcon(); | ||||
|       bool websocketHandler(); | ||||
|       JSON::Value getStatusJSON(std::string & reqHost, const std::string & useragent = ""); | ||||
|       bool stayConnected; | ||||
|       virtual bool onFinish(){ | ||||
|         return stayConnected; | ||||
|       } | ||||
|     private: | ||||
|       std::string origStreamName; | ||||
|       std::string mistPath; | ||||
|   private: | ||||
|     std::string origStreamName; | ||||
|     std::string mistPath; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHTTP mistOut; | ||||
|  |  | |||
|  | @ -1,39 +1,41 @@ | |||
| #include "output_http_minimalserver.h" | ||||
| #include <fstream> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutHTTPMinimalServer::OutHTTPMinimalServer(Socket::Connection & conn) : HTTPOutput(conn){ | ||||
|     //resolve symlinks etc to a real path
 | ||||
|     char * rp = realpath(config->getString("webroot").c_str(), 0); | ||||
| namespace Mist{ | ||||
|   OutHTTPMinimalServer::OutHTTPMinimalServer(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     // resolve symlinks etc to a real path
 | ||||
|     char *rp = realpath(config->getString("webroot").c_str(), 0); | ||||
|     if (rp){ | ||||
|       resolved_path = rp; | ||||
|       resolved_path += "/"; | ||||
|       free(rp); | ||||
|     } | ||||
|   } | ||||
|   OutHTTPMinimalServer::~OutHTTPMinimalServer() {} | ||||
|    | ||||
|   void OutHTTPMinimalServer::init(Util::Config * cfg){ | ||||
|   OutHTTPMinimalServer::~OutHTTPMinimalServer(){} | ||||
| 
 | ||||
|   void OutHTTPMinimalServer::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "HTTPMinimalServer"; | ||||
|     capa["friendly"] = "Utility: Static HTTP file server"; | ||||
|     capa["desc"] = "Serves static files over HTTP from a set folder"; | ||||
|     capa["url_rel"] = "/static/"; | ||||
|     capa["url_prefix"] = "/static/"; | ||||
|     cfg->addOption("webroot", JSON::fromString("{\"arg\":\"string\", \"short\":\"w\",\"long\":\"webroot\",\"help\":\"Root directory for static files to serve.\"}")); | ||||
|     cfg->addOption("webroot", JSON::fromString("{\"arg\":\"string\", " | ||||
|                                                "\"short\":\"w\",\"long\":\"webroot\",\"help\":" | ||||
|                                                "\"Root directory for static files to serve.\"}")); | ||||
|     capa["required"]["webroot"]["name"] = "Web root directory"; | ||||
|     capa["required"]["webroot"]["help"] = "Main directory where files are served from."; | ||||
|     capa["required"]["webroot"]["type"] = "str"; | ||||
|     capa["required"]["webroot"]["option"] = "--webroot"; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   void OutHTTPMinimalServer::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|     //determine actual file path for the request
 | ||||
|     // determine actual file path for the request
 | ||||
|     std::string path = resolved_path + H.url.substr(8); | ||||
|      | ||||
|     //convert this to a real path with resolved symlinks etc
 | ||||
|     char * rp = realpath(path.c_str(), 0); | ||||
| 
 | ||||
|     // convert this to a real path with resolved symlinks etc
 | ||||
|     char *rp = realpath(path.c_str(), 0); | ||||
|     if (rp){ | ||||
|       path = rp; | ||||
|       free(rp); | ||||
|  | @ -48,7 +50,7 @@ namespace Mist { | |||
|       H.Clean(); | ||||
|       H.SetHeader("Server", "mistserver/" PACKAGE_VERSION); | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         return; | ||||
|       } | ||||
|  | @ -57,7 +59,6 @@ namespace Mist { | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     char buffer[4096]; | ||||
|     std::ifstream inFile(path.c_str()); | ||||
|     inFile.seekg(0, std::ios_base::end); | ||||
|  | @ -67,7 +68,7 @@ namespace Mist { | |||
|     H.SetHeader("Server", "mistserver/" PACKAGE_VERSION); | ||||
|     H.SetHeader("Content-Length", filesize); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); | ||||
|       return; | ||||
|  | @ -78,5 +79,4 @@ namespace Mist { | |||
|       myConn.SendNow(buffer, inFile.gcount()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,16 +1,16 @@ | |||
| #include "output_http.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutHTTPMinimalServer : public HTTPOutput { | ||||
|     public: | ||||
|       OutHTTPMinimalServer(Socket::Connection & conn); | ||||
|       ~OutHTTPMinimalServer(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|     private: | ||||
|       std::string resolved_path; | ||||
| namespace Mist{ | ||||
|   class OutHTTPMinimalServer : public HTTPOutput{ | ||||
|   public: | ||||
|     OutHTTPMinimalServer(Socket::Connection &conn); | ||||
|     ~OutHTTPMinimalServer(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
| 
 | ||||
|   private: | ||||
|     std::string resolved_path; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHTTPMinimalServer mistOut; | ||||
| 
 | ||||
|  |  | |||
|  | @ -41,15 +41,23 @@ namespace Mist{ | |||
|     capa["optional"]["wrappers"]["option"] = "--wrappers"; | ||||
|     capa["optional"]["wrappers"]["short"] = "w"; | ||||
|     cfg->addConnectorOptions(4433, capa); | ||||
|     cfg->addOption("nostreamtext", JSON::fromString("{\"arg\":\"string\", \"default\":\"\", \"short\":\"t\",\"long\":\"nostreamtext\",\"help\":\"Text or HTML to display when streams are unavailable.\"}")); | ||||
|     cfg->addOption("nostreamtext", | ||||
|                    JSON::fromString("{\"arg\":\"string\", \"default\":\"\", " | ||||
|                                     "\"short\":\"t\",\"long\":\"nostreamtext\",\"help\":\"Text or " | ||||
|                                     "HTML to display when streams are unavailable.\"}")); | ||||
|     capa["optional"]["nostreamtext"]["name"] = "Stream unavailable text"; | ||||
|     capa["optional"]["nostreamtext"]["help"] = "Text or HTML to display when streams are unavailable."; | ||||
|     capa["optional"]["nostreamtext"]["help"] = | ||||
|         "Text or HTML to display when streams are unavailable."; | ||||
|     capa["optional"]["nostreamtext"]["default"] = ""; | ||||
|     capa["optional"]["nostreamtext"]["type"] = "str"; | ||||
|     capa["optional"]["nostreamtext"]["option"] = "--nostreamtext"; | ||||
|     cfg->addOption("pubaddr", JSON::fromString("{\"arg\":\"string\", \"default\":\"\", \"short\":\"A\",\"long\":\"public-address\",\"help\":\"Full public address this output is available as.\"}")); | ||||
|     cfg->addOption("pubaddr", | ||||
|                    JSON::fromString("{\"arg\":\"string\", \"default\":\"\", " | ||||
|                                     "\"short\":\"A\",\"long\":\"public-address\",\"help\":\"Full " | ||||
|                                     "public address this output is available as.\"}")); | ||||
|     capa["optional"]["pubaddr"]["name"] = "Public address"; | ||||
|     capa["optional"]["pubaddr"]["help"] = "Full public address this output is available as, if being proxied"; | ||||
|     capa["optional"]["pubaddr"]["help"] = | ||||
|         "Full public address this output is available as, if being proxied"; | ||||
|     capa["optional"]["pubaddr"]["default"] = ""; | ||||
|     capa["optional"]["pubaddr"]["type"] = "inputlist"; | ||||
|     capa["optional"]["pubaddr"]["option"] = "--public-address"; | ||||
|  | @ -157,7 +165,7 @@ namespace Mist{ | |||
|           int todo = http_buf.get().size(); | ||||
|           int done = 0; | ||||
|           while (done < todo){ | ||||
|             ret = mbedtls_ssl_write(&ssl, (const unsigned char*)http_buf.get().data() + done, todo - done); | ||||
|             ret = mbedtls_ssl_write(&ssl, (const unsigned char *)http_buf.get().data() + done, todo - done); | ||||
|             if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT){ | ||||
|               HIGH_MSG("SSL disconnect!"); | ||||
|               http.close(); | ||||
|  | @ -172,9 +180,7 @@ namespace Mist{ | |||
|           http_buf.get().clear(); | ||||
|         } | ||||
|       } | ||||
|       if (!activity){ | ||||
|         Util::sleep(50); | ||||
|       } | ||||
|       if (!activity){Util::sleep(50);} | ||||
|     } | ||||
|     // close the HTTP process (close stdio, kill its PID)
 | ||||
|     http.close(); | ||||
|  | @ -187,7 +193,6 @@ namespace Mist{ | |||
|     return 0; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   OutHTTPS::~OutHTTPS(){ | ||||
|     HIGH_MSG("Ending SSL connection handler"); | ||||
|     // close when we're done
 | ||||
|  | @ -204,7 +209,7 @@ namespace Mist{ | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     //Declare and set up all required mbedtls structures
 | ||||
|     // Declare and set up all required mbedtls structures
 | ||||
|     int ret; | ||||
|     mbedtls_ssl_config_init(&sslConf); | ||||
|     mbedtls_entropy_init(&entropy); | ||||
|  | @ -213,14 +218,15 @@ namespace Mist{ | |||
|     mbedtls_ctr_drbg_init(&ctr_drbg); | ||||
| 
 | ||||
|     // seed the rng
 | ||||
|     if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)"MistServer", 10)) != 0){ | ||||
|     if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, | ||||
|                                      (const unsigned char *)"MistServer", 10)) != 0){ | ||||
|       FAIL_MSG("Could not seed the random number generator!"); | ||||
|     } | ||||
| 
 | ||||
|     //Read certificate chain(s) from cmdline option(s)
 | ||||
|     // Read certificate chain(s) from cmdline option(s)
 | ||||
|     JSON::Value certs = config->getOption("cert", true); | ||||
|     jsonForEach(certs, it){ | ||||
|       if (it->asStringRef().size()){//Ignore empty entries (default is empty)
 | ||||
|       if (it->asStringRef().size()){// Ignore empty entries (default is empty)
 | ||||
|         ret = mbedtls_x509_crt_parse_file(&srvcert, it->asStringRef().c_str()); | ||||
|         if (ret != 0){ | ||||
|           WARN_MSG("Could not load any certificates from file: %s", it->asStringRef().c_str()); | ||||
|  | @ -228,14 +234,15 @@ namespace Mist{ | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     //Read key from cmdline option
 | ||||
|     // Read key from cmdline option
 | ||||
|     ret = mbedtls_pk_parse_keyfile(&pkey, config->getString("key").c_str(), 0); | ||||
|     if (ret != 0){ | ||||
|       FAIL_MSG("Could not load any keys from file: %s", config->getString("key").c_str()); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if ((ret = mbedtls_ssl_config_defaults(&sslConf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0){ | ||||
|     if ((ret = mbedtls_ssl_config_defaults(&sslConf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, | ||||
|                                            MBEDTLS_SSL_PRESET_DEFAULT)) != 0){ | ||||
|       FAIL_MSG("SSL config defaults failed"); | ||||
|       return; | ||||
|     } | ||||
|  | @ -248,12 +255,11 @@ namespace Mist{ | |||
| 
 | ||||
|     Output::listener(conf, callback); | ||||
| 
 | ||||
|     //Free all the mbedtls structures
 | ||||
|     // Free all the mbedtls structures
 | ||||
|     mbedtls_x509_crt_free(&srvcert); | ||||
|     mbedtls_pk_free(&pkey); | ||||
|     mbedtls_ssl_config_free(&sslConf); | ||||
|     mbedtls_ctr_drbg_free(&ctr_drbg); | ||||
|     mbedtls_entropy_free(&entropy); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| #pragma once | ||||
| #include <mist/defines.h> | ||||
| #include "output.h" | ||||
| #include <mbedtls/certs.h> | ||||
| #include <mbedtls/ctr_drbg.h> | ||||
|  | @ -8,27 +7,29 @@ | |||
| #include <mbedtls/ssl.h> | ||||
| #include <mbedtls/timing.h> | ||||
| #include <mbedtls/x509.h> | ||||
| #include <mist/defines.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
| 
 | ||||
|   class OutHTTPS : public Output { | ||||
|     public: | ||||
|       OutHTTPS(Socket::Connection & C); | ||||
|       virtual ~OutHTTPS(); | ||||
|       void onRequest(){}; | ||||
|       int run(); | ||||
|       static bool listenMode(){return true;} | ||||
|       static void init(Util::Config * cfg); | ||||
|       static void listener(Util::Config & conf, int (*callback)(Socket::Connection & S)); | ||||
|     private: | ||||
|       mbedtls_net_context client_fd; | ||||
|       mbedtls_ssl_context ssl; | ||||
|       static mbedtls_entropy_context entropy; | ||||
|       static mbedtls_ctr_drbg_context ctr_drbg; | ||||
|       static mbedtls_ssl_config sslConf; | ||||
|       static mbedtls_x509_crt srvcert; | ||||
|       static mbedtls_pk_context pkey; | ||||
|   class OutHTTPS : public Output{ | ||||
|   public: | ||||
|     OutHTTPS(Socket::Connection &C); | ||||
|     virtual ~OutHTTPS(); | ||||
|     void onRequest(){}; | ||||
|     int run(); | ||||
|     static bool listenMode(){return true;} | ||||
|     static void init(Util::Config *cfg); | ||||
|     static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S)); | ||||
| 
 | ||||
|   private: | ||||
|     mbedtls_net_context client_fd; | ||||
|     mbedtls_ssl_context ssl; | ||||
|     static mbedtls_entropy_context entropy; | ||||
|     static mbedtls_ctr_drbg_context ctr_drbg; | ||||
|     static mbedtls_ssl_config sslConf; | ||||
|     static mbedtls_x509_crt srvcert; | ||||
|     static mbedtls_pk_context pkey; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHTTPS mistOut; | ||||
|  |  | |||
|  | @ -1,56 +1,56 @@ | |||
| #include "output_httpts.h" | ||||
| #include <mist/defines.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/stream.h> | ||||
| #include <unistd.h> | ||||
| #include <mist/procs.h> | ||||
| #include <mist/stream.h> | ||||
| #include <mist/url.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   OutHTTPTS::OutHTTPTS(Socket::Connection & conn) : TSOutput(conn){ | ||||
|     sendRepeatingHeaders = 500;//PAT/PMT every 500ms (DVB spec)
 | ||||
|      | ||||
|   OutHTTPTS::OutHTTPTS(Socket::Connection &conn) : TSOutput(conn){ | ||||
|     sendRepeatingHeaders = 500; // PAT/PMT every 500ms (DVB spec)
 | ||||
| 
 | ||||
|     if (config->getString("target").substr(0, 6) == "srt://"){ | ||||
|       std::string tgt = config->getString("target"); | ||||
|       HTTP::URL srtUrl(tgt); | ||||
|       config->getOption("target", true).append("ts-exec:srt-live-transmit file://con "+srtUrl.getUrl()); | ||||
|       config->getOption("target", true).append("ts-exec:srt-live-transmit file://con " + srtUrl.getUrl()); | ||||
|       INFO_MSG("Rewriting SRT target '%s' to '%s'", tgt.c_str(), config->getString("target").c_str()); | ||||
|     } | ||||
|     if(config->getString("target").substr(0,8) == "ts-exec:"){ | ||||
|         std::string input = config->getString("target").substr(8); | ||||
|         char *args[128]; | ||||
|         uint8_t argCnt = 0; | ||||
|         char *startCh = 0; | ||||
|         for (char *i = (char *)input.c_str(); i <= input.data() + input.size(); ++i){ | ||||
|           if (!*i){ | ||||
|             if (startCh){args[argCnt++] = startCh;} | ||||
|             break; | ||||
|           } | ||||
|           if (*i == ' '){ | ||||
|             if (startCh){ | ||||
|               args[argCnt++] = startCh; | ||||
|               startCh = 0; | ||||
|               *i = 0; | ||||
|             } | ||||
|           }else{ | ||||
|             if (!startCh){startCh = i;} | ||||
|           } | ||||
|     if (config->getString("target").substr(0, 8) == "ts-exec:"){ | ||||
|       std::string input = config->getString("target").substr(8); | ||||
|       char *args[128]; | ||||
|       uint8_t argCnt = 0; | ||||
|       char *startCh = 0; | ||||
|       for (char *i = (char *)input.c_str(); i <= input.data() + input.size(); ++i){ | ||||
|         if (!*i){ | ||||
|           if (startCh){args[argCnt++] = startCh;} | ||||
|           break; | ||||
|         } | ||||
|         if (*i == ' '){ | ||||
|           if (startCh){ | ||||
|             args[argCnt++] = startCh; | ||||
|             startCh = 0; | ||||
|             *i = 0; | ||||
|           } | ||||
|         }else{ | ||||
|           if (!startCh){startCh = i;} | ||||
|         } | ||||
|         args[argCnt] = 0; | ||||
| 
 | ||||
|         int fin = -1; | ||||
|         Util::Procs::StartPiped(args, &fin, 0, 0); | ||||
|         myConn.open(fin, -1); | ||||
| 
 | ||||
|         wantRequest = false; | ||||
|         parseData = true; | ||||
|       } | ||||
|       args[argCnt] = 0; | ||||
| 
 | ||||
|       int fin = -1; | ||||
|       Util::Procs::StartPiped(args, &fin, 0, 0); | ||||
|       myConn.open(fin, -1); | ||||
| 
 | ||||
|       wantRequest = false; | ||||
|       parseData = true; | ||||
|     } | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   OutHTTPTS::~OutHTTPTS(){} | ||||
| 
 | ||||
|   void OutHTTPTS::initialSeek(){ | ||||
|     //Adds passthrough support to the regular initialSeek function
 | ||||
|     // Adds passthrough support to the regular initialSeek function
 | ||||
|     if (targetParams.count("passthrough")){ | ||||
|       selectedTracks.clear(); | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|  | @ -61,7 +61,7 @@ namespace Mist{ | |||
|     Output::initialSeek(); | ||||
|   } | ||||
| 
 | ||||
|   void OutHTTPTS::init(Util::Config * cfg){ | ||||
|   void OutHTTPTS::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "HTTPTS"; | ||||
|     capa["friendly"] = "TS over HTTP"; | ||||
|  | @ -81,17 +81,20 @@ namespace Mist{ | |||
|     capa["methods"][0u]["priority"] = 1; | ||||
|     capa["push_urls"].append("/*.ts"); | ||||
|     capa["push_urls"].append("ts-exec:*"); | ||||
|     | ||||
| 
 | ||||
|     { | ||||
|       int fin = 0, fout = 0, ferr = 0; | ||||
|       pid_t srt_tx = -1; | ||||
|       const char *args[] = {"srt-live-transmit", 0}; | ||||
|       const char *args[] ={"srt-live-transmit", 0}; | ||||
|       srt_tx = Util::Procs::StartPiped(args, 0, 0, 0); | ||||
|       if (srt_tx > 1){ | ||||
|         capa["push_urls"].append("srt://*"); | ||||
|         capa["desc"] = capa["desc"].asStringRef() + ". SRT push output support (srt://*) is installed and available."; | ||||
|         capa["desc"] = capa["desc"].asStringRef() + | ||||
|                        ". SRT push output support (srt://*) is installed and available."; | ||||
|       }else{ | ||||
|         capa["desc"] = capa["desc"].asStringRef() + ". To enable SRT push output support, please install the srt-live-transmit binary."; | ||||
|         capa["desc"] = | ||||
|             capa["desc"].asStringRef() + | ||||
|             ". To enable SRT push output support, please install the srt-live-transmit binary."; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -103,10 +106,8 @@ namespace Mist{ | |||
|     cfg->addOption("target", opt); | ||||
|   } | ||||
| 
 | ||||
|   bool OutHTTPTS::isRecording(){ | ||||
|     return config->getString("target").size(); | ||||
|   } | ||||
|    | ||||
|   bool OutHTTPTS::isRecording(){return config->getString("target").size();} | ||||
| 
 | ||||
|   void OutHTTPTS::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|     initialize(); | ||||
|  | @ -118,23 +119,22 @@ namespace Mist{ | |||
|     H.clearHeader("transferMode.dlna.org"); | ||||
|     H.SetHeader("Content-Type", "video/mpeg"); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); | ||||
|       return; | ||||
|     } | ||||
|     H.protocol = "HTTP/1.0";//Force HTTP/1.0 because some devices just don't understand chunked replies
 | ||||
|     H.protocol = "HTTP/1.0"; // Force HTTP/1.0 because some devices just don't understand chunked replies
 | ||||
|     H.StartResponse(H, myConn); | ||||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|   } | ||||
| 
 | ||||
|   void OutHTTPTS::sendTS(const char * tsData, unsigned int len){ | ||||
|   void OutHTTPTS::sendTS(const char *tsData, unsigned int len){ | ||||
|     if (!isRecording()){ | ||||
|       H.Chunkify(tsData, len, myConn); | ||||
|     }else{ | ||||
|       myConn.SendNow(tsData, len); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,19 +1,22 @@ | |||
| #include "output_ts_base.h" | ||||
| #include "output_http.h" | ||||
| #include "output_ts_base.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
|   class OutHTTPTS : public TSOutput{ | ||||
|     public: | ||||
|       OutHTTPTS(Socket::Connection & conn); | ||||
|       ~OutHTTPTS(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendTS(const char * tsData, unsigned int len=188); | ||||
|       void initialSeek(); | ||||
|     private: | ||||
|       bool isRecording(); | ||||
|       bool isFileTarget(){return isRecording() && config->getString("target").substr(0,8) != "ts-exec:";} | ||||
|   public: | ||||
|     OutHTTPTS(Socket::Connection &conn); | ||||
|     ~OutHTTPTS(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendTS(const char *tsData, unsigned int len = 188); | ||||
|     void initialSeek(); | ||||
| 
 | ||||
|   private: | ||||
|     bool isRecording(); | ||||
|     bool isFileTarget(){ | ||||
|       return isRecording() && config->getString("target").substr(0, 8) != "ts-exec:"; | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutHTTPTS mistOut; | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| #include "output_json.h" | ||||
| #include <mist/stream.h> | ||||
| #include <iomanip> | ||||
| #include <mist/stream.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutJSON::OutJSON(Socket::Connection & conn) : HTTPOutput(conn){ | ||||
| namespace Mist{ | ||||
|   OutJSON::OutJSON(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     realTime = 0; | ||||
|     bootMsOffset = 0; | ||||
|     keepReselecting = false; | ||||
|  | @ -11,7 +11,7 @@ namespace Mist { | |||
|     noReceive = false; | ||||
|   } | ||||
| 
 | ||||
|   void OutJSON::init(Util::Config * cfg){ | ||||
|   void OutJSON::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "JSON"; | ||||
|     capa["friendly"] = "JSON over HTTP"; | ||||
|  | @ -30,14 +30,14 @@ namespace Mist { | |||
| 
 | ||||
|   void OutJSON::sendNext(){ | ||||
|     if (keepReselecting){ | ||||
|       //If we can select more tracks, do it and continue.
 | ||||
|       // If we can select more tracks, do it and continue.
 | ||||
|       if (selectDefaultTracks()){ | ||||
|         return;//After a seek, the current packet is invalid. Do nothing and return here.
 | ||||
|         return; // After a seek, the current packet is invalid. Do nothing and return here.
 | ||||
|       } | ||||
|     } | ||||
|     JSON::Value jPack; | ||||
|     if (myMeta.tracks[thisPacket.getTrackId()].codec == "JSON"){ | ||||
|       char * dPtr; | ||||
|       char *dPtr; | ||||
|       size_t dLen; | ||||
|       thisPacket.getString("data", dPtr, dLen); | ||||
|       jPack["data"] = JSON::fromString(dPtr, dLen); | ||||
|  | @ -48,7 +48,7 @@ namespace Mist { | |||
|     } | ||||
|     if (dupcheck){ | ||||
|       if (jPack.compareExcept(lastVal, nodup)){ | ||||
|         return;//skip duplicates
 | ||||
|         return; // skip duplicates
 | ||||
|       } | ||||
|       lastVal = jPack; | ||||
|     } | ||||
|  | @ -57,7 +57,7 @@ namespace Mist { | |||
|       return; | ||||
|     } | ||||
|     if (!jsonp.size()){ | ||||
|       if(!first) { | ||||
|       if (!first){ | ||||
|         myConn.SendNow(", ", 2); | ||||
|       }else{ | ||||
|         myConn.SendNow("[", 1); | ||||
|  | @ -67,9 +67,7 @@ namespace Mist { | |||
|       myConn.SendNow(jsonp + "("); | ||||
|     } | ||||
|     myConn.SendNow(jPack.toString()); | ||||
|     if (jsonp.size()){ | ||||
|       myConn.SendNow(");\n", 3); | ||||
|     } | ||||
|     if (jsonp.size()){myConn.SendNow(");\n", 3);} | ||||
|   } | ||||
| 
 | ||||
|   void OutJSON::sendHeader(){ | ||||
|  | @ -81,9 +79,9 @@ namespace Mist { | |||
|     H.SendResponse("200", "OK", myConn); | ||||
|     sentHeader = true; | ||||
|   } | ||||
|    | ||||
|   void OutJSON::onFail(const std::string & msg, bool critical){ | ||||
|     //Only run failure handle if we're not being persistent
 | ||||
| 
 | ||||
|   void OutJSON::onFail(const std::string &msg, bool critical){ | ||||
|     // Only run failure handle if we're not being persistent
 | ||||
|     if (!keepReselecting){ | ||||
|       HTTPOutput::onFail(msg, critical); | ||||
|     }else{ | ||||
|  | @ -118,9 +116,7 @@ namespace Mist { | |||
|       } | ||||
|       recursive = false; | ||||
|     } | ||||
|     if (!webSock && !jsonp.size() && !first){ | ||||
|       myConn.SendNow("]\n", 2); | ||||
|     } | ||||
|     if (!webSock && !jsonp.size() && !first){myConn.SendNow("]\n", 2);} | ||||
|     myConn.close(); | ||||
|     return false; | ||||
|   } | ||||
|  | @ -133,18 +129,18 @@ namespace Mist { | |||
|   void OutJSON::preWebsocketConnect(){ | ||||
|     if (H.GetVar("password") != ""){pushPass = H.GetVar("password");} | ||||
|     if (H.GetVar("password").size() || H.GetVar("push").size()){noReceive = true;} | ||||
|    | ||||
| 
 | ||||
|     if (H.GetVar("persist") != ""){keepReselecting = true;} | ||||
|     if (H.GetVar("dedupe") != ""){ | ||||
|       dupcheck = true; | ||||
|       size_t index; | ||||
|       std::string dupes = H.GetVar("dedupe"); | ||||
|       while (dupes != "") { | ||||
|       while (dupes != ""){ | ||||
|         index = dupes.find(','); | ||||
|         nodup.insert(dupes.substr(0, index)); | ||||
|         if (index != std::string::npos) { | ||||
|         if (index != std::string::npos){ | ||||
|           dupes.erase(0, index + 1); | ||||
|         } else { | ||||
|         }else{ | ||||
|           dupes = ""; | ||||
|         } | ||||
|       } | ||||
|  | @ -165,29 +161,27 @@ namespace Mist { | |||
|         bootMsOffset = Util::bootMS(); | ||||
|       } | ||||
|     } | ||||
|     //We now know we're allowed to push. Read a JSON object.
 | ||||
|     // We now know we're allowed to push. Read a JSON object.
 | ||||
|     JSON::Value inJSON = JSON::fromString(webSock->data, webSock->data.size()); | ||||
|     if (!inJSON || !inJSON.isObject()){ | ||||
|       //Ignore empty and/or non-parsable JSON packets
 | ||||
|       // Ignore empty and/or non-parsable JSON packets
 | ||||
|       MEDIUM_MSG("Ignoring non-JSON object: %s", webSock->data); | ||||
|       return; | ||||
|     } | ||||
|     //Let's create a new track for pushing purposes, if needed
 | ||||
|     // Let's create a new track for pushing purposes, if needed
 | ||||
|     if (!pushTrack){ | ||||
|       pushTrack = 1; | ||||
|       while (myMeta.tracks.count(pushTrack)){ | ||||
|         ++pushTrack; | ||||
|       } | ||||
|       while (myMeta.tracks.count(pushTrack)){++pushTrack;} | ||||
|     } | ||||
|     myMeta.tracks[pushTrack].type = "meta"; | ||||
|     myMeta.tracks[pushTrack].codec = "JSON"; | ||||
|     //We have a track set correctly. Let's attempt to buffer a frame.
 | ||||
|     // We have a track set correctly. Let's attempt to buffer a frame.
 | ||||
|     lastSendTime = Util::bootMS(); | ||||
|     if (!inJSON.isMember("unix")){ | ||||
|       //Base timestamp on arrival time
 | ||||
|       // Base timestamp on arrival time
 | ||||
|       lastOutTime = (lastSendTime - bootMsOffset); | ||||
|     }else{ | ||||
|       //Base timestamp on unix time
 | ||||
|       // Base timestamp on unix time
 | ||||
|       lastOutTime = (lastSendTime - bootMsOffset) + (inJSON["unix"].asInt() - Util::epoch()) * 1000; | ||||
|     } | ||||
|     lastOutData = inJSON.toString(); | ||||
|  | @ -202,9 +196,7 @@ namespace Mist { | |||
|   void OutJSON::onIdle(){ | ||||
|     if (nProxy.trackState[pushTrack] != FILL_ACC){ | ||||
|       continueNegotiate(pushTrack); | ||||
|       if (nProxy.trackState[pushTrack] == FILL_ACC){ | ||||
|         idleInterval = 5000; | ||||
|       } | ||||
|       if (nProxy.trackState[pushTrack] == FILL_ACC){idleInterval = 5000;} | ||||
|       return; | ||||
|     } | ||||
|     lastOutTime += (Util::bootMS() - lastSendTime); | ||||
|  | @ -216,14 +208,14 @@ namespace Mist { | |||
| 
 | ||||
|   void OutJSON::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|     preWebsocketConnect();//Not actually a websocket, but we need to do the same checks
 | ||||
|     preWebsocketConnect(); // Not actually a websocket, but we need to do the same checks
 | ||||
|     jsonp = ""; | ||||
|     if (H.GetVar("callback") != ""){jsonp = H.GetVar("callback");} | ||||
|     if (H.GetVar("jsonp") != ""){jsonp = H.GetVar("jsonp");} | ||||
|      | ||||
| 
 | ||||
|     H.Clean(); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.SetHeader("Content-Type", "text/javascript"); | ||||
|       H.protocol = "HTTP/1.0"; | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|  | @ -235,5 +227,4 @@ namespace Mist { | |||
|     wantRequest = false; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,36 +1,37 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/websocket.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutJSON : public HTTPOutput { | ||||
|     public: | ||||
|       OutJSON(Socket::Connection & conn); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void onIdle(); | ||||
|       virtual void onWebsocketFrame(); | ||||
|       virtual void onWebsocketConnect(); | ||||
|       virtual void preWebsocketConnect(); | ||||
|       bool onFinish(); | ||||
|       void onFail(const std::string & msg, bool critical = false); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|       bool doesWebsockets(){return true;} | ||||
|     protected: | ||||
|       JSON::Value lastVal; | ||||
|       std::string lastOutData; | ||||
|       uint64_t lastOutTime; | ||||
|       uint64_t lastSendTime; | ||||
|       bool keepReselecting; | ||||
|       std::string jsonp; | ||||
|       std::string pushPass; | ||||
|       uint64_t pushTrack; | ||||
|       int64_t bootMsOffset; | ||||
|       bool dupcheck; | ||||
|       std::set<std::string> nodup; | ||||
|       bool first; | ||||
|       bool noReceive; | ||||
| namespace Mist{ | ||||
|   class OutJSON : public HTTPOutput{ | ||||
|   public: | ||||
|     OutJSON(Socket::Connection &conn); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void onIdle(); | ||||
|     virtual void onWebsocketFrame(); | ||||
|     virtual void onWebsocketConnect(); | ||||
|     virtual void preWebsocketConnect(); | ||||
|     bool onFinish(); | ||||
|     void onFail(const std::string &msg, bool critical = false); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
|     bool doesWebsockets(){return true;} | ||||
| 
 | ||||
|   protected: | ||||
|     JSON::Value lastVal; | ||||
|     std::string lastOutData; | ||||
|     uint64_t lastOutTime; | ||||
|     uint64_t lastSendTime; | ||||
|     bool keepReselecting; | ||||
|     std::string jsonp; | ||||
|     std::string pushPass; | ||||
|     uint64_t pushTrack; | ||||
|     int64_t bootMsOffset; | ||||
|     bool dupcheck; | ||||
|     std::set<std::string> nodup; | ||||
|     bool first; | ||||
|     bool noReceive; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutJSON mistOut; | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| #include "output_progressive_flv.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutProgressiveFLV::OutProgressiveFLV(Socket::Connection & conn) : HTTPOutput(conn){} | ||||
|    | ||||
|   void OutProgressiveFLV::init(Util::Config * cfg){ | ||||
| namespace Mist{ | ||||
|   OutProgressiveFLV::OutProgressiveFLV(Socket::Connection &conn) : HTTPOutput(conn){} | ||||
| 
 | ||||
|   void OutProgressiveFLV::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "FLV"; | ||||
|     capa["friendly"] = "Flash progressive over HTTP (FLV)"; | ||||
|  | @ -30,7 +30,7 @@ namespace Mist { | |||
|     capa["methods"][0u]["priority"] = 5; | ||||
|     capa["methods"][0u]["player_url"] = "/oldflashplayer.swf"; | ||||
|     capa["push_urls"].append("/*.flv"); | ||||
|      | ||||
| 
 | ||||
|     JSON::Value opt; | ||||
|     opt["arg"] = "string"; | ||||
|     opt["default"] = ""; | ||||
|  | @ -38,7 +38,6 @@ namespace Mist { | |||
|     opt["help"] = "Target filename to store FLV file as, or - for stdout."; | ||||
|     cfg->addOption("target", opt); | ||||
| 
 | ||||
| 
 | ||||
|     opt.null(); | ||||
|     opt["short"] = "k"; | ||||
|     opt["long"] = "keyframe"; | ||||
|  | @ -46,12 +45,10 @@ namespace Mist { | |||
|     cfg->addOption("keyframeonly", opt); | ||||
|   } | ||||
| 
 | ||||
|   bool OutProgressiveFLV::isRecording(){ | ||||
|     return config->getString("target").size(); | ||||
|   } | ||||
|    | ||||
|   bool OutProgressiveFLV::isRecording(){return config->getString("target").size();} | ||||
| 
 | ||||
|   void OutProgressiveFLV::sendNext(){ | ||||
|     //If there are now more selectable tracks, select the new track and do a seek to the current timestamp
 | ||||
|     // If there are now more selectable tracks, select the new track and do a seek to the current timestamp
 | ||||
|     if (myMeta.live && selectedTracks.size() < 2){ | ||||
|       static unsigned long long lastMeta = 0; | ||||
|       if (Util::epoch() > lastMeta + 5){ | ||||
|  | @ -60,7 +57,8 @@ namespace Mist { | |||
|         if (myMeta.tracks.size() > 1){ | ||||
|           if (selectDefaultTracks()){ | ||||
|             INFO_MSG("Track selection changed - resending headers and continuing"); | ||||
|             for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|             for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); | ||||
|                  it != selectedTracks.end(); it++){ | ||||
|               if (myMeta.tracks[*it].type == "video" && tag.DTSCVideoInit(myMeta.tracks[*it])){ | ||||
|                 myConn.SendNow(tag.data, tag.len); | ||||
|               } | ||||
|  | @ -74,21 +72,19 @@ namespace Mist { | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     DTSC::Track & trk = myMeta.tracks[thisPacket.getTrackId()]; | ||||
|     DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()]; | ||||
|     tag.DTSCLoader(thisPacket, trk); | ||||
|     if (trk.codec == "PCM" && trk.size == 16){ | ||||
|       char * ptr = tag.getData(); | ||||
|       char *ptr = tag.getData(); | ||||
|       uint32_t ptrSize = tag.getDataLen(); | ||||
|       for (uint32_t i = 0; i < ptrSize; i+=2){ | ||||
|       for (uint32_t i = 0; i < ptrSize; i += 2){ | ||||
|         char tmpchar = ptr[i]; | ||||
|         ptr[i] = ptr[i+1]; | ||||
|         ptr[i+1] = tmpchar; | ||||
|         ptr[i] = ptr[i + 1]; | ||||
|         ptr[i + 1] = tmpchar; | ||||
|       } | ||||
|     } | ||||
|     myConn.SendNow(tag.data, tag.len);  | ||||
|     if (config->getBool("keyframeonly")){ | ||||
|       config->is_active = false; | ||||
|     } | ||||
|     myConn.SendNow(tag.data, tag.len); | ||||
|     if (config->getBool("keyframeonly")){config->is_active = false;} | ||||
|   } | ||||
| 
 | ||||
|   void OutProgressiveFLV::sendHeader(){ | ||||
|  | @ -101,8 +97,9 @@ namespace Mist { | |||
|     } | ||||
|     if (config->getBool("keyframeonly")){ | ||||
|       selectedTracks.clear(); | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ | ||||
|         if (it->second.type =="video"){ | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|            it != myMeta.tracks.end(); it++){ | ||||
|         if (it->second.type == "video"){ | ||||
|           selectedTracks.insert(it->first); | ||||
|           break; | ||||
|         } | ||||
|  | @ -132,18 +129,18 @@ namespace Mist { | |||
| 
 | ||||
|   void OutProgressiveFLV::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|      | ||||
| 
 | ||||
|     H.Clean(); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.SetHeader("Content-Type", "video/x-flv"); | ||||
|       H.protocol = "HTTP/1.0"; | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|   } | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,20 +1,20 @@ | |||
| #include "output_http.h" | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   class OutProgressiveFLV : public HTTPOutput{ | ||||
|   public: | ||||
|     OutProgressiveFLV(Socket::Connection &conn); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutProgressiveFLV : public HTTPOutput { | ||||
|     public: | ||||
|       OutProgressiveFLV(Socket::Connection & conn); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|     private: | ||||
|       virtual bool inlineRestartCapable() const{return true;} | ||||
|       FLV::Tag tag; | ||||
|       bool isRecording(); | ||||
|       bool isFileTarget(){return isRecording();} | ||||
|   private: | ||||
|     virtual bool inlineRestartCapable() const{return true;} | ||||
|     FLV::Tag tag; | ||||
|     bool isRecording(); | ||||
|     bool isFileTarget(){return isRecording();} | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutProgressiveFLV mistOut; | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| #include "output_progressive_mp3.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutProgressiveMP3::OutProgressiveMP3(Socket::Connection & conn) : HTTPOutput(conn){} | ||||
| namespace Mist{ | ||||
|   OutProgressiveMP3::OutProgressiveMP3(Socket::Connection &conn) : HTTPOutput(conn){} | ||||
| 
 | ||||
|   void OutProgressiveMP3::init(Util::Config * cfg){ | ||||
|   void OutProgressiveMP3::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "MP3"; | ||||
|     capa["friendly"] = "MP3 over HTTP"; | ||||
|  | @ -22,13 +22,11 @@ namespace Mist { | |||
|     opt["help"] = "Target filename to store MP3 file as, or - for stdout."; | ||||
|     cfg->addOption("target", opt); | ||||
|   } | ||||
|    | ||||
|   bool OutProgressiveMP3::isRecording(){ | ||||
|     return config->getString("target").size(); | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   bool OutProgressiveMP3::isRecording(){return config->getString("target").size();} | ||||
| 
 | ||||
|   void OutProgressiveMP3::sendNext(){ | ||||
|     char * dataPointer = 0; | ||||
|     char *dataPointer = 0; | ||||
|     size_t len = 0; | ||||
|     thisPacket.getString("data", dataPointer, len); | ||||
|     myConn.SendNow(dataPointer, len); | ||||
|  | @ -41,7 +39,7 @@ namespace Mist { | |||
|       H.SetHeader("Content-Type", "audio/mpeg"); | ||||
|       H.protocol = "HTTP/1.0"; | ||||
|       H.setCORSHeaders(); | ||||
|       if(method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (method == "OPTIONS" || method == "HEAD"){ | ||||
|         H.SendResponse("200", "OK", myConn); | ||||
|         return; | ||||
|       } | ||||
|  | @ -52,19 +50,19 @@ namespace Mist { | |||
| 
 | ||||
|   void OutProgressiveMP3::onHTTP(){ | ||||
|     std::string method = H.method; | ||||
|      | ||||
| 
 | ||||
|     H.Clean(); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       H.SetHeader("Content-Type", "audio/mpeg"); | ||||
|       H.protocol = "HTTP/1.0"; | ||||
|       H.SendResponse("200", "OK", myConn); | ||||
|       H.Clean(); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,18 +1,18 @@ | |||
| #include "output_http.h" | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   class OutProgressiveMP3 : public HTTPOutput{ | ||||
|   public: | ||||
|     OutProgressiveMP3(Socket::Connection &conn); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutProgressiveMP3 : public HTTPOutput { | ||||
|     public: | ||||
|       OutProgressiveMP3(Socket::Connection & conn); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|     private: | ||||
|       bool isRecording(); | ||||
|       bool isFileTarget(){return isRecording();} | ||||
|   private: | ||||
|     bool isRecording(); | ||||
|     bool isFileTarget(){return isRecording();} | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutProgressiveMP3 mistOut; | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,80 +1,75 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/http_parser.h> | ||||
| #include <list> | ||||
| #include <mist/http_parser.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
|   struct keyPart{ | ||||
|     public: | ||||
|       bool operator < (const keyPart& rhs) const { | ||||
|         if (time < rhs.time){ | ||||
|           return true; | ||||
|         } | ||||
|         if (time > rhs.time){ | ||||
|           return false; | ||||
|         } | ||||
|         if (trackID < rhs.trackID){ | ||||
|           return true; | ||||
|         } | ||||
|         return (trackID == rhs.trackID && index < rhs.index); | ||||
|       } | ||||
|       size_t trackID; | ||||
|       uint64_t time; | ||||
|       uint64_t byteOffset;//Stores relative bpos for fragmented MP4
 | ||||
|       uint64_t index; | ||||
|       uint32_t size; | ||||
|   public: | ||||
|     bool operator<(const keyPart &rhs) const{ | ||||
|       if (time < rhs.time){return true;} | ||||
|       if (time > rhs.time){return false;} | ||||
|       if (trackID < rhs.trackID){return true;} | ||||
|       return (trackID == rhs.trackID && index < rhs.index); | ||||
|     } | ||||
|     size_t trackID; | ||||
|     uint64_t time; | ||||
|     uint64_t byteOffset; // Stores relative bpos for fragmented MP4
 | ||||
|     uint64_t index; | ||||
|     uint32_t size; | ||||
|   }; | ||||
|    | ||||
| 
 | ||||
|   struct fragSet{ | ||||
|     uint64_t firstPart; | ||||
|     uint64_t lastPart; | ||||
|     uint64_t firstTime; | ||||
|     uint64_t lastTime; | ||||
|   }; | ||||
|   class OutProgressiveMP4 : public HTTPOutput { | ||||
|     public: | ||||
|       OutProgressiveMP4(Socket::Connection & conn); | ||||
|       ~OutProgressiveMP4(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       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
 | ||||
|       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 findSeekPoint(uint64_t byteStart, uint64_t & seekPoint, uint64_t headerSize); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|       bool doesWebsockets(){return true;} | ||||
|       void onIdle(); | ||||
|       bool onFinish(); | ||||
|       virtual void onWebsocketFrame(); | ||||
|       virtual void onWebsocketConnect(); | ||||
|     protected: | ||||
|       Util::ResizeablePointer webBuf; | ||||
|       uint64_t fileSize; | ||||
|       uint64_t byteStart; | ||||
|       uint64_t byteEnd; | ||||
|       int64_t leftOver; | ||||
|       uint64_t currPos; | ||||
|       uint64_t seekPoint; | ||||
|        | ||||
|       //variables for standard MP4
 | ||||
|       std::set <keyPart> sortSet;//needed for unfragmented MP4, remembers the order of keyparts
 | ||||
|   class OutProgressiveMP4 : public HTTPOutput{ | ||||
|   public: | ||||
|     OutProgressiveMP4(Socket::Connection &conn); | ||||
|     ~OutProgressiveMP4(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     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
 | ||||
|     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 findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
|     bool doesWebsockets(){return true;} | ||||
|     void onIdle(); | ||||
|     bool onFinish(); | ||||
|     virtual void onWebsocketFrame(); | ||||
|     virtual void onWebsocketConnect(); | ||||
| 
 | ||||
|       //variables for fragmented
 | ||||
|       size_t fragSeqNum;//the sequence number of the next keyframe/fragment when producing fragmented MP4's
 | ||||
|       size_t vidTrack;//the video track we use as fragmenting base
 | ||||
|       uint64_t realBaseOffset;//base offset for every moof packet
 | ||||
|       //from sendnext
 | ||||
|        | ||||
|       bool sending3GP; | ||||
|       bool chromeWorkaround; | ||||
|       int keysOnly; | ||||
|       uint64_t estimateFileSize(); | ||||
|   protected: | ||||
|     Util::ResizeablePointer webBuf; | ||||
|     uint64_t fileSize; | ||||
|     uint64_t byteStart; | ||||
|     uint64_t byteEnd; | ||||
|     int64_t leftOver; | ||||
|     uint64_t currPos; | ||||
|     uint64_t seekPoint; | ||||
| 
 | ||||
|       //This is a dirty solution... but it prevents copying and copying and copying again
 | ||||
|       std::map<size_t, fragSet> currentPartSet; | ||||
|     // variables for standard MP4
 | ||||
|     std::set<keyPart> sortSet; // needed for unfragmented MP4, remembers the order of keyparts
 | ||||
| 
 | ||||
|     // variables for fragmented
 | ||||
|     size_t fragSeqNum; // the sequence number of the next keyframe/fragment when producing fragmented MP4's
 | ||||
|     size_t vidTrack;         // the video track we use as fragmenting base
 | ||||
|     uint64_t realBaseOffset; // base offset for every moof packet
 | ||||
|     // from sendnext
 | ||||
| 
 | ||||
|     bool sending3GP; | ||||
|     bool chromeWorkaround; | ||||
|     int keysOnly; | ||||
|     uint64_t estimateFileSize(); | ||||
| 
 | ||||
|     // This is a dirty solution... but it prevents copying and copying and copying again
 | ||||
|     std::map<size_t, fragSet> currentPartSet; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutProgressiveMP4 mistOut; | ||||
|  |  | |||
|  | @ -1,16 +1,16 @@ | |||
| #include "output_progressive_ogg.h" | ||||
| #include <algorithm> | ||||
| #include <mist/bitstream.h> | ||||
| #include <mist/defines.h> | ||||
| #include <algorithm> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutProgressiveOGG::OutProgressiveOGG(Socket::Connection & conn) : HTTPOutput(conn){ | ||||
| namespace Mist{ | ||||
|   OutProgressiveOGG::OutProgressiveOGG(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     realTime = 0; | ||||
|   } | ||||
| 
 | ||||
|   OutProgressiveOGG::~OutProgressiveOGG(){} | ||||
| 
 | ||||
|   void OutProgressiveOGG::init(Util::Config * cfg){ | ||||
|   void OutProgressiveOGG::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "OGG"; | ||||
|     capa["friendly"] = "OGG over HTTP"; | ||||
|  | @ -30,15 +30,16 @@ namespace Mist { | |||
|   void OutProgressiveOGG::sendNext(){ | ||||
|     unsigned int track = thisPacket.getTrackId(); | ||||
| 
 | ||||
| 
 | ||||
|     OGG::oggSegment newSegment; | ||||
|     thisPacket.getString("data", newSegment.dataString); | ||||
|     pageBuffer[track].totalFrames = ((double)thisPacket.getTime() / (1000000.0f / myMeta.tracks[track].fpks)) + 1.5; //should start at 1. added .5 for rounding.
 | ||||
|     pageBuffer[track].totalFrames = | ||||
|         ((double)thisPacket.getTime() / (1000000.0f / myMeta.tracks[track].fpks)) + | ||||
|         1.5; // should start at 1. added .5 for rounding.
 | ||||
| 
 | ||||
|     if (pageBuffer[track].codec == OGG::THEORA){ | ||||
|       newSegment.isKeyframe = thisPacket.getFlag("keyframe"); | ||||
|       if (newSegment.isKeyframe == true){ | ||||
|         pageBuffer[track].sendTo(myConn);//send data remaining in buffer (expected to fit on a page), keyframe will allways start on new page
 | ||||
|         pageBuffer[track].sendTo(myConn); // send data remaining in buffer (expected to fit on a page), keyframe will allways start on new page
 | ||||
|         pageBuffer[track].lastKeyFrame = pageBuffer[track].totalFrames; | ||||
|       } | ||||
|       newSegment.framesSinceKeyFrame = pageBuffer[track].totalFrames - pageBuffer[track].lastKeyFrame; | ||||
|  | @ -51,37 +52,36 @@ namespace Mist { | |||
|     pageBuffer[track].oggSegments.push_back(newSegment); | ||||
| 
 | ||||
|     if (pageBuffer[track].codec == OGG::VORBIS){ | ||||
|       pageBuffer[track].vorbisStuff();//this updates lastKeyFrame
 | ||||
|     } | ||||
|     while (pageBuffer[track].shouldSend()){  | ||||
|       pageBuffer[track].sendTo(myConn); | ||||
|       pageBuffer[track].vorbisStuff(); // this updates lastKeyFrame
 | ||||
|     } | ||||
|     while (pageBuffer[track].shouldSend()){pageBuffer[track].sendTo(myConn);} | ||||
|   } | ||||
| 
 | ||||
|   bool OutProgressiveOGG::onFinish(){ | ||||
|     for (std::map<long long unsigned int, OGG::Page>::iterator it = pageBuffer.begin(); it != pageBuffer.end(); it++){ | ||||
|     for (std::map<long long unsigned int, OGG::Page>::iterator it = pageBuffer.begin(); | ||||
|          it != pageBuffer.end(); it++){ | ||||
|       it->second.setHeaderType(OGG::EndOfStream); | ||||
|       it->second.sendTo(myConn); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|   bool OutProgressiveOGG::parseInit(std::string & initData, std::deque<std::string> & output){ | ||||
|   bool OutProgressiveOGG::parseInit(std::string &initData, std::deque<std::string> &output){ | ||||
|     std::string temp; | ||||
|     unsigned int index = 0; | ||||
|     if (initData[0] == 0x02){ //"special" case, requires interpretation similar to table
 | ||||
|     if (initData[0] == 0x02){//"special" case, requires interpretation similar to table
 | ||||
|       if (initData.size() < 7){ | ||||
|         FAIL_MSG("initData size too tiny (size: %lu)", initData.size()); | ||||
|         return false; | ||||
|       } | ||||
|       unsigned int len1 = 0 ; | ||||
|       unsigned int len2 = 0 ; | ||||
|       unsigned int len1 = 0; | ||||
|       unsigned int len2 = 0; | ||||
|       index = 1; | ||||
|       while (initData[index] == 255){ //get len 1
 | ||||
|       while (initData[index] == 255){// get len 1
 | ||||
|         len1 += initData[index++]; | ||||
|       } | ||||
|       len1 += initData[index++]; | ||||
| 
 | ||||
|       while (initData[index] == 255){ //get len 1
 | ||||
|       while (initData[index] == 255){// get len 1
 | ||||
|         len2 += initData[index++]; | ||||
|       } | ||||
|       len2 += initData[index++]; | ||||
|  | @ -97,9 +97,9 @@ namespace Mist { | |||
|       temp = initData.substr(index, len2); | ||||
|       output.push_back(temp); | ||||
|       index += len2; | ||||
|       temp = initData.substr(index);      //remainder of string:
 | ||||
|       output.push_back(temp);             //add data to output deque
 | ||||
|     } else { | ||||
|       temp = initData.substr(index); // remainder of string:
 | ||||
|       output.push_back(temp);        // add data to output deque
 | ||||
|     }else{ | ||||
|       if (initData.size() < 7){ | ||||
|         FAIL_MSG("initData size too tiny (size: %lu)", initData.size()); | ||||
|         return false; | ||||
|  | @ -107,14 +107,14 @@ namespace Mist { | |||
|       unsigned int len = 0; | ||||
|       for (unsigned int i = 0; i < 3; i++){ | ||||
|         temp = initData.substr(index, 2); | ||||
|         len = (((unsigned int)temp[0]) << 8) | (temp[1]); //2 bytes len
 | ||||
|         index += 2; //start of data
 | ||||
|         len = (((unsigned int)temp[0]) << 8) | (temp[1]); // 2 bytes len
 | ||||
|         index += 2;                                       // start of data
 | ||||
|         if (index + len > initData.size()){ | ||||
|           FAIL_MSG("index+len > initData size"); | ||||
|           return false; | ||||
|         } | ||||
|         temp = initData.substr(index, len); | ||||
|         output.push_back(temp);             //add data to output deque
 | ||||
|         output.push_back(temp); // add data to output deque
 | ||||
|         index += len; | ||||
|         INFO_MSG("init data len[%d]: %d ", i, len); | ||||
|       } | ||||
|  | @ -124,26 +124,25 @@ namespace Mist { | |||
|   } | ||||
| 
 | ||||
|   void OutProgressiveOGG::sendHeader(){ | ||||
|     HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers
 | ||||
|     HTTP_S.Clean(); // make sure no parts of old requests are left in any buffers
 | ||||
|     HTTP_S.SetHeader("Content-Type", "video/ogg"); | ||||
|     HTTP_S.protocol = "HTTP/1.0"; | ||||
|     myConn.SendNow(HTTP_S.BuildResponse("200", "OK")); //no SetBody = unknown length - this is intentional, we will stream the entire file
 | ||||
|      | ||||
|     myConn.SendNow(HTTP_S.BuildResponse("200", "OK")); // no SetBody = unknown length - this is intentional, we will stream the entire file
 | ||||
| 
 | ||||
|     std::map<int, std::deque<std::string> > initData; | ||||
| 
 | ||||
|     OGG::oggSegment newSegment; | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       if (myMeta.tracks[*it].codec == "theora"){ //get size and position of init data for this page.
 | ||||
|         parseInit(myMeta.tracks[*it].init,  initData[*it]); | ||||
|       if (myMeta.tracks[*it].codec == "theora"){// get size and position of init data for this page.
 | ||||
|         parseInit(myMeta.tracks[*it].init, initData[*it]); | ||||
|         pageBuffer[*it].codec = OGG::THEORA; | ||||
|         pageBuffer[*it].totalFrames = 1; //starts at frame number 1, according to weird offDetectMeta function.
 | ||||
|         pageBuffer[*it].totalFrames = 1; // starts at frame number 1, according to weird offDetectMeta function.
 | ||||
|         std::string tempStr = initData[*it][0]; | ||||
|         theora::header tempHead((char *)tempStr.c_str(), 42); | ||||
|         pageBuffer[*it].split = tempHead.getKFGShift(); | ||||
|         INFO_MSG("got theora KFG shift: %d", pageBuffer[*it].split); //looks OK.
 | ||||
|       } else if (myMeta.tracks[*it].codec == "vorbis"){ | ||||
|         parseInit(myMeta.tracks[*it].init,  initData[*it]); | ||||
|         INFO_MSG("got theora KFG shift: %d", pageBuffer[*it].split); // looks OK.
 | ||||
|       }else if (myMeta.tracks[*it].codec == "vorbis"){ | ||||
|         parseInit(myMeta.tracks[*it].init, initData[*it]); | ||||
|         pageBuffer[*it].codec = OGG::VORBIS; | ||||
|         pageBuffer[*it].totalFrames = 0; | ||||
|         pageBuffer[*it].sampleRate = myMeta.tracks[*it].rate; | ||||
|  | @ -152,19 +151,19 @@ namespace Mist { | |||
|         pageBuffer[*it].blockSize[0] = std::min(tempHead.getBlockSize0(), tempHead.getBlockSize1()); | ||||
|         pageBuffer[*it].blockSize[1] = std::max(tempHead.getBlockSize0(), tempHead.getBlockSize1()); | ||||
|         char audioChannels = tempHead.getAudioChannels(); //?
 | ||||
|         vorbis::header tempHead2((char *)initData[*it][2].data(), initData[*it][2].size());         | ||||
|         pageBuffer[*it].vorbisModes = tempHead2.readModeDeque(audioChannels);//getting modes
 | ||||
|       } else if (myMeta.tracks[*it].codec == "opus"){ | ||||
|         vorbis::header tempHead2((char *)initData[*it][2].data(), initData[*it][2].size()); | ||||
|         pageBuffer[*it].vorbisModes = tempHead2.readModeDeque(audioChannels); // getting modes
 | ||||
|       }else if (myMeta.tracks[*it].codec == "opus"){ | ||||
|         pageBuffer[*it].totalFrames = 0; //?
 | ||||
|         pageBuffer[*it].codec = OGG::OPUS; | ||||
|         initData[*it].push_back(myMeta.tracks[*it].init); | ||||
|         initData[*it].push_back(std::string("OpusTags\000\000\000\012MistServer\000\000\000\000", 26)); | ||||
|       } | ||||
|       pageBuffer[*it].clear(OGG::BeginOfStream, 0, *it, 0);   //CREATES a (map)pageBuffer object, *it = id, pagetype=BOS
 | ||||
|       pageBuffer[*it].clear(OGG::BeginOfStream, 0, *it, 0); // CREATES a (map)pageBuffer object, *it = id, pagetype=BOS
 | ||||
|       newSegment.dataString = initData[*it].front(); | ||||
|       initData[*it].pop_front(); | ||||
|       pageBuffer[*it].oggSegments.push_back(newSegment); | ||||
|       pageBuffer[*it].sendTo(myConn, 0); //granule position of 0
 | ||||
|       pageBuffer[*it].sendTo(myConn, 0); // granule position of 0
 | ||||
|     } | ||||
|     for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ | ||||
|       while (initData[*it].size()){ | ||||
|  | @ -173,7 +172,7 @@ namespace Mist { | |||
|         pageBuffer[*it].oggSegments.push_back(newSegment); | ||||
|       } | ||||
|       while (pageBuffer[*it].oggSegments.size()){ | ||||
|         pageBuffer[*it].sendTo(myConn, 0); //granule position of 0
 | ||||
|         pageBuffer[*it].sendTo(myConn, 0); // granule position of 0
 | ||||
|       } | ||||
|     } | ||||
|     sentHeader = true; | ||||
|  | @ -182,7 +181,7 @@ namespace Mist { | |||
|   void OutProgressiveOGG::onRequest(){ | ||||
|     if (HTTP_R.Read(myConn)){ | ||||
|       DEBUG_MSG(DLVL_DEVEL, "Received request %s", HTTP_R.getUrl().c_str()); | ||||
|        | ||||
| 
 | ||||
|       if (HTTP_R.method == "OPTIONS" || HTTP_R.method == "HEAD"){ | ||||
|         HTTP_S.Clean(); | ||||
|         HTTP_S.SetHeader("Content-Type", "video/ogg"); | ||||
|  | @ -191,7 +190,7 @@ namespace Mist { | |||
|         HTTP_S.Clean(); | ||||
|         return; | ||||
|       } | ||||
|        | ||||
| 
 | ||||
|       if (HTTP_R.GetVar("audio") != ""){ | ||||
|         selectedTracks.insert(JSON::Value(HTTP_R.GetVar("audio")).asInt()); | ||||
|       } | ||||
|  | @ -203,11 +202,4 @@ namespace Mist { | |||
|       HTTP_R.Clean(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,28 +1,24 @@ | |||
| #include "output_http.h" | ||||
| #include <mist/ogg.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/ogg.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutProgressiveOGG : public HTTPOutput { | ||||
|     public: | ||||
|       OutProgressiveOGG(Socket::Connection & conn); | ||||
|       ~OutProgressiveOGG(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onRequest(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|       bool onFinish(); | ||||
|       bool parseInit(std::string & initData, std::deque<std::string> & output); | ||||
|     protected: | ||||
|       HTTP::Parser HTTP_R;//Received HTTP
 | ||||
|       HTTP::Parser HTTP_S;//Sent HTTP
 | ||||
|       std::map <long long unsigned int, OGG::Page > pageBuffer; //OGG specific variables
 | ||||
| namespace Mist{ | ||||
|   class OutProgressiveOGG : public HTTPOutput{ | ||||
|   public: | ||||
|     OutProgressiveOGG(Socket::Connection &conn); | ||||
|     ~OutProgressiveOGG(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onRequest(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
|     bool onFinish(); | ||||
|     bool parseInit(std::string &initData, std::deque<std::string> &output); | ||||
| 
 | ||||
|   protected: | ||||
|     HTTP::Parser HTTP_R;                                    // Received HTTP
 | ||||
|     HTTP::Parser HTTP_S;                                    // Sent HTTP
 | ||||
|     std::map<long long unsigned int, OGG::Page> pageBuffer; // OGG specific variables
 | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutProgressiveOGG mistOut; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,13 +1,12 @@ | |||
| #include "output_push.h" | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/shared_memory.h> | ||||
| #include <sys/stat.h> | ||||
| #include <mist/tinythread.h> | ||||
| #include <sys/stat.h> | ||||
| 
 | ||||
| #define PUSH_INDEX_SIZE 5 // Build index based on most recent X segments
 | ||||
| 
 | ||||
| #define PUSH_INDEX_SIZE 5 //Build index based on most recent X segments
 | ||||
| 
 | ||||
| Util::Config * pConf; | ||||
| Util::Config *pConf; | ||||
| std::string sName; | ||||
| 
 | ||||
| std::string baseURL; | ||||
|  | @ -19,35 +18,31 @@ std::string dstHost; | |||
| long long dstPort; | ||||
| std::string dstUrl; | ||||
| 
 | ||||
| //Used to keep track of all segments that can be pushed
 | ||||
| // Used to keep track of all segments that can be pushed
 | ||||
| std::map<std::string, std::map<int, std::string> > pushableSegments; | ||||
| //Used to keep track of the timestamp of each pushableSegment
 | ||||
| // Used to keep track of the timestamp of each pushableSegment
 | ||||
| std::map<std::string, std::map<int, int> > pushableTimes; | ||||
| //Used to keep track of the duration of each pushableSegment
 | ||||
| // Used to keep track of the duration of each pushableSegment
 | ||||
| std::map<std::string, std::map<int, int> > pushableDurations; | ||||
| 
 | ||||
| 
 | ||||
| //For each quality, store the latest number found in the push list
 | ||||
| // For each quality, store the latest number found in the push list
 | ||||
| std::map<std::string, int> latestNumber; | ||||
| 
 | ||||
| //For each quality, store whether it is currently being pushed.
 | ||||
| // For each quality, store whether it is currently being pushed.
 | ||||
| std::map<std::string, bool> parsing; | ||||
| 
 | ||||
| //For each quality, store an fprint-style string of the relative url to the index_<beginTime>_<endTime>.m3u8
 | ||||
| // For each quality, store an fprint-style string of the relative url to the index_<beginTime>_<endTime>.m3u8
 | ||||
| std::map<std::string, std::string> qualityIndex; | ||||
| //For each quality, store an fprint-style string of the relative url to the segment.
 | ||||
| // For each quality, store an fprint-style string of the relative url to the segment.
 | ||||
| std::map<std::string, std::string> qualitySegment; | ||||
| 
 | ||||
| //For each quality, store the last PUSH_INDEX_SIZE - 1 timestamps. Used to generate a time-constrained index.m3u8.
 | ||||
| // For each quality, store the last PUSH_INDEX_SIZE - 1 timestamps. Used to generate a time-constrained index.m3u8.
 | ||||
| std::map<std::string, std::deque<int> > qualityBeginTimes; | ||||
| 
 | ||||
| 
 | ||||
| //Parses a uri of the form 'http://<host>[:<port>]/<url>, and split it into variables
 | ||||
| void parseURI(const std::string & uri, std::string & host, long long & port, std::string & url){ | ||||
| // Parses a uri of the form 'http://<host>[:<port>]/<url>, and split it into variables
 | ||||
| void parseURI(const std::string &uri, std::string &host, long long &port, std::string &url){ | ||||
|   int loc = 0; | ||||
|   if (uri.find("http://") == 0){ | ||||
|     loc += 7; | ||||
|   } | ||||
|   if (uri.find("http://") == 0){loc += 7;} | ||||
|   host = uri.substr(loc, uri.find_first_of(":/", 7) - 7); | ||||
|   loc += host.size(); | ||||
|   if (uri[loc] == ':'){ | ||||
|  | @ -57,53 +52,49 @@ void parseURI(const std::string & uri, std::string & host, long long & port, std | |||
|   url = uri.substr(loc); | ||||
| } | ||||
| 
 | ||||
| //Do an HTTP request, and route it into a post request on a different socket.
 | ||||
| void proxyToPost(Socket::Connection & src, const std::string & srcUrl, Socket::Connection & dst, const std::string & dstUrl){ | ||||
| // Do an HTTP request, and route it into a post request on a different socket.
 | ||||
| void proxyToPost(Socket::Connection &src, const std::string &srcUrl, Socket::Connection &dst, | ||||
|                  const std::string &dstUrl){ | ||||
|   INFO_MSG("Routing %s to %s", srcUrl.c_str(), dstUrl.c_str()); | ||||
|    | ||||
|   //Send the initial request
 | ||||
| 
 | ||||
|   // Send the initial request
 | ||||
|   HTTP::Parser H; | ||||
|   H.url = srcUrl; | ||||
|   H.SendRequest(src); | ||||
|   H.Clean(); | ||||
| 
 | ||||
|   //Read only the headers of the reply
 | ||||
|   // Read only the headers of the reply
 | ||||
|   H.headerOnly = true; | ||||
|   while (src.connected()){ | ||||
|     if (src.Received().size() || src.spool()){ | ||||
|       if (H.Read(src)){ | ||||
|         break; | ||||
|       } | ||||
|       if (H.Read(src)){break;} | ||||
|     } | ||||
|   } | ||||
|   H.headerOnly = false; | ||||
| 
 | ||||
|   INFO_MSG("Reply from %s: %s %s", src.getHost().c_str(), H.url.c_str(), H.method.c_str()); | ||||
| 
 | ||||
|   //Change the headers of the reply to form a post request
 | ||||
|   // Change the headers of the reply to form a post request
 | ||||
|   H.method = "POST"; | ||||
|   H.url = dstUrl; | ||||
|   H.protocol = "HTTP/1.1"; | ||||
|   H.SetHeader("Host", dstHost); | ||||
|   //Start the post request
 | ||||
|   // Start the post request
 | ||||
|   H.SendRequest(dst); | ||||
|   //Route the original payload.
 | ||||
|   // Route the original payload.
 | ||||
|   H.Proxy(src, dst); | ||||
| 
 | ||||
|   H.Clean(); | ||||
|   while (dst.connected()){ | ||||
|     if (dst.Received().size() || dst.spool()){ | ||||
|       if (H.Read(dst)){ | ||||
|         break; | ||||
|       } | ||||
|       if (H.Read(dst)){break;} | ||||
|     } | ||||
|   } | ||||
|   INFO_MSG("Reply from %s: %s %s", dst.getHost().c_str(), H.url.c_str(), H.method.c_str()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ///Push the first registered segment for this quality
 | ||||
| void pushFirstElement(std::string qId) { | ||||
| /// Push the first registered segment for this quality
 | ||||
| void pushFirstElement(std::string qId){ | ||||
|   std::string semName = "MstPushLock" + sName; | ||||
|   IPC::semaphore pushLock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); | ||||
| 
 | ||||
|  | @ -111,9 +102,9 @@ void pushFirstElement(std::string qId) { | |||
|   int time; | ||||
|   int beginTime; | ||||
|   int duration; | ||||
|   //Wait for exclusive access to all globals
 | ||||
|   // Wait for exclusive access to all globals
 | ||||
|   pushLock.wait(); | ||||
|   //Retrieve all globals for the segment to be pushed
 | ||||
|   // Retrieve all globals for the segment to be pushed
 | ||||
|   if (pushableSegments[qId].size()){ | ||||
|     url = pushableSegments[qId].begin()->second; | ||||
|     time = pushableTimes[qId].begin()->second; | ||||
|  | @ -124,80 +115,77 @@ void pushFirstElement(std::string qId) { | |||
|       beginTime = time; | ||||
|     } | ||||
|   } | ||||
|   //Give up exclusive access to all globals
 | ||||
|   // Give up exclusive access to all globals
 | ||||
|   pushLock.post(); | ||||
|   //Return if we do not have a segment to push
 | ||||
|   if (url == ""){ | ||||
|     return; | ||||
|   } | ||||
|   // Return if we do not have a segment to push
 | ||||
|   if (url == ""){return;} | ||||
| 
 | ||||
|   //Create both source and destination connections
 | ||||
|   // Create both source and destination connections
 | ||||
|   Socket::Connection srcConn(srcHost, srcPort, true); | ||||
|   Socket::Connection dstConn(dstHost, dstPort, true); | ||||
| 
 | ||||
|   //Set the locations to push to for this segment
 | ||||
|   // Set the locations to push to for this segment
 | ||||
|   std::string srcLocation = baseURL + url; | ||||
|   std::string dstLocation = dstUrl.substr(0, dstUrl.rfind("/")) + url; | ||||
| 
 | ||||
|   //Push the segment
 | ||||
|   // Push the segment
 | ||||
|   proxyToPost(srcConn, srcLocation, dstConn, dstLocation); | ||||
|    | ||||
|    | ||||
| 
 | ||||
|   srcConn.open(srcHost, srcPort, true); | ||||
| 
 | ||||
|   //Set the location to push to for the index containing this segment.
 | ||||
|   //The index will contain (at most) the last PUSH_INDEX_SIZE segments.
 | ||||
|   // Set the location to push to for the index containing this segment.
 | ||||
|   // The index will contain (at most) the last PUSH_INDEX_SIZE segments.
 | ||||
|   char srcIndex[200]; | ||||
|   snprintf(srcIndex, 200, qualityIndex[qId].c_str(), beginTime , time + duration);  | ||||
|   snprintf(srcIndex, 200, qualityIndex[qId].c_str(), beginTime, time + duration); | ||||
|   srcLocation = baseURL + srcIndex; | ||||
|   dstLocation = dstLocation.substr(0, dstLocation.rfind("/")) + "/index.m3u8"; | ||||
| 
 | ||||
|   //Push the index
 | ||||
|   // Push the index
 | ||||
|   proxyToPost(srcConn, srcLocation, dstConn, dstLocation); | ||||
| 
 | ||||
| 
 | ||||
|   srcConn.open(srcHost, srcPort, true); | ||||
| 
 | ||||
|   //Set the location to push to for the global index containing all qualities.
 | ||||
|   // Set the location to push to for the global index containing all qualities.
 | ||||
|   srcLocation = baseURL + "/push/index.m3u8"; | ||||
|   dstLocation = dstLocation.substr(0, dstLocation.rfind("/")); | ||||
|   dstLocation = dstLocation.substr(0, dstLocation.rfind("/")) + "/index.m3u8"; | ||||
| 
 | ||||
|   //Push the global index
 | ||||
|   // Push the global index
 | ||||
|   proxyToPost(srcConn, srcLocation, dstConn, dstLocation); | ||||
| 
 | ||||
|   //Close both connections
 | ||||
|   // Close both connections
 | ||||
|   ///\todo Make the dstConn "persistent" for each thread?
 | ||||
|   srcConn.close(); | ||||
|   dstConn.close(); | ||||
| 
 | ||||
|   //Wait for exclusive access to all globals
 | ||||
|   // Wait for exclusive access to all globals
 | ||||
|   pushLock.wait(); | ||||
|   //Update all globals to indicate the segment has been pushed correctly
 | ||||
|   // Update all globals to indicate the segment has been pushed correctly
 | ||||
|   pushableSegments[qId].erase(pushableSegments[qId].begin()); | ||||
|   pushableTimes[qId].erase(pushableTimes[qId].begin()); | ||||
|   pushableDurations[qId].erase(pushableDurations[qId].begin()); | ||||
|   qualityBeginTimes[qId].push_back(time); | ||||
|   //Remove the first elements fromt he beginTimes map to make sure we have PUSH_INDEX_SIZE elements in our index.
 | ||||
|   //We use -1 here, because we use the segment to currently push as well as everything stored in the map
 | ||||
|   // Remove the first elements fromt he beginTimes map to make sure we have PUSH_INDEX_SIZE elements in our index.
 | ||||
|   // We use -1 here, because we use the segment to currently push as well as everything stored in the map
 | ||||
|   while (qualityBeginTimes[qId].size() > PUSH_INDEX_SIZE - 1){ | ||||
|     qualityBeginTimes[qId].pop_front(); | ||||
|   } | ||||
|   //Give up exclusive access to all globals
 | ||||
|   // Give up exclusive access to all globals
 | ||||
|   pushLock.post(); | ||||
| } | ||||
| 
 | ||||
| ///Thread used to push data.
 | ||||
| void pushThread(void * nullPointer){ | ||||
| /// Thread used to push data.
 | ||||
| void pushThread(void *nullPointer){ | ||||
|   std::string myThread; | ||||
| 
 | ||||
|   //Attempt to claim a non-claimed quality.
 | ||||
|   // Attempt to claim a non-claimed quality.
 | ||||
|   std::string semName = "MstPushClaim" + sName; | ||||
|   IPC::semaphore pushThreadLock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); | ||||
| 
 | ||||
|   pushThreadLock.wait(); | ||||
|   for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); it != pushableSegments.end(); it++){ | ||||
|     if (it->second.size()){//Make sure we dont try to "claim" pushing an empty track
 | ||||
|   for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); | ||||
|        it != pushableSegments.end(); it++){ | ||||
|     if (it->second.size()){// Make sure we dont try to "claim" pushing an empty track
 | ||||
|       if (!parsing.count(it->first) || !parsing[it->first]){ | ||||
|         INFO_MSG("Claiming thread %s", it->first.c_str()); | ||||
|         myThread = it->first; | ||||
|  | @ -208,99 +196,91 @@ void pushThread(void * nullPointer){ | |||
|   } | ||||
|   pushThreadLock.post(); | ||||
| 
 | ||||
|   //Return if we were unable to claim a quality
 | ||||
|   // Return if we were unable to claim a quality
 | ||||
|   if (myThread == ""){ | ||||
|     INFO_MSG("No thread claimed"); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   //While this output is active, push the first element in the list
 | ||||
|   // While this output is active, push the first element in the list
 | ||||
|   while (pConf->is_active){ | ||||
|     pushFirstElement(myThread); | ||||
|     if (!pushableSegments[myThread].size()){ | ||||
|       Util::wait(1000); | ||||
|     } | ||||
|     if (!pushableSegments[myThread].size()){Util::wait(1000);} | ||||
|   } | ||||
| 
 | ||||
|   parsing[myThread] = false; | ||||
| } | ||||
| 
 | ||||
| namespace Mist{ | ||||
| 
 | ||||
| namespace Mist { | ||||
| 
 | ||||
|   OutPush::OutPush(Socket::Connection & conn) : Output(conn){ | ||||
|     config->activate(); | ||||
|   } | ||||
|   OutPush::OutPush(Socket::Connection &conn) : Output(conn){config->activate();} | ||||
|   OutPush::~OutPush(){} | ||||
|    | ||||
|   void OutPush::requestHandler() { | ||||
|     //Set aal basic data only the first time.
 | ||||
| 
 | ||||
|   void OutPush::requestHandler(){ | ||||
|     // Set aal basic data only the first time.
 | ||||
|     if (streamName == ""){ | ||||
|       srcPort = 80; | ||||
|       parseURI(config->getString("pushlist"), srcHost, srcPort, pushURL); | ||||
|       dstPort = 80; | ||||
|       parseURI(config->getString("destination"), dstHost, dstPort, dstUrl); | ||||
| 
 | ||||
| 
 | ||||
|       //Strip "/push/list" from the URL
 | ||||
|       // Strip "/push/list" from the URL
 | ||||
|       baseURL = pushURL.substr(0, pushURL.rfind("/")); | ||||
|       baseURL = baseURL.substr(0, baseURL.rfind("/")); | ||||
| 
 | ||||
|       //Locate the streamname from the pushURL
 | ||||
|       // Locate the streamname from the pushURL
 | ||||
|       int loc = baseURL.find("/", 1) + 1; | ||||
|       streamName = pushURL.substr(loc, pushURL.rfind("/") - loc); | ||||
|       sName = streamName; | ||||
| 
 | ||||
|       INFO_MSG("host: %s, port %lld, url %s, baseURL %s, streamName %s", srcHost.c_str(), srcPort, pushURL.c_str(), baseURL.c_str(), streamName.c_str()); | ||||
|       INFO_MSG("host: %s, port %lld, url %s, baseURL %s, streamName %s", srcHost.c_str(), srcPort, | ||||
|                pushURL.c_str(), baseURL.c_str(), streamName.c_str()); | ||||
|     } | ||||
|     //Reconnect when disconnected
 | ||||
|     if (!listConn.connected()){ | ||||
|       listConn.open(srcHost, srcPort, true); | ||||
|     } | ||||
|     //Request the push list
 | ||||
|     // Reconnect when disconnected
 | ||||
|     if (!listConn.connected()){listConn.open(srcHost, srcPort, true);} | ||||
|     // Request the push list
 | ||||
|     if (listConn.connected()){ | ||||
|       HTTP::Parser hReq; | ||||
|       hReq.url = baseURL + "/push/list"; | ||||
|       hReq.SendRequest(listConn); | ||||
|       hReq.Clean(); | ||||
|       //Read the entire response, not just the headers!
 | ||||
|       // Read the entire response, not just the headers!
 | ||||
|       while (!hReq.Read(listConn) && listConn.connected()){ | ||||
|         Util::sleep(100); | ||||
|         listConn.spool(); | ||||
|       } | ||||
| 
 | ||||
|       //Construct and parse the json list
 | ||||
|       // Construct and parse the json list
 | ||||
|       JSON::Value reply = JSON::fromString(hReq.body); | ||||
|       int numQualities = reply["qualities"].size(); | ||||
|       for (int i = 0; i < numQualities; i++){ | ||||
|         JSON::Value & qRef = reply["qualities"][i]; | ||||
|         JSON::Value &qRef = reply["qualities"][i]; | ||||
|         std::string qId = qRef["id"].asString(); | ||||
| 
 | ||||
|         //Set both the index and segment urls when not yet set.
 | ||||
|         // Set both the index and segment urls when not yet set.
 | ||||
|         if (!qualityIndex.count(qId)){ | ||||
|           qualityIndex[qId] = qRef["index"].asString(); | ||||
|           qualitySegment[qId] = qRef["segment"].asString(); | ||||
|         } | ||||
|          | ||||
|         //Save latest segment number before parsing
 | ||||
| 
 | ||||
|         // Save latest segment number before parsing
 | ||||
|         int curLatestNumber = latestNumber[qId]; | ||||
|          | ||||
|         //Loop over all segments
 | ||||
| 
 | ||||
|         // Loop over all segments
 | ||||
|         for (int j = 0; j < qRef["segments"].size(); j++){ | ||||
|           JSON::Value & segRef = qRef["segments"][j]; | ||||
|           JSON::Value &segRef = qRef["segments"][j]; | ||||
|           int thisNumber = segRef["number"].asInt(); | ||||
| 
 | ||||
|           //Check if this segment is newer than the newest segment before parsing
 | ||||
|           // Check if this segment is newer than the newest segment before parsing
 | ||||
|           if (thisNumber > curLatestNumber){ | ||||
|             //If it is the highest so far, store its number
 | ||||
|             if (thisNumber > latestNumber[qId]){ | ||||
|               latestNumber[qId] = thisNumber; | ||||
|             } | ||||
|             //If it is not yet added, add it.
 | ||||
|             // If it is the highest so far, store its number
 | ||||
|             if (thisNumber > latestNumber[qId]){latestNumber[qId] = thisNumber;} | ||||
|             // If it is not yet added, add it.
 | ||||
|             if (!pushableSegments[qId].count(thisNumber)){ | ||||
|               char segmentUrl[200]; | ||||
|               //The qualitySegment map contains a printf-style string
 | ||||
|               snprintf(segmentUrl, 200, qualitySegment[qId].c_str(), segRef["time"].asInt(), segRef["time"].asInt() + segRef["duration"].asInt()); | ||||
|               // The qualitySegment map contains a printf-style string
 | ||||
|               snprintf(segmentUrl, 200, qualitySegment[qId].c_str(), segRef["time"].asInt(), | ||||
|                        segRef["time"].asInt() + segRef["duration"].asInt()); | ||||
|               pushableSegments[qId][segRef["number"].asInt()] = segmentUrl; | ||||
|               pushableTimes[qId][segRef["number"].asInt()] = segRef["time"].asInt(); | ||||
|               pushableDurations[qId][segRef["number"].asInt()] = segRef["duration"].asInt(); | ||||
|  | @ -309,15 +289,14 @@ namespace Mist { | |||
|         } | ||||
|       } | ||||
|     } | ||||
|     //Calculate how many qualities are not yet being pushed
 | ||||
|     // Calculate how many qualities are not yet being pushed
 | ||||
|     int threadsToSpawn = pushableSegments.size(); | ||||
|     for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); it != pushableSegments.end(); it++){ | ||||
|       if (parsing.count(it->first) && parsing[it->first]){ | ||||
|         threadsToSpawn --; | ||||
|       } | ||||
|     for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); | ||||
|          it != pushableSegments.end(); it++){ | ||||
|       if (parsing.count(it->first) && parsing[it->first]){threadsToSpawn--;} | ||||
|     } | ||||
|     //And start a thread for each unpushed quality.
 | ||||
|     //Threads determine which quality to push for themselves.
 | ||||
|     // And start a thread for each unpushed quality.
 | ||||
|     // Threads determine which quality to push for themselves.
 | ||||
|     for (int i = 0; i < threadsToSpawn; i++){ | ||||
|       tthread::thread thisThread(pushThread, 0); | ||||
|       thisThread.detach(); | ||||
|  | @ -325,17 +304,19 @@ namespace Mist { | |||
|     Util::sleep(100); | ||||
|   } | ||||
| 
 | ||||
|   void OutPush::init(Util::Config * cfg){ | ||||
|   void OutPush::init(Util::Config *cfg){ | ||||
|     Output::init(cfg); | ||||
|     capa["name"] = "Push"; | ||||
|     capa["desc"] = "Enables HTTP Pushing."; | ||||
|     capa["required"]["pushlist"]["name"] = "URL location of the pushing list"; | ||||
|     capa["required"]["pushlist"]["help"] = "This is the location that will be checked for pushable data."; | ||||
|     capa["required"]["pushlist"]["help"] = | ||||
|         "This is the location that will be checked for pushable data."; | ||||
|     capa["required"]["pushlist"]["option"] = "--pushlist"; | ||||
|     capa["required"]["pushlist"]["short"] = "p"; | ||||
|     capa["required"]["pushlist"]["type"] = "str"; | ||||
|     capa["required"]["destination"]["name"] = "URL location of the destination"; | ||||
|     capa["required"]["destination"]["help"] = "This is the location that the data will be pushed to."; | ||||
|     capa["required"]["destination"]["help"] = | ||||
|         "This is the location that the data will be pushed to."; | ||||
|     capa["required"]["destination"]["option"] = "--destination"; | ||||
|     capa["required"]["destination"]["short"] = "D"; | ||||
|     capa["required"]["destination"]["type"] = "str"; | ||||
|  | @ -343,4 +324,4 @@ namespace Mist { | |||
|     pConf = cfg; | ||||
|     config = cfg; | ||||
|   } | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -2,18 +2,19 @@ | |||
| 
 | ||||
| #include "output.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutPush : public Output { | ||||
|     public: | ||||
|       OutPush(Socket::Connection & conn); | ||||
|       ~OutPush(); | ||||
|       static bool listenMode(){return false;} | ||||
|       virtual void requestHandler(); | ||||
|       static void init(Util::Config * cfg); | ||||
|     protected: | ||||
|       Socket::Connection listConn; | ||||
|       std::string pushURL; | ||||
| namespace Mist{ | ||||
|   class OutPush : public Output{ | ||||
|   public: | ||||
|     OutPush(Socket::Connection &conn); | ||||
|     ~OutPush(); | ||||
|     static bool listenMode(){return false;} | ||||
|     virtual void requestHandler(); | ||||
|     static void init(Util::Config *cfg); | ||||
| 
 | ||||
|   protected: | ||||
|     Socket::Connection listConn; | ||||
|     std::string pushURL; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutPush mistOut; | ||||
|  |  | |||
|  | @ -1,46 +1,44 @@ | |||
| #include "output_raw.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutRaw::OutRaw(Socket::Connection & conn) : Output(conn) { | ||||
| namespace Mist{ | ||||
|   OutRaw::OutRaw(Socket::Connection &conn) : Output(conn){ | ||||
|     streamName = config->getString("streamname"); | ||||
|     initialize(); | ||||
|     std::string tracks = config->getString("tracks"); | ||||
|     if (tracks.size()){ | ||||
|       selectedTracks.clear(); | ||||
|       unsigned int currTrack = 0; | ||||
|       //loop over tracks, add any found track IDs to selectedTracks
 | ||||
|       // loop over tracks, add any found track IDs to selectedTracks
 | ||||
|       for (unsigned int i = 0; i < tracks.size(); ++i){ | ||||
|         if (tracks[i] >= '0' && tracks[i] <= '9'){ | ||||
|           currTrack = currTrack*10 + (tracks[i] - '0'); | ||||
|           currTrack = currTrack * 10 + (tracks[i] - '0'); | ||||
|         }else{ | ||||
|           if (currTrack > 0){ | ||||
|             selectedTracks.insert(currTrack); | ||||
|           } | ||||
|           if (currTrack > 0){selectedTracks.insert(currTrack);} | ||||
|           currTrack = 0; | ||||
|         } | ||||
|       } | ||||
|       if (currTrack > 0){ | ||||
|         selectedTracks.insert(currTrack); | ||||
|       } | ||||
|       if (currTrack > 0){selectedTracks.insert(currTrack);} | ||||
|     } | ||||
|     parseData = true; | ||||
|     seek(config->getInteger("seek")); | ||||
|   } | ||||
|    | ||||
|   OutRaw::~OutRaw() {} | ||||
|    | ||||
|   void OutRaw::init(Util::Config * cfg){ | ||||
| 
 | ||||
|   OutRaw::~OutRaw(){} | ||||
| 
 | ||||
|   void OutRaw::init(Util::Config *cfg){ | ||||
|     Output::init(cfg); | ||||
|     capa["name"] = "RAW"; | ||||
|     capa["friendly"] = "DTSC over stdout"; | ||||
|     capa["desc"] = "Pseudostreaming in DTSC format over standard output"; | ||||
|     capa["deps"] = ""; | ||||
|     capa["required"]["streamname"]["name"] = "Stream"; | ||||
|     capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports."; | ||||
|     capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add " | ||||
|                                              "this protocol multiple times using different ports."; | ||||
|     capa["required"]["streamname"]["type"] = "str"; | ||||
|     capa["required"]["streamname"]["option"] = "--stream"; | ||||
|     capa["optional"]["tracks"]["name"] = "Tracks"; | ||||
|     capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces"; | ||||
|     capa["optional"]["tracks"]["help"] = | ||||
|         "The track IDs of the stream that this connector will transmit separated by spaces"; | ||||
|     capa["optional"]["tracks"]["type"] = "str"; | ||||
|     capa["optional"]["tracks"]["option"] = "--tracks"; | ||||
|     capa["optional"]["seek"]["name"] = "Seek point"; | ||||
|  | @ -48,24 +46,25 @@ namespace Mist { | |||
|     capa["optional"]["seek"]["type"] = "int"; | ||||
|     capa["optional"]["seek"]["option"] = "--seek"; | ||||
|     capa["codecs"][0u][0u].append("+*"); | ||||
|     cfg->addOption("streamname", | ||||
|                    JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); | ||||
|     cfg->addOption("tracks", | ||||
|                    JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}")); | ||||
|     cfg->addOption("seek", | ||||
|                    JSON::fromString("{\"arg\":\"integer\",\"value\":[0],\"short\": \"S\",\"long\":\"seek\",\"help\":\"The time in milliseconds to seek to, 0 by default.\"}")); | ||||
|     cfg->addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":" | ||||
|                                                   "\"stream\",\"help\":\"The name of the stream " | ||||
|                                                   "that this connector will transmit.\"}")); | ||||
|     cfg->addOption("tracks", JSON::fromString( | ||||
|                                  "{\"arg\":\"string\",\"value\":[\"\"],\"short\": " | ||||
|                                  "\"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream " | ||||
|                                  "that this connector will transmit separated by spaces.\"}")); | ||||
|     cfg->addOption("seek", JSON::fromString("{\"arg\":\"integer\",\"value\":[0],\"short\": " | ||||
|                                             "\"S\",\"long\":\"seek\",\"help\":\"The time in " | ||||
|                                             "milliseconds to seek to, 0 by default.\"}")); | ||||
|     cfg->addConnectorOptions(666, capa); | ||||
|     config = cfg; | ||||
|   } | ||||
|    | ||||
|   void OutRaw::sendNext(){ | ||||
|     myConn.SendNow(thisPacket.getData(), thisPacket.getDataLen()); | ||||
|   } | ||||
| 
 | ||||
|   void OutRaw::sendNext(){myConn.SendNow(thisPacket.getData(), thisPacket.getDataLen());} | ||||
| 
 | ||||
|   void OutRaw::sendHeader(){ | ||||
|     myMeta.send(myConn); | ||||
|     sentHeader = true; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,15 +1,14 @@ | |||
| #include "output.h" | ||||
| 
 | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutRaw : public Output { | ||||
|     public: | ||||
|       OutRaw(Socket::Connection & conn); | ||||
|       ~OutRaw(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
| namespace Mist{ | ||||
|   class OutRaw : public Output{ | ||||
|   public: | ||||
|     OutRaw(Socket::Connection &conn); | ||||
|     ~OutRaw(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutRaw mistOut; | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,37 +1,37 @@ | |||
| #include "output.h" | ||||
| #include <mist/flv_tag.h> | ||||
| #include <mist/amf.h> | ||||
| #include <mist/rtmpchunks.h> | ||||
| #include <mist/flv_tag.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/rtmpchunks.h> | ||||
| #include <mist/url.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutRTMP : public Output{ | ||||
|   public: | ||||
|     OutRTMP(Socket::Connection &conn); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onRequest(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
|     static bool listenMode(); | ||||
|     void requestHandler(); | ||||
|     bool onFinish(); | ||||
| 
 | ||||
|  class OutRTMP : public Output { | ||||
|     public: | ||||
|       OutRTMP(Socket::Connection & conn); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onRequest(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|       static bool listenMode(); | ||||
|       void requestHandler(); | ||||
|       bool onFinish(); | ||||
|     protected: | ||||
|       std::string streamOut;///<When pushing out, the output stream name
 | ||||
|       int64_t rtmpOffset; | ||||
|       uint64_t lastOutTime; | ||||
|       unsigned int maxbps; | ||||
|       int64_t bootMsOffset; | ||||
|       std::string app_name; | ||||
|       void parseChunk(Socket::Buffer & inputBuffer); | ||||
|       void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId); | ||||
|       void sendCommand(AMF::Object & amfReply, int messageType, int streamId); | ||||
|       void startPushOut(const char * args); | ||||
|       HTTP::URL pushApp, pushUrl; | ||||
|       uint8_t authAttempts; | ||||
|   protected: | ||||
|     std::string streamOut; ///< When pushing out, the output stream name
 | ||||
|     int64_t rtmpOffset; | ||||
|     uint64_t lastOutTime; | ||||
|     unsigned int maxbps; | ||||
|     int64_t bootMsOffset; | ||||
|     std::string app_name; | ||||
|     void parseChunk(Socket::Buffer &inputBuffer); | ||||
|     void parseAMFCommand(AMF::Object &amfData, int messageType, int streamId); | ||||
|     void sendCommand(AMF::Object &amfReply, int messageType, int streamId); | ||||
|     void startPushOut(const char *args); | ||||
|     HTTP::URL pushApp, pushUrl; | ||||
|     uint8_t authAttempts; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutRTMP mistOut; | ||||
|  |  | |||
|  | @ -16,7 +16,9 @@ namespace Mist{ | |||
| 
 | ||||
|   /// Helper functions for passing packets into the OutRTSP class
 | ||||
|   void insertPacket(const DTSC::Packet &pkt){classPointer->incomingPacket(pkt);} | ||||
|   void insertRTP(const uint64_t track, const RTP::Packet &p){classPointer->incomingRTP(track, p);} | ||||
|   void insertRTP(const uint64_t track, const RTP::Packet &p){ | ||||
|     classPointer->incomingRTP(track, p); | ||||
|   } | ||||
| 
 | ||||
|   /// Takes incoming packets and buffers them.
 | ||||
|   void OutRTSP::incomingPacket(const DTSC::Packet &pkt){ | ||||
|  | @ -31,14 +33,17 @@ namespace Mist{ | |||
|     } | ||||
|     /// \TODO Make this less inefficient. Seriously. Maybe use DTSC::RetimedPacket by extending with bmo functionality...?
 | ||||
|     static DTSC::Packet newPkt; | ||||
|     char * pktData; | ||||
|     char *pktData; | ||||
|     size_t pktDataLen; | ||||
|     pkt.getString("data", pktData, pktDataLen); | ||||
|     newPkt.genericFill(pkt.getTime() + packetOffset, pkt.getInt("offset"), pkt.getTrackId(), pktData, pktDataLen, 0, pkt.getFlag("keyframe"), bootMsOffset); | ||||
|     newPkt.genericFill(pkt.getTime() + packetOffset, pkt.getInt("offset"), pkt.getTrackId(), | ||||
|                        pktData, pktDataLen, 0, pkt.getFlag("keyframe"), bootMsOffset); | ||||
|     bufferLivePacket(newPkt); | ||||
|     //bufferLivePacket(DTSC::RetimedPacket(pkt.getTime() + packetOffset, pkt));
 | ||||
|     // bufferLivePacket(DTSC::RetimedPacket(pkt.getTime() + packetOffset, pkt));
 | ||||
|   } | ||||
|   void OutRTSP::incomingRTP(const uint64_t track, const RTP::Packet &p){ | ||||
|     sdpState.handleIncomingRTP(track, p); | ||||
|   } | ||||
|   void OutRTSP::incomingRTP(const uint64_t track, const RTP::Packet &p){sdpState.handleIncomingRTP(track, p);} | ||||
| 
 | ||||
|   OutRTSP::OutRTSP(Socket::Connection &myConn) : Output(myConn){ | ||||
|     connectedAt = Util::epoch() + 2208988800ll; | ||||
|  | @ -149,8 +154,7 @@ namespace Mist{ | |||
|       callBack = sendUDP; | ||||
|       if (Util::epoch() / 5 != sdpState.tracks[tid].rtcpSent){ | ||||
|         sdpState.tracks[tid].rtcpSent = Util::epoch() / 5; | ||||
|         sdpState.tracks[tid].pack.sendRTCP_SR(connectedAt, &sdpState.tracks[tid].rtcp, tid, myMeta, | ||||
|                                            sendUDP); | ||||
|         sdpState.tracks[tid].pack.sendRTCP_SR(connectedAt, &sdpState.tracks[tid].rtcp, tid, myMeta, sendUDP); | ||||
|       } | ||||
|     }else{ | ||||
|       socket = &myConn; | ||||
|  | @ -158,7 +162,7 @@ namespace Mist{ | |||
|     } | ||||
| 
 | ||||
|     uint64_t offset = thisPacket.getInt("offset"); | ||||
|     sdpState.tracks[tid].pack.setTimestamp((timestamp+offset) * SDP::getMultiplier(myMeta.tracks[tid])); | ||||
|     sdpState.tracks[tid].pack.setTimestamp((timestamp + offset) * SDP::getMultiplier(myMeta.tracks[tid])); | ||||
|     sdpState.tracks[tid].pack.sendData(socket, callBack, dataPointer, dataLen, | ||||
|                                        sdpState.tracks[tid].channel, myMeta.tracks[tid].codec); | ||||
|   } | ||||
|  | @ -175,8 +179,7 @@ namespace Mist{ | |||
|     while ((!expectTCP || handleTCP()) && HTTP_R.Read(myConn)){ | ||||
|       // cancel broken URLs
 | ||||
|       if (HTTP_R.url.size() < 8){ | ||||
|         WARN_MSG("Invalid data found in RTSP input around ~%llub - disconnecting!", | ||||
|                  myConn.dataDown()); | ||||
|         WARN_MSG("Invalid data found in RTSP input around ~%llub - disconnecting!", myConn.dataDown()); | ||||
|         myConn.close(); | ||||
|         break; | ||||
|       } | ||||
|  | @ -195,18 +198,17 @@ namespace Mist{ | |||
|         Util::sanitizeName(streamName); | ||||
|       } | ||||
|       if (streamName.size()){ | ||||
|         HTTP_S.SetHeader("Session", | ||||
|                          Secure::md5(HTTP_S.GetHeader("User-Agent") + getConnectedHost()) + "_" + | ||||
|                              streamName); | ||||
|         HTTP_S.SetHeader("Session", Secure::md5(HTTP_S.GetHeader("User-Agent") + getConnectedHost()) + | ||||
|                                         "_" + streamName); | ||||
|       } | ||||
| 
 | ||||
|       //allow setting of max lead time through buffer variable.
 | ||||
|       //max lead time is set in MS, but the variable is in integer seconds for simplicity.
 | ||||
|       // allow setting of max lead time through buffer variable.
 | ||||
|       // max lead time is set in MS, but the variable is in integer seconds for simplicity.
 | ||||
|       if (HTTP_R.GetVar("buffer") != ""){ | ||||
|         maxSkipAhead = JSON::Value(HTTP_R.GetVar("buffer")).asInt() * 1000; | ||||
|       } | ||||
|       //allow setting of play back rate through buffer variable.
 | ||||
|       //play back rate is set in MS per second, but the variable is a simple multiplier.
 | ||||
|       // allow setting of play back rate through buffer variable.
 | ||||
|       // play back rate is set in MS per second, but the variable is a simple multiplier.
 | ||||
|       if (HTTP_R.GetVar("rate") != ""){ | ||||
|         double multiplier = atof(HTTP_R.GetVar("rate").c_str()); | ||||
|         if (multiplier){ | ||||
|  | @ -257,13 +259,16 @@ namespace Mist{ | |||
|         std::stringstream transportString; | ||||
|         transportString << "v=0\r\n" | ||||
|                            "o=- " | ||||
|                         << Util::getMS() << " 1 IN IP4 127.0.0.1\r\n" | ||||
|                                             "s=" | ||||
|                         << streamName << "\r\n" | ||||
|                                          "c=IN IP4 0.0.0.0\r\n" | ||||
|                                          "i=" | ||||
|                         << streamName << "\r\n" | ||||
|                                          "u=" | ||||
|                         << Util::getMS() | ||||
|                         << " 1 IN IP4 127.0.0.1\r\n" | ||||
|                            "s=" | ||||
|                         << streamName | ||||
|                         << "\r\n" | ||||
|                            "c=IN IP4 0.0.0.0\r\n" | ||||
|                            "i=" | ||||
|                         << streamName | ||||
|                         << "\r\n" | ||||
|                            "u=" | ||||
|                         << HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) << "/" << streamName | ||||
|                         << "\r\n" | ||||
|                            "t=0 0\r\n" | ||||
|  | @ -283,8 +288,7 @@ namespace Mist{ | |||
|         } | ||||
|         transportString << "\r\n"; | ||||
|         HIGH_MSG("Reply: %s", transportString.str().c_str()); | ||||
|         HTTP_S.SetHeader("Content-Base", | ||||
|                          HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) + "/" + streamName); | ||||
|         HTTP_S.SetHeader("Content-Base", HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) + "/" + streamName); | ||||
|         HTTP_S.SetHeader("Content-Type", "application/sdp"); | ||||
|         HTTP_S.SetBody(transportString.str()); | ||||
|         HTTP_S.SendResponse("200", "OK", myConn); | ||||
|  | @ -435,9 +439,7 @@ namespace Mist{ | |||
|         lastRecv = Util::epoch(); // prevent disconnect of idle TCP connection when using UDP
 | ||||
|         myConn.addDown(s.data_len); | ||||
|         RTP::Packet pack(s.data, s.data_len); | ||||
|         if (!it->second.theirSSRC){ | ||||
|           it->second.theirSSRC = pack.getSSRC(); | ||||
|         } | ||||
|         if (!it->second.theirSSRC){it->second.theirSSRC = pack.getSSRC();} | ||||
|         it->second.sorter.addPacket(pack); | ||||
|       } | ||||
|       if (selectedTracks.count(it->first) && Util::epoch() / 5 != it->second.rtcpSent){ | ||||
|  | @ -446,5 +448,4 @@ namespace Mist{ | |||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ namespace Mist{ | |||
| 
 | ||||
|   private: | ||||
|     long long connectedAt;   ///< The timestamp the connection was made, as reference point for RTCP
 | ||||
|                              ///packets.
 | ||||
|                              /// packets.
 | ||||
|     unsigned int pausepoint; ///< Position to pause at, when reached
 | ||||
|     SDP::State sdpState; | ||||
|     HTTP::Parser HTTP_R, HTTP_S; | ||||
|  | @ -35,7 +35,6 @@ namespace Mist{ | |||
|     bool handleTCP(); | ||||
|     void handleUDP(); | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutRTSP mistOut; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,22 +1,23 @@ | |||
| #include <mist/defines.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/bitfields.h> | ||||
| #include "output_sanitycheck.h" | ||||
| #include <mist/bitfields.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/defines.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutSanityCheck::OutSanityCheck(Socket::Connection & conn) : Output(conn){ | ||||
| namespace Mist{ | ||||
|   OutSanityCheck::OutSanityCheck(Socket::Connection &conn) : Output(conn){ | ||||
|     streamName = config->getString("streamname"); | ||||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|     initialize(); | ||||
|     initialSeek(); | ||||
|     sortSet.clear(); | ||||
|     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; | ||||
|       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.size = myMeta.tracks[*subIt].parts[0].getSize(); // bytesize of frame (alle parts all together)
 | ||||
|       temp.index = 0; | ||||
|       sortSet.insert(temp); | ||||
|     } | ||||
|  | @ -25,67 +26,72 @@ namespace Mist { | |||
|     if (config->getInteger("seek")){ | ||||
|       uint64_t seekPoint = config->getInteger("seek"); | ||||
| 
 | ||||
|       while (!sortSet.empty() && sortSet.begin()->time < seekPoint) { | ||||
|       while (!sortSet.empty() && sortSet.begin()->time < seekPoint){ | ||||
|         keyPart temp; | ||||
|         temp.index = sortSet.begin()->index + 1; | ||||
|         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(); | ||||
|           temp.size = myMeta.tracks[temp.trackID].parts[temp.index].getSize();//bytesize of frame
 | ||||
|         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); | ||||
|         } | ||||
|         //remove highest keyPart
 | ||||
|         // remove highest keyPart
 | ||||
|         sortSet.erase(sortSet.begin()); | ||||
|       } | ||||
|       seek(seekPoint); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   void OutSanityCheck::init(Util::Config * cfg) { | ||||
|   void OutSanityCheck::init(Util::Config *cfg){ | ||||
|     Output::init(cfg); | ||||
|     capa["name"] = "SanityCheck"; | ||||
|     capa["desc"] = "Does sanity check on a stream"; | ||||
|     capa["codecs"][0u][0u].append("+*"); | ||||
|     cfg->addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); | ||||
|     cfg->addOption("seek", JSON::fromString("{\"arg\":\"string\",\"short\":\"S\",\"long\":\"seek\",\"help\":\"Time in ms to check from - by default start of stream\"}")); | ||||
|     cfg->addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":" | ||||
|                                                   "\"stream\",\"help\":\"The name of the stream " | ||||
|                                                   "that this connector will transmit.\"}")); | ||||
|     cfg->addOption( | ||||
|         "seek", JSON::fromString("{\"arg\":\"string\",\"short\":\"S\",\"long\":\"seek\",\"help\":" | ||||
|                                  "\"Time in ms to check from - by default start of stream\"}")); | ||||
|     cfg->addBasicConnectorOptions(capa); | ||||
|     config = cfg; | ||||
|   } | ||||
| 
 | ||||
|   void OutSanityCheck::sendNext() { | ||||
|     if ((unsigned long)thisPacket.getTrackId() != sortSet.begin()->trackID || thisPacket.getTime() != sortSet.begin()->time) { | ||||
|   void OutSanityCheck::sendNext(){ | ||||
|     if ((unsigned long)thisPacket.getTrackId() != sortSet.begin()->trackID || | ||||
|         thisPacket.getTime() != sortSet.begin()->time){ | ||||
|       while (packets.size()){ | ||||
|         std::cout << packets.front() << std::endl; | ||||
|         packets.pop_front(); | ||||
|       } | ||||
|       std::cout << "Input is inconsistent! Expected " << sortSet.begin()->trackID << ":" << sortSet.begin()->time << " but got " << thisPacket.getTrackId() << ":" << thisPacket.getTime() << " (part " << sortSet.begin()->index << " in " << myMeta.tracks[sortSet.begin()->trackID].codec << " track)" << std::endl; | ||||
|       std::cout << "Input is inconsistent! Expected " << sortSet.begin()->trackID << ":" | ||||
|                 << sortSet.begin()->time << " but got " << thisPacket.getTrackId() << ":" | ||||
|                 << thisPacket.getTime() << " (part " << sortSet.begin()->index << " in " | ||||
|                 << myMeta.tracks[sortSet.begin()->trackID].codec << " track)" << std::endl; | ||||
|       myConn.close(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     //Packet is normally sent here
 | ||||
|     // Packet is normally sent here
 | ||||
|     packets.push_back(thisPacket.toSummary()); | ||||
|     while (packets.size() > 10){packets.pop_front();} | ||||
| 
 | ||||
|     //keep track of where we are
 | ||||
|     if (!sortSet.empty()) { | ||||
|     // keep track of where we are
 | ||||
|     if (!sortSet.empty()){ | ||||
|       keyPart temp; | ||||
|       temp.index = sortSet.begin()->index + 1; | ||||
|       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
 | ||||
|       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
 | ||||
|         temp.size = myMeta.tracks[temp.trackID].parts[temp.index].getSize(); // bytesize of frame
 | ||||
|         sortSet.insert(temp); | ||||
|       } | ||||
|       //remove highest keyPart
 | ||||
|       // remove highest keyPart
 | ||||
|       sortSet.erase(sortSet.begin()); | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,43 +1,38 @@ | |||
| #include "output.h" | ||||
| #include <list> | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
|   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; | ||||
|           } | ||||
|           if (trackID == rhs.trackID){ | ||||
|             return index < rhs.index; | ||||
|           } | ||||
|         } | ||||
|         return false; | ||||
|   public: | ||||
|     bool operator<(const keyPart &rhs) const{ | ||||
|       if (time < rhs.time){return true;} | ||||
|       if (time == rhs.time){ | ||||
|         if (trackID < rhs.trackID){return true;} | ||||
|         if (trackID == rhs.trackID){return index < rhs.index;} | ||||
|       } | ||||
|       long unsigned int trackID; | ||||
|       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; | ||||
|       return false; | ||||
|     } | ||||
|     long unsigned int trackID; | ||||
|     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; | ||||
|   }; | ||||
|    | ||||
|   class OutSanityCheck : public Output { | ||||
|     public: | ||||
|       OutSanityCheck(Socket::Connection & conn); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void sendNext(); | ||||
|       static bool listenMode(){return false;} | ||||
|     protected: | ||||
|       std::deque<std::string> packets; | ||||
|       std::set <keyPart> sortSet;//needed for unfragmented MP4, remembers the order of keyparts
 | ||||
| 
 | ||||
|   class OutSanityCheck : public Output{ | ||||
|   public: | ||||
|     OutSanityCheck(Socket::Connection &conn); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void sendNext(); | ||||
|     static bool listenMode(){return false;} | ||||
| 
 | ||||
|   protected: | ||||
|     std::deque<std::string> packets; | ||||
|     std::set<keyPart> sortSet; // needed for unfragmented MP4, remembers the order of keyparts
 | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutSanityCheck mistOut; | ||||
|  |  | |||
|  | @ -1,14 +1,16 @@ | |||
| #include "output_srt.h" | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <iomanip> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/http_parser.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutProgressiveSRT::OutProgressiveSRT(Socket::Connection & conn) : HTTPOutput(conn){realTime = 0;} | ||||
|   OutProgressiveSRT::~OutProgressiveSRT() {} | ||||
|    | ||||
|   void OutProgressiveSRT::init(Util::Config * cfg){ | ||||
| namespace Mist{ | ||||
|   OutProgressiveSRT::OutProgressiveSRT(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|     realTime = 0; | ||||
|   } | ||||
|   OutProgressiveSRT::~OutProgressiveSRT(){} | ||||
| 
 | ||||
|   void OutProgressiveSRT::init(Util::Config *cfg){ | ||||
|     HTTPOutput::init(cfg); | ||||
|     capa["name"] = "SRT"; | ||||
|     capa["friendly"] = "SubRip/WebVTT over HTTP"; | ||||
|  | @ -25,49 +27,44 @@ namespace Mist { | |||
|     capa["methods"][1u]["priority"] = 9; | ||||
|     capa["methods"][1u]["url_rel"] = "/$.vtt"; | ||||
|   } | ||||
|    | ||||
| 
 | ||||
|   void OutProgressiveSRT::sendNext(){ | ||||
|     char * dataPointer = 0; | ||||
|     char *dataPointer = 0; | ||||
|     size_t len = 0; | ||||
|     thisPacket.getString("data", dataPointer, len); | ||||
| //    INFO_MSG("getting sub: %s", dataPointer);
 | ||||
|     //ignore empty subs
 | ||||
|     if (len == 0 || (len == 1 && dataPointer[0] == ' ')){ | ||||
|       return; | ||||
|     } | ||||
|     //    INFO_MSG("getting sub: %s", dataPointer);
 | ||||
|     // ignore empty subs
 | ||||
|     if (len == 0 || (len == 1 && dataPointer[0] == ' ')){return;} | ||||
|     std::stringstream tmp; | ||||
|     if(!webVTT) { | ||||
|       tmp << lastNum++ << std::endl; | ||||
|     } | ||||
|     if (!webVTT){tmp << lastNum++ << std::endl;} | ||||
|     long long unsigned int time = thisPacket.getTime(); | ||||
|   | ||||
|      | ||||
|     //filter subtitle in specific timespan
 | ||||
|     if(filter_from > 0 && time < filter_from){ | ||||
|     index++;    //when using seek, the index is lost.
 | ||||
| 
 | ||||
|     // filter subtitle in specific timespan
 | ||||
|     if (filter_from > 0 && time < filter_from){ | ||||
|       index++; // when using seek, the index is lost.
 | ||||
|       seek(filter_from); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if(filter_to > 0 && time > filter_to && filter_to > filter_from){ | ||||
|     if (filter_to > 0 && time > filter_to && filter_to > filter_from){ | ||||
|       config->is_active = false; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     char tmpBuf[50]; | ||||
|     int tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000); | ||||
|     int tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), | ||||
|                          ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000); | ||||
|     tmp.write(tmpBuf, tmpLen); | ||||
|     tmp << " --> "; | ||||
|     time += thisPacket.getInt("duration"); | ||||
|     if (time == thisPacket.getTime()){ | ||||
|       time += len * 75 + 800; | ||||
|     } | ||||
|     tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000); | ||||
|     if (time == thisPacket.getTime()){time += len * 75 + 800;} | ||||
|     tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), | ||||
|                      ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000); | ||||
|     tmp.write(tmpBuf, tmpLen); | ||||
|     tmp << std::endl; | ||||
|     myConn.SendNow(tmp.str()); | ||||
|     //prevent double newlines
 | ||||
|     if (dataPointer[len-1] == '\n'){--dataPointer;} | ||||
|     // prevent double newlines
 | ||||
|     if (dataPointer[len - 1] == '\n'){--dataPointer;} | ||||
|     myConn.SendNow(dataPointer, len); | ||||
|     myConn.SendNow("\n\n"); | ||||
|   } | ||||
|  | @ -81,9 +78,7 @@ namespace Mist { | |||
|     } | ||||
|     H.protocol = "HTTP/1.0"; | ||||
|     H.SendResponse("200", "OK", myConn); | ||||
|     if (webVTT){ | ||||
|       myConn.SendNow("WEBVTT\n\n"); | ||||
|     } | ||||
|     if (webVTT){myConn.SendNow("WEBVTT\n\n");} | ||||
|     sentHeader = true; | ||||
|   } | ||||
| 
 | ||||
|  | @ -94,21 +89,17 @@ namespace Mist { | |||
|       selectedTracks.clear(); | ||||
|       selectedTracks.insert(JSON::Value(H.GetVar("track")).asInt()); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     filter_from = 0; | ||||
|     filter_to = 0; | ||||
|     index = 0; | ||||
| 
 | ||||
|     if (H.GetVar("from") != ""){ | ||||
|       filter_from = JSON::Value(H.GetVar("from")).asInt(); | ||||
|     } | ||||
|     if (H.GetVar("to") != ""){ | ||||
|       filter_to = JSON::Value(H.GetVar("to")).asInt(); | ||||
|     } | ||||
|     if (H.GetVar("from") != ""){filter_from = JSON::Value(H.GetVar("from")).asInt();} | ||||
|     if (H.GetVar("to") != ""){filter_to = JSON::Value(H.GetVar("to")).asInt();} | ||||
| 
 | ||||
|     H.Clean(); | ||||
|     H.setCORSHeaders(); | ||||
|     if(method == "OPTIONS" || method == "HEAD"){ | ||||
|     if (method == "OPTIONS" || method == "HEAD"){ | ||||
|       if (webVTT){ | ||||
|         H.SetHeader("Content-Type", "text/vtt; charset=utf-8"); | ||||
|       }else{ | ||||
|  | @ -123,5 +114,4 @@ namespace Mist { | |||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,22 +1,22 @@ | |||
| #include "output_http.h" | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   class OutProgressiveSRT : public HTTPOutput{ | ||||
|   public: | ||||
|     OutProgressiveSRT(Socket::Connection &conn); | ||||
|     ~OutProgressiveSRT(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutProgressiveSRT : public HTTPOutput { | ||||
|     public: | ||||
|       OutProgressiveSRT(Socket::Connection & conn); | ||||
|       ~OutProgressiveSRT(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|     protected: | ||||
|       bool webVTT; | ||||
|       int lastNum; | ||||
|       uint32_t filter_from; | ||||
|       uint32_t filter_to; | ||||
|       uint32_t index; | ||||
|   protected: | ||||
|     bool webVTT; | ||||
|     int lastNum; | ||||
|     uint32_t filter_from; | ||||
|     uint32_t filter_to; | ||||
|     uint32_t index; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutProgressiveSRT mistOut; | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| #include "output_ts.h" | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/defines.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/url.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   OutTS::OutTS(Socket::Connection & conn) : TSOutput(conn){ | ||||
|     sendRepeatingHeaders = 500;//PAT/PMT every 500ms (DVB spec)
 | ||||
| namespace Mist{ | ||||
|   OutTS::OutTS(Socket::Connection &conn) : TSOutput(conn){ | ||||
|     sendRepeatingHeaders = 500; // PAT/PMT every 500ms (DVB spec)
 | ||||
|     streamName = config->getString("streamname"); | ||||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|  | @ -30,7 +30,7 @@ namespace Mist { | |||
|       udpSize = 5; | ||||
|       if (targetParams.count("tracks")){tracks = targetParams["tracks"];} | ||||
|       if (targetParams.count("pkts")){udpSize = atoi(targetParams["pkts"].c_str());} | ||||
|       packetBuffer.reserve(188*udpSize); | ||||
|       packetBuffer.reserve(188 * udpSize); | ||||
|       if (target.path.size()){ | ||||
|         if (!pushSock.bind(0, target.path)){ | ||||
|           disconnect(); | ||||
|  | @ -43,40 +43,38 @@ namespace Mist { | |||
|       pushSock.SetDestination(target.host, target.getPort()); | ||||
|     } | ||||
|     unsigned int currTrack = 0; | ||||
|     //loop over tracks, add any found track IDs to selectedTracks
 | ||||
|     // loop over tracks, add any found track IDs to selectedTracks
 | ||||
|     if (tracks != ""){ | ||||
|       selectedTracks.clear(); | ||||
|       for (unsigned int i = 0; i < tracks.size(); ++i){ | ||||
|         if (tracks[i] >= '0' && tracks[i] <= '9'){ | ||||
|           currTrack = currTrack*10 + (tracks[i] - '0'); | ||||
|           currTrack = currTrack * 10 + (tracks[i] - '0'); | ||||
|         }else{ | ||||
|           if (currTrack > 0){ | ||||
|             selectedTracks.insert(currTrack); | ||||
|           } | ||||
|           if (currTrack > 0){selectedTracks.insert(currTrack);} | ||||
|           currTrack = 0; | ||||
|         } | ||||
|       } | ||||
|       if (currTrack > 0){ | ||||
|         selectedTracks.insert(currTrack); | ||||
|       } | ||||
|       if (currTrack > 0){selectedTracks.insert(currTrack);} | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   OutTS::~OutTS() {} | ||||
|    | ||||
|   void OutTS::init(Util::Config * cfg){ | ||||
| 
 | ||||
|   OutTS::~OutTS(){} | ||||
| 
 | ||||
|   void OutTS::init(Util::Config *cfg){ | ||||
|     Output::init(cfg); | ||||
|     capa["name"] = "TS"; | ||||
|     capa["friendly"] = "TS over TCP"; | ||||
|     capa["desc"] = "Real time streaming in MPEG2/TS format over raw TCP"; | ||||
|     capa["deps"] = ""; | ||||
|     capa["required"]["streamname"]["name"] = "Source stream"; | ||||
|     capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports."; | ||||
|     capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add " | ||||
|                                              "this protocol multiple times using different ports."; | ||||
|     capa["required"]["streamname"]["type"] = "str"; | ||||
|     capa["required"]["streamname"]["option"] = "--stream"; | ||||
|     capa["required"]["streamname"]["short"] = "s"; | ||||
|     capa["optional"]["tracks"]["name"] = "Tracks"; | ||||
|     capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces"; | ||||
|     capa["optional"]["tracks"]["help"] = | ||||
|         "The track IDs of the stream that this connector will transmit separated by spaces"; | ||||
|     capa["optional"]["tracks"]["type"] = "str"; | ||||
|     capa["optional"]["tracks"]["option"] = "--tracks"; | ||||
|     capa["optional"]["tracks"]["short"] = "t"; | ||||
|  | @ -91,7 +89,7 @@ namespace Mist { | |||
|     cfg->addConnectorOptions(8888, capa); | ||||
|     config = cfg; | ||||
|     capa["push_urls"].append("tsudp://*"); | ||||
|      | ||||
| 
 | ||||
|     JSON::Value opt; | ||||
|     opt["arg"] = "string"; | ||||
|     opt["default"] = ""; | ||||
|  | @ -101,7 +99,7 @@ namespace Mist { | |||
|   } | ||||
| 
 | ||||
|   void OutTS::initialSeek(){ | ||||
|     //Adds passthrough support to the regular initialSeek function
 | ||||
|     // Adds passthrough support to the regular initialSeek function
 | ||||
|     if (targetParams.count("passthrough")){ | ||||
|       selectedTracks.clear(); | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); | ||||
|  | @ -112,7 +110,7 @@ namespace Mist { | |||
|     Output::initialSeek(); | ||||
|   } | ||||
| 
 | ||||
|   void OutTS::sendTS(const char * tsData, unsigned int len){ | ||||
|   void OutTS::sendTS(const char *tsData, unsigned int len){ | ||||
|     if (pushOut){ | ||||
|       static int curFilled = 0; | ||||
|       if (curFilled == udpSize){ | ||||
|  | @ -123,14 +121,12 @@ namespace Mist { | |||
|         curFilled = 0; | ||||
|       } | ||||
|       packetBuffer.append(tsData, len); | ||||
|       curFilled ++; | ||||
|       curFilled++; | ||||
|     }else{ | ||||
|       myConn.SendNow(tsData, len); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool OutTS::listenMode(){ | ||||
|     return !(config->getString("target").size()); | ||||
|   } | ||||
|   bool OutTS::listenMode(){return !(config->getString("target").size());} | ||||
| 
 | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,20 +1,21 @@ | |||
| #include "output_ts_base.h" | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
|   class OutTS : public TSOutput{ | ||||
|     public: | ||||
|       OutTS(Socket::Connection & conn); | ||||
|       ~OutTS(); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void sendTS(const char * tsData, unsigned int len=188); | ||||
|       static bool listenMode(); | ||||
|       void initialSeek(); | ||||
|     private: | ||||
|       unsigned int udpSize; | ||||
|       bool pushOut; | ||||
|       std::string packetBuffer; | ||||
|       Socket::UDPConnection pushSock; | ||||
|   public: | ||||
|     OutTS(Socket::Connection &conn); | ||||
|     ~OutTS(); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void sendTS(const char *tsData, unsigned int len = 188); | ||||
|     static bool listenMode(); | ||||
|     void initialSeek(); | ||||
| 
 | ||||
|   private: | ||||
|     unsigned int udpSize; | ||||
|     bool pushOut; | ||||
|     std::string packetBuffer; | ||||
|     Socket::UDPConnection pushSock; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutTS mistOut; | ||||
|  |  | |||
|  | @ -1,19 +1,20 @@ | |||
| #include "output_ts_base.h" | ||||
| #include <mist/bitfields.h> | ||||
| 
 | ||||
| namespace Mist { | ||||
|   TSOutput::TSOutput(Socket::Connection & conn) : TS_BASECLASS(conn){ | ||||
|     packCounter=0; | ||||
| namespace Mist{ | ||||
|   TSOutput::TSOutput(Socket::Connection &conn) : TS_BASECLASS(conn){ | ||||
|     packCounter = 0; | ||||
|     ts_from = 0; | ||||
|     setBlocking(true); | ||||
|     sendRepeatingHeaders = 0; | ||||
|     lastHeaderTime = 0; | ||||
|   } | ||||
| 
 | ||||
|   void TSOutput::fillPacket(char const * data, size_t dataLen, bool & firstPack, bool video, bool keyframe, uint32_t pkgPid, int & contPkg){ | ||||
|     do { | ||||
|   void TSOutput::fillPacket(char const *data, size_t dataLen, bool &firstPack, bool video, | ||||
|                             bool keyframe, uint32_t pkgPid, int &contPkg){ | ||||
|     do{ | ||||
|       if (!packData.getBytesFree()){ | ||||
|         if ( (sendRepeatingHeaders && thisPacket.getTime() - lastHeaderTime > sendRepeatingHeaders) || !packCounter){ | ||||
|         if ((sendRepeatingHeaders && thisPacket.getTime() - lastHeaderTime > sendRepeatingHeaders) || !packCounter){ | ||||
|           lastHeaderTime = thisPacket.getTime(); | ||||
|           TS::Packet tmpPack; | ||||
|           tmpPack.FromPointer(TS::PAT); | ||||
|  | @ -24,12 +25,12 @@ namespace Mist { | |||
|           packCounter += 3; | ||||
|         } | ||||
|         sendTS(packData.checkAndGetBuffer()); | ||||
|         packCounter ++; | ||||
|         packCounter++; | ||||
|         packData.clear(); | ||||
|       } | ||||
|        | ||||
| 
 | ||||
|       if (!dataLen){return;} | ||||
|        | ||||
| 
 | ||||
|       if (packData.getBytesFree() == 184){ | ||||
|         packData.clear(); | ||||
|         packData.setPID(pkgPid); | ||||
|  | @ -40,45 +41,43 @@ namespace Mist { | |||
|             if (keyframe){ | ||||
|               packData.setRandomAccess(true); | ||||
|               packData.setESPriority(true); | ||||
|             }       | ||||
|             packData.setPCR(thisPacket.getTime() * 27000);       | ||||
|             } | ||||
|             packData.setPCR(thisPacket.getTime() * 27000); | ||||
|           } | ||||
|           firstPack = false; | ||||
|         } | ||||
|       } | ||||
|        | ||||
| 
 | ||||
|       size_t tmp = packData.fillFree(data, dataLen); | ||||
|       data += tmp; | ||||
|       dataLen -= tmp; | ||||
|     } while(dataLen); | ||||
|     }while (dataLen); | ||||
|   } | ||||
| 
 | ||||
|   void TSOutput::sendNext(){ | ||||
|     //Get ready some data to speed up accesses
 | ||||
|     // Get ready some data to speed up accesses
 | ||||
|     uint32_t trackId = thisPacket.getTrackId(); | ||||
|     DTSC::Track & Trk = myMeta.tracks[trackId]; | ||||
|     bool & firstPack = first[trackId]; | ||||
|     DTSC::Track &Trk = myMeta.tracks[trackId]; | ||||
|     bool &firstPack = first[trackId]; | ||||
|     uint32_t pkgPid = 255 + trackId; | ||||
|     int & contPkg = contCounters[pkgPid]; | ||||
|     int &contPkg = contCounters[pkgPid]; | ||||
|     uint64_t packTime = thisPacket.getTime(); | ||||
|     bool video = (Trk.type == "video"); | ||||
|     bool keyframe = thisPacket.getInt("keyframe"); | ||||
|     firstPack = true; | ||||
| 
 | ||||
|     char * dataPointer = 0; | ||||
|     char *dataPointer = 0; | ||||
|     size_t tmpDataLen = 0; | ||||
|     thisPacket.getString("data", dataPointer, tmpDataLen); //data
 | ||||
|     thisPacket.getString("data", dataPointer, tmpDataLen); // data
 | ||||
|     uint64_t dataLen = tmpDataLen; | ||||
|     packTime *= 90; | ||||
|     std::string bs; | ||||
|     //prepare bufferstring    
 | ||||
|     // prepare bufferstring
 | ||||
|     if (video){ | ||||
|       if (Trk.codec == "H264" || Trk.codec == "HEVC"){ | ||||
|         unsigned int extraSize = 0;       | ||||
|         //dataPointer[4] & 0x1f is used to check if this should be done later: fillPacket("\000\000\000\001\011\360", 6);
 | ||||
|         if (Trk.codec == "H264" && (dataPointer[4] & 0x1f) != 0x09){ | ||||
|           extraSize += 6; | ||||
|         } | ||||
|         unsigned int extraSize = 0; | ||||
|         // dataPointer[4] & 0x1f is used to check if this should be done later: fillPacket("\000\000\000\001\011\360", 6);
 | ||||
|         if (Trk.codec == "H264" && (dataPointer[4] & 0x1f) != 0x09){extraSize += 6;} | ||||
|         if (keyframe){ | ||||
|           if (Trk.codec == "H264"){ | ||||
|             MP4::AVCC avccbox; | ||||
|  | @ -95,9 +94,9 @@ namespace Mist { | |||
|           } | ||||
|           /*LTS-END*/ | ||||
|         } | ||||
|          | ||||
|         unsigned int watKunnenWeIn1Ding = 65490-13; | ||||
|         unsigned int splitCount = (dataLen+extraSize) / watKunnenWeIn1Ding; | ||||
| 
 | ||||
|         unsigned int watKunnenWeIn1Ding = 65490 - 13; | ||||
|         unsigned int splitCount = (dataLen + extraSize) / watKunnenWeIn1Ding; | ||||
|         unsigned int currPack = 0; | ||||
|         uint64_t ThisNaluSize = 0; | ||||
|         unsigned int i = 0; | ||||
|  | @ -106,11 +105,13 @@ namespace Mist { | |||
| 
 | ||||
|         while (currPack <= splitCount){ | ||||
|           unsigned int alreadySent = 0; | ||||
|           bs = TS::Packet::getPESVideoLeadIn((currPack != splitCount ? watKunnenWeIn1Ding : dataLen+extraSize - currPack*watKunnenWeIn1Ding), packTime, offset, !currPack, Trk.bps); | ||||
|           bs = TS::Packet::getPESVideoLeadIn( | ||||
|               (currPack != splitCount ? watKunnenWeIn1Ding : dataLen + extraSize - currPack * watKunnenWeIn1Ding), | ||||
|               packTime, offset, !currPack, Trk.bps); | ||||
|           fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||
|           if (!currPack){ | ||||
|             if (Trk.codec == "H264" && (dataPointer[4] & 0x1f) != 0x09){ | ||||
|               //End of previous nal unit, if not already present
 | ||||
|               // End of previous nal unit, if not already present
 | ||||
|               fillPacket("\000\000\000\001\011\360", 6, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|               alreadySent += 6; | ||||
|             } | ||||
|  | @ -135,7 +136,7 @@ namespace Mist { | |||
|           } | ||||
|           while (i + 4 < (unsigned int)dataLen){ | ||||
|             if (nalLead){ | ||||
|               fillPacket("\000\000\000\001"+4-nalLead,nalLead, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|               fillPacket("\000\000\000\001" + 4 - nalLead, nalLead, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|               i += nalLead; | ||||
|               alreadySent += nalLead; | ||||
|               nalLead = 0; | ||||
|  | @ -143,31 +144,34 @@ namespace Mist { | |||
|             if (!ThisNaluSize){ | ||||
|               ThisNaluSize = Bit::btohl(dataPointer + i); | ||||
|               if (ThisNaluSize + i + 4 > dataLen){ | ||||
|                 WARN_MSG("Too big NALU detected (%" PRIu64 " > %" PRIu64 ") - skipping!", ThisNaluSize + i + 4, dataLen); | ||||
|                 WARN_MSG("Too big NALU detected (%" PRIu64 " > %" PRIu64 ") - skipping!", | ||||
|                          ThisNaluSize + i + 4, dataLen); | ||||
|                 break; | ||||
|               } | ||||
|               if (alreadySent + 4 > watKunnenWeIn1Ding){ | ||||
|                 nalLead = 4 - (watKunnenWeIn1Ding-alreadySent); | ||||
|                 fillPacket("\000\000\000\001",watKunnenWeIn1Ding-alreadySent, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|                 i += watKunnenWeIn1Ding-alreadySent; | ||||
|                 alreadySent += watKunnenWeIn1Ding-alreadySent; | ||||
|                 nalLead = 4 - (watKunnenWeIn1Ding - alreadySent); | ||||
|                 fillPacket("\000\000\000\001", watKunnenWeIn1Ding - alreadySent, firstPack, video, | ||||
|                            keyframe, pkgPid, contPkg); | ||||
|                 i += watKunnenWeIn1Ding - alreadySent; | ||||
|                 alreadySent += watKunnenWeIn1Ding - alreadySent; | ||||
|               }else{ | ||||
|                 fillPacket("\000\000\000\001",4, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|                 fillPacket("\000\000\000\001", 4, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|                 alreadySent += 4; | ||||
|                 i += 4; | ||||
|               } | ||||
|             } | ||||
|             if (alreadySent + ThisNaluSize > watKunnenWeIn1Ding){ | ||||
|               fillPacket(dataPointer+i,watKunnenWeIn1Ding-alreadySent, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|               i += watKunnenWeIn1Ding-alreadySent; | ||||
|               ThisNaluSize -= watKunnenWeIn1Ding-alreadySent; | ||||
|               alreadySent += watKunnenWeIn1Ding-alreadySent; | ||||
|               fillPacket(dataPointer + i, watKunnenWeIn1Ding - alreadySent, firstPack, video, | ||||
|                          keyframe, pkgPid, contPkg); | ||||
|               i += watKunnenWeIn1Ding - alreadySent; | ||||
|               ThisNaluSize -= watKunnenWeIn1Ding - alreadySent; | ||||
|               alreadySent += watKunnenWeIn1Ding - alreadySent; | ||||
|             }else{ | ||||
|               fillPacket(dataPointer+i,ThisNaluSize, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|               fillPacket(dataPointer + i, ThisNaluSize, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|               alreadySent += ThisNaluSize; | ||||
|               i += ThisNaluSize; | ||||
|               ThisNaluSize = 0; | ||||
|             }           | ||||
|             } | ||||
|             if (alreadySent == watKunnenWeIn1Ding){ | ||||
|               packData.addStuffing(); | ||||
|               fillPacket(0, 0, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|  | @ -188,29 +192,29 @@ namespace Mist { | |||
|       long unsigned int tempLen = dataLen; | ||||
|       if (Trk.codec == "AAC"){ | ||||
|         tempLen += 7; | ||||
|         //Make sure TS timestamp is sample-aligned, if possible
 | ||||
|         // Make sure TS timestamp is sample-aligned, if possible
 | ||||
|         uint32_t freq = Trk.rate; | ||||
|         if (freq){ | ||||
|           uint64_t aacSamples = (packTime/90) * freq / 1000; | ||||
|           //round to nearest packet, assuming all 1024 samples (probably wrong, but meh)
 | ||||
|           uint64_t aacSamples = (packTime / 90) * freq / 1000; | ||||
|           // round to nearest packet, assuming all 1024 samples (probably wrong, but meh)
 | ||||
|           aacSamples += 512; | ||||
|           aacSamples /= 1024; | ||||
|           aacSamples *= 1024; | ||||
|           //Get closest 90kHz clock time to perfect sample alignment
 | ||||
|           // Get closest 90kHz clock time to perfect sample alignment
 | ||||
|           packTime = aacSamples * 90000 / freq; | ||||
|         } | ||||
|       } | ||||
|       bs = TS::Packet::getPESAudioLeadIn(tempLen, packTime, Trk.bps); | ||||
|       fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||
|       if (Trk.codec == "AAC"){         | ||||
|         bs = TS::getAudioHeader(dataLen, Trk.init);       | ||||
|       if (Trk.codec == "AAC"){ | ||||
|         bs = TS::getAudioHeader(dataLen, Trk.init); | ||||
|         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||
|       } | ||||
|       fillPacket(dataPointer,dataLen, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|       fillPacket(dataPointer, dataLen, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|     } | ||||
|     if (packData.getBytesFree() < 184){ | ||||
|       packData.addStuffing(); | ||||
|       fillPacket(0, 0, firstPack, video, keyframe, pkgPid, contPkg); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| #include <mist/defines.h> | ||||
| #include "output.h" | ||||
| #include "output_http.h" | ||||
| #include <mist/defines.h> | ||||
| #include <mist/mp4_generic.h> | ||||
| #include <mist/ts_packet.h> | ||||
| 
 | ||||
|  | @ -8,30 +8,32 @@ | |||
| #define TS_BASECLASS Output | ||||
| #endif | ||||
| 
 | ||||
| namespace Mist { | ||||
| namespace Mist{ | ||||
| 
 | ||||
|   class TSOutput : public TS_BASECLASS { | ||||
|     public: | ||||
|       TSOutput(Socket::Connection & conn); | ||||
|       virtual ~TSOutput(){}; | ||||
|       virtual void sendNext();       | ||||
|       virtual void sendTS(const char * tsData, unsigned int len=188){}; | ||||
|       void fillPacket(char const * data, size_t dataLen, bool & firstPack, bool video, bool keyframe, uint32_t pkgPid, int & contPkg);     | ||||
|       virtual void sendHeader(){ | ||||
|         sentHeader = true; | ||||
|         packCounter = 0; | ||||
|       } | ||||
|     protected: | ||||
|       virtual bool inlineRestartCapable() const{return true;} | ||||
|       std::map<unsigned int, bool> first; | ||||
|       std::map<unsigned int, int> contCounters; | ||||
|       int contPAT; | ||||
|       int contPMT; | ||||
|       int contSDT; | ||||
|       unsigned int packCounter; | ||||
|       TS::Packet packData; | ||||
|       uint64_t sendRepeatingHeaders; ///< Amount of ms between PAT/PMT. Zero means do not repeat.
 | ||||
|       uint64_t lastHeaderTime; ///< Timestamp last PAT/PMT were sent.
 | ||||
|       uint64_t ts_from; ///< Starting time to subtract from timestamps
 | ||||
|   class TSOutput : public TS_BASECLASS{ | ||||
|   public: | ||||
|     TSOutput(Socket::Connection &conn); | ||||
|     virtual ~TSOutput(){}; | ||||
|     virtual void sendNext(); | ||||
|     virtual void sendTS(const char *tsData, unsigned int len = 188){}; | ||||
|     void fillPacket(char const *data, size_t dataLen, bool &firstPack, bool video, bool keyframe, | ||||
|                     uint32_t pkgPid, int &contPkg); | ||||
|     virtual void sendHeader(){ | ||||
|       sentHeader = true; | ||||
|       packCounter = 0; | ||||
|     } | ||||
| 
 | ||||
|   protected: | ||||
|     virtual bool inlineRestartCapable() const{return true;} | ||||
|     std::map<unsigned int, bool> first; | ||||
|     std::map<unsigned int, int> contCounters; | ||||
|     int contPAT; | ||||
|     int contPMT; | ||||
|     int contSDT; | ||||
|     unsigned int packCounter; | ||||
|     TS::Packet packData; | ||||
|     uint64_t sendRepeatingHeaders; ///< Amount of ms between PAT/PMT. Zero means do not repeat.
 | ||||
|     uint64_t lastHeaderTime;       ///< Timestamp last PAT/PMT were sent.
 | ||||
|     uint64_t ts_from;              ///< Starting time to subtract from timestamps
 | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -37,22 +37,22 @@ namespace Mist{ | |||
|     size_t len = 0; | ||||
|     thisPacket.getString("data", dataPointer, len); | ||||
| 
 | ||||
|     //PCM must be converted to little-endian if > 8 bits per sample
 | ||||
|     // PCM must be converted to little-endian if > 8 bits per sample
 | ||||
|     static Util::ResizeablePointer swappy; | ||||
|     DTSC::Track & trk = myMeta.tracks[thisPacket.getTrackId()]; | ||||
|     DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()]; | ||||
|     if (trk.codec == "PCM"){ | ||||
|       if (trk.size > 8 && swappy.allocate(len)){ | ||||
|         if (trk.size == 16){ | ||||
|           for (uint32_t i = 0; i < len; i+=2){ | ||||
|             swappy[i] = dataPointer[i+1]; | ||||
|             swappy[i+1] = dataPointer[i]; | ||||
|           for (uint32_t i = 0; i < len; i += 2){ | ||||
|             swappy[i] = dataPointer[i + 1]; | ||||
|             swappy[i + 1] = dataPointer[i]; | ||||
|           } | ||||
|         } | ||||
|         if (trk.size == 24){ | ||||
|           for (uint32_t i = 0; i < len; i+=3){ | ||||
|             swappy[i] = dataPointer[i+2]; | ||||
|             swappy[i+1] = dataPointer[i+1]; | ||||
|             swappy[i+2] = dataPointer[i]; | ||||
|           for (uint32_t i = 0; i < len; i += 3){ | ||||
|             swappy[i] = dataPointer[i + 2]; | ||||
|             swappy[i + 1] = dataPointer[i + 1]; | ||||
|             swappy[i + 2] = dataPointer[i]; | ||||
|           } | ||||
|         } | ||||
|         dataPointer = swappy; | ||||
|  | @ -78,8 +78,7 @@ namespace Mist{ | |||
|     uint32_t total_data = 0xFFFFFFFFul - 80; | ||||
|     if (!myMeta.live){ | ||||
|       total_data = 0; | ||||
|       for (std::deque<unsigned long>::iterator it = Trk.keySizes.begin(); it != Trk.keySizes.end(); | ||||
|            ++it){ | ||||
|       for (std::deque<unsigned long>::iterator it = Trk.keySizes.begin(); it != Trk.keySizes.end(); ++it){ | ||||
|         total_data += *it; | ||||
|       } | ||||
|     } | ||||
|  | @ -95,7 +94,7 @@ namespace Mist{ | |||
|     myConn.SendNow(RIFF::fmt::generate(fmt, Trk.channels, Trk.rate, Trk.bps, | ||||
|                                        Trk.channels * (Trk.size << 3), Trk.size)); | ||||
|     // Send sample count per channel
 | ||||
|     if (fmt != 1){//Not required for PCM
 | ||||
|     if (fmt != 1){// Not required for PCM
 | ||||
|       if (!myMeta.live){ | ||||
|         myConn.SendNow(RIFF::fact::generate(((Trk.lastms - Trk.firstms) * Trk.rate) / 1000)); | ||||
|       }else{ | ||||
|  | @ -127,5 +126,4 @@ namespace Mist{ | |||
|     parseData = true; | ||||
|     wantRequest = false; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }// namespace Mist
 | ||||
|  |  | |||
|  | @ -1,21 +1,21 @@ | |||
| #include "output_http.h" | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   class OutWAV : public HTTPOutput{ | ||||
|   public: | ||||
|     OutWAV(Socket::Connection &conn); | ||||
|     static void init(Util::Config *cfg); | ||||
|     void onHTTP(); | ||||
|     void sendNext(); | ||||
|     void sendHeader(); | ||||
| 
 | ||||
| namespace Mist { | ||||
|   class OutWAV : public HTTPOutput { | ||||
|     public: | ||||
|       OutWAV(Socket::Connection & conn); | ||||
|       static void init(Util::Config * cfg); | ||||
|       void onHTTP(); | ||||
|       void sendNext(); | ||||
|       void sendHeader(); | ||||
|     protected: | ||||
|       virtual bool inlineRestartCapable() const{return true;} | ||||
|     private: | ||||
|       bool isRecording(); | ||||
|       bool isFileTarget(){return isRecording();} | ||||
|   protected: | ||||
|     virtual bool inlineRestartCapable() const{return true;} | ||||
| 
 | ||||
|   private: | ||||
|     bool isRecording(); | ||||
|     bool isFileTarget(){return isRecording();} | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutWAV mistOut; | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -2,21 +2,21 @@ | |||
| 
 | ||||
|   SOME NOTES ON MIST | ||||
| 
 | ||||
|      - When a user wants to start pushing video into Mist we need to  | ||||
|      - When a user wants to start pushing video into Mist we need to | ||||
|        check if the user is actually allowed to do this. When the user | ||||
|        is allowed to push we have to call the function `allowPush("")`.  | ||||
|    | ||||
|   SIGNALING  | ||||
|      | ||||
|        is allowed to push we have to call the function `allowPush("")`. | ||||
| 
 | ||||
|   SIGNALING | ||||
| 
 | ||||
|      1. Client sends the offer: | ||||
|       | ||||
| 
 | ||||
|           { | ||||
|             type: "offer_sdp", | ||||
|             offer_sdp: <the-client-offer-sdp>, | ||||
|           }  | ||||
|       | ||||
|           } | ||||
| 
 | ||||
|         Server responds with: | ||||
|       | ||||
| 
 | ||||
|           SUCCESS: | ||||
|           { | ||||
|             type: "on_answer_sdp", | ||||
|  | @ -35,7 +35,7 @@ | |||
|           { | ||||
|             type: "video_bitrate" | ||||
|             video_bitrate: 600000 | ||||
|           }  | ||||
|           } | ||||
| 
 | ||||
|         Server responds with: | ||||
| 
 | ||||
|  | @ -44,82 +44,80 @@ | |||
|             type: "on_video_bitrate" | ||||
|             result: true | ||||
|           } | ||||
|    | ||||
| 
 | ||||
|           ERROR: | ||||
|           { | ||||
|              type: "on_video_bitrate" | ||||
|              result: false | ||||
|           } | ||||
|   | ||||
| 
 | ||||
|  */ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "output.h" | ||||
| #include "output_http.h" | ||||
| #include <mist/certificate.h> | ||||
| #include <mist/dtls_srtp_handshake.h> | ||||
| #include <mist/h264.h> | ||||
| #include <mist/http_parser.h> | ||||
| #include <mist/rtp_fec.h> | ||||
| #include <mist/sdp_media.h> | ||||
| #include <mist/socket.h> | ||||
| #include <mist/srtp.h> | ||||
| #include <mist/stun.h> | ||||
| #include <mist/tinythread.h> | ||||
| #include <mist/websocket.h> | ||||
| #include <mist/certificate.h>  | ||||
| #include <mist/stun.h>        | ||||
| #include <mist/dtls_srtp_handshake.h> | ||||
| #include <mist/srtp.h>         | ||||
| 
 | ||||
| #define NACK_BUFFER_SIZE 1024 | ||||
| 
 | ||||
| #if defined(WEBRTC_PCAP) | ||||
| #  include <mist/pcap.h> | ||||
| #include <mist/pcap.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace Mist { | ||||
|    | ||||
| namespace Mist{ | ||||
| 
 | ||||
|   /* ------------------------------------------------ */ | ||||
| 
 | ||||
|   class nackBuffer{ | ||||
|     public: | ||||
|       bool isBuffered(uint16_t seq){ | ||||
|         if (!bufs[seq%NACK_BUFFER_SIZE].size()){return false;} | ||||
|         RTP::Packet tmpPkt(bufs[seq%NACK_BUFFER_SIZE], bufs[seq%NACK_BUFFER_SIZE].size()); | ||||
|         return (tmpPkt.getSequence() == seq); | ||||
|       } | ||||
|       const char * getData(uint16_t seq){ | ||||
|         return bufs[seq % NACK_BUFFER_SIZE]; | ||||
|       } | ||||
|       size_t getSize(uint16_t seq){ | ||||
|         return bufs[seq % NACK_BUFFER_SIZE].size(); | ||||
|       } | ||||
|       void assign(uint16_t seq, const char * p, size_t s){ | ||||
|         bufs[seq % NACK_BUFFER_SIZE].assign(p, s); | ||||
|       } | ||||
|     private: | ||||
|       Util::ResizeablePointer bufs[NACK_BUFFER_SIZE]; | ||||
|   public: | ||||
|     bool isBuffered(uint16_t seq){ | ||||
|       if (!bufs[seq % NACK_BUFFER_SIZE].size()){return false;} | ||||
|       RTP::Packet tmpPkt(bufs[seq % NACK_BUFFER_SIZE], bufs[seq % NACK_BUFFER_SIZE].size()); | ||||
|       return (tmpPkt.getSequence() == seq); | ||||
|     } | ||||
|     const char *getData(uint16_t seq){return bufs[seq % NACK_BUFFER_SIZE];} | ||||
|     size_t getSize(uint16_t seq){return bufs[seq % NACK_BUFFER_SIZE].size();} | ||||
|     void assign(uint16_t seq, const char *p, size_t s){ | ||||
|       bufs[seq % NACK_BUFFER_SIZE].assign(p, s); | ||||
|     } | ||||
| 
 | ||||
|   private: | ||||
|     Util::ResizeablePointer bufs[NACK_BUFFER_SIZE]; | ||||
|   }; | ||||
| 
 | ||||
|   class WebRTCTrack { | ||||
|   class WebRTCTrack{ | ||||
|   public: | ||||
|     WebRTCTrack();                       ///< Initializes to some defaults.
 | ||||
|      | ||||
|     WebRTCTrack(); ///< Initializes to some defaults.
 | ||||
| 
 | ||||
|   public: | ||||
|     RTP::toDTSC rtpToDTSC;               ///< Converts RTP packets into DTSC packets. 
 | ||||
|     RTP::FECSorter sorter;                  ///< Takes care of sorting the received RTP packet and keeps track of some statistics. Will call a callback whenever a packet can be used. (e.g. not lost, in correct order).
 | ||||
|     RTP::Packet rtpPacketizer;           ///< Used when we're sending RTP data back to the other peer. 
 | ||||
|     uint64_t payloadType;                ///< The payload type that was extracted from the `m=` media line in the SDP.  
 | ||||
|     std::string localIcePwd;  | ||||
|     RTP::toDTSC rtpToDTSC; ///< Converts RTP packets into DTSC packets.
 | ||||
|     RTP::FECSorter sorter; ///< Takes care of sorting the received RTP packet and keeps track of some
 | ||||
|                            ///< statistics. Will call a callback whenever a packet can be used. (e.g. not lost, in correct order).
 | ||||
|     RTP::Packet rtpPacketizer; ///< Used when we're sending RTP data back to the other peer.
 | ||||
|     uint64_t payloadType; ///< The payload type that was extracted from the `m=` media line in the SDP.
 | ||||
|     std::string localIcePwd; | ||||
|     std::string localIceUFrag; | ||||
|     uint32_t SSRC;                       ///< The SSRC of the RTP packets.
 | ||||
|     uint32_t timestampMultiplier;        ///< Used for outgoing streams to convert the DTSC timestamps into RTP timestamps.
 | ||||
|     uint8_t ULPFECPayloadType;           ///< When we've enabled FEC for a video stream this holds the payload type that is used to distinguish between ordinary video RTP packets and FEC packets.
 | ||||
|     uint8_t REDPayloadType;              ///< When using RED and ULPFEC this holds the payload type of the RED stream.
 | ||||
|     uint8_t RTXPayloadType;              ///< The retransmission payload type when we use RTX (retransmission with separate SSRC/payload type)
 | ||||
|     uint16_t prevReceivedSequenceNumber; ///< The previously received sequence number. This is used to NACK packets when we loose one. 
 | ||||
|     uint32_t SSRC; ///< The SSRC of the RTP packets.
 | ||||
|     uint32_t timestampMultiplier; ///< Used for outgoing streams to convert the DTSC timestamps into RTP timestamps.
 | ||||
|     uint8_t ULPFECPayloadType; ///< When we've enabled FEC for a video stream this holds the payload type that is used to distinguish between ordinary video RTP packets and FEC packets.
 | ||||
|     uint8_t REDPayloadType; ///< When using RED and ULPFEC this holds the payload type of the RED stream.
 | ||||
|     uint8_t RTXPayloadType; ///< The retransmission payload type when we use RTX (retransmission with separate SSRC/payload type)
 | ||||
|     uint16_t prevReceivedSequenceNumber; ///< The previously received sequence number. This is used to NACK packets when we loose one.
 | ||||
|   }; | ||||
| 
 | ||||
|   /* ------------------------------------------------ */ | ||||
|    | ||||
|   class OutWebRTC : public HTTPOutput { | ||||
| 
 | ||||
|   class OutWebRTC : public HTTPOutput{ | ||||
|   public: | ||||
|     OutWebRTC(Socket::Connection &myConn); | ||||
|     ~OutWebRTC(); | ||||
|  | @ -132,66 +130,70 @@ namespace Mist { | |||
|     bool onFinish(); | ||||
|     bool doesWebsockets(){return true;} | ||||
|     void handleWebRTCInputOutputFromThread(); | ||||
|     int onDTLSHandshakeWantsToWrite(const uint8_t* data, int* nbytes); | ||||
|     int onDTLSHandshakeWantsToWrite(const uint8_t *data, int *nbytes); | ||||
|     void onRTPSorterHasPacket(const uint64_t trackID, const RTP::Packet &pkt); | ||||
|     void onDTSCConverterHasPacket(const DTSC::Packet& pkt); | ||||
|     void onDTSCConverterHasPacket(const DTSC::Packet &pkt); | ||||
|     void onDTSCConverterHasInitData(const uint64_t trackID, const std::string &initData); | ||||
|     void onRTPPacketizerHasRTPPacket(char* data, uint32_t nbytes); | ||||
|     void onRTPPacketizerHasRTCPPacket(char* data, uint32_t nbytes); | ||||
|     void onRTPPacketizerHasRTPPacket(char *data, uint32_t nbytes); | ||||
|     void onRTPPacketizerHasRTCPPacket(char *data, uint32_t nbytes); | ||||
| 
 | ||||
|   private: | ||||
|     std::string externalAddr; | ||||
|     void ackNACK(uint32_t SSRC, uint16_t seq); | ||||
|     bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read some data, othewise false. 
 | ||||
|     bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read some data, othewise false.
 | ||||
|     void handleReceivedSTUNPacket(); | ||||
|     void handleReceivedDTLSPacket(); | ||||
|     void handleReceivedRTPOrRTCPPacket(); | ||||
|     bool handleSignalingCommandRemoteOfferForInput(SDP::Session &sdpSession); | ||||
|     bool handleSignalingCommandRemoteOfferForOutput(SDP::Session &sdpSession); | ||||
|     void sendSignalingError(const std::string& commandType, const std::string& errorMessage); | ||||
|      | ||||
|     bool createWebRTCTrackFromAnswer(const SDP::Media& mediaAnswer, const SDP::MediaFormat& formatAnswer, WebRTCTrack& result); | ||||
|     void sendSignalingError(const std::string &commandType, const std::string &errorMessage); | ||||
| 
 | ||||
|     bool createWebRTCTrackFromAnswer(const SDP::Media &mediaAnswer, | ||||
|                                      const SDP::MediaFormat &formatAnswer, WebRTCTrack &result); | ||||
|     void sendRTCPFeedbackREMB(const WebRTCTrack &rtcTrack); | ||||
|     void sendRTCPFeedbackPLI(const WebRTCTrack &rtcTrack); ///< Picture Los Indication: request keyframe.
 | ||||
|     void sendRTCPFeedbackRR(WebRTCTrack &rtcTrack); | ||||
|     void sendRTCPFeedbackNACK(const WebRTCTrack &rtcTrack, uint16_t missingSequenceNumber); ///< Notify sender that we're missing a sequence number.
 | ||||
|     void sendSPSPPS(DTSC::Track& dtscTrack, WebRTCTrack& rtcTrack);///< When we're streaming H264 to e.g. the browser we inject the PPS and SPS nals.
 | ||||
|     void sendRTCPFeedbackNACK(const WebRTCTrack &rtcTrack, | ||||
|                               uint16_t missingSequenceNumber); ///< Notify sender that we're missing a sequence number.
 | ||||
|     void sendSPSPPS(DTSC::Track &dtscTrack, WebRTCTrack &rtcTrack); ///< When we're streaming H264 to e.g. the browser we inject the PPS and SPS nals.
 | ||||
|     void extractFrameSizeFromVP8KeyFrame(const DTSC::Packet &pkt); | ||||
|     void updateCapabilitiesWithSDPOffer(SDP::Session &sdpSession); | ||||
|     bool bindUDPSocketOnLocalCandidateAddress(uint16_t port); ///< Binds our UDP socket onto the IP address that we shared via our SDP answer. We *have to* bind on a specific IP, see https://gist.github.com/roxlu/6c5ab696840256dac71b6247bab59ce9
 | ||||
|     bool bindUDPSocketOnLocalCandidateAddress( | ||||
|         uint16_t port); ///< Binds our UDP socket onto the IP address that we shared via our SDP answer.
 | ||||
|                         ///< We *have to* bind on a specific IP, see https://gist.github.com/roxlu/6c5ab696840256dac71b6247bab59ce9
 | ||||
|     std::string getLocalCandidateAddress(); | ||||
|      | ||||
| 
 | ||||
|   private: | ||||
|     SDP::Session sdp;                             ///< SDP parser. 
 | ||||
|     SDP::Answer sdpAnswer;                        ///< WIP: Replacing our `sdp` member .. 
 | ||||
|     Certificate cert;                             ///< The TLS certificate. Used to generate a fingerprint in SDP answers.
 | ||||
|     DTLSSRTPHandshake dtlsHandshake;              ///< Implements the DTLS handshake using the mbedtls library (fork). 
 | ||||
|     SRTPReader srtpReader;                        ///< Used to unprotect incoming RTP and RTCP data. Uses the keys that were exchanged with DTLS. 
 | ||||
|     SRTPWriter srtpWriter;                        ///< Used to protect our RTP and RTCP data when sending data to another peer. Uses the keys that were exchanged with DTLS. 
 | ||||
|     Socket::UDPConnection udp;                    ///< Our UDP socket over which WebRTC data is received and sent. 
 | ||||
|     StunReader stunReader;                        ///< Decodes STUN messages; during a session we keep receiving STUN messages to which we need to reply.
 | ||||
|     std::map<uint64_t, WebRTCTrack> webrtcTracks; ///< WebRTCTracks indexed by payload type for incoming data and indexed by myMeta.tracks[].trackID for outgoing data. 
 | ||||
|     tthread::thread* webRTCInputOutputThread;     ///< The thread in which we read WebRTC data when we're receive media from another peer. 
 | ||||
|     uint16_t udpPort;                             ///< The port on which our webrtc socket is bound. This is where we receive RTP, STUN, DTLS, etc. */
 | ||||
|     uint32_t SSRC;                                ///< The SSRC for this local instance. Is used when generating RTCP reports. */
 | ||||
|     uint64_t rtcpTimeoutInMillis;                 ///< When current time in millis exceeds this timeout we have to send a new RTCP packet.
 | ||||
|     SDP::Session sdp;      ///< SDP parser.
 | ||||
|     SDP::Answer sdpAnswer; ///< WIP: Replacing our `sdp` member ..
 | ||||
|     Certificate cert;      ///< The TLS certificate. Used to generate a fingerprint in SDP answers.
 | ||||
|     DTLSSRTPHandshake dtlsHandshake; ///< Implements the DTLS handshake using the mbedtls library (fork).
 | ||||
|     SRTPReader srtpReader; ///< Used to unprotect incoming RTP and RTCP data. Uses the keys that were exchanged with DTLS.
 | ||||
|     SRTPWriter srtpWriter; ///< Used to protect our RTP and RTCP data when sending data to another peer. Uses the keys that were exchanged with DTLS.
 | ||||
|     Socket::UDPConnection udp; ///< Our UDP socket over which WebRTC data is received and sent.
 | ||||
|     StunReader stunReader; ///< Decodes STUN messages; during a session we keep receiving STUN messages to which we need to reply.
 | ||||
|     std::map<uint64_t, WebRTCTrack> webrtcTracks; ///< WebRTCTracks indexed by payload type for incoming data and indexed by myMeta.tracks[].trackID for outgoing data.
 | ||||
|     tthread::thread *webRTCInputOutputThread; ///< The thread in which we read WebRTC data when we're receive media from another peer.
 | ||||
|     uint16_t udpPort; ///< The port on which our webrtc socket is bound. This is where we receive RTP, STUN, DTLS, etc. */
 | ||||
|     uint32_t SSRC; ///< The SSRC for this local instance. Is used when generating RTCP reports. */
 | ||||
|     uint64_t rtcpTimeoutInMillis; ///< When current time in millis exceeds this timeout we have to send a new RTCP packet.
 | ||||
|     uint64_t rtcpKeyFrameTimeoutInMillis; | ||||
|     uint64_t rtcpKeyFrameDelayInMillis; | ||||
|     Util::ResizeablePointer rtpOutBuffer;                           ///< Buffer into which we copy (unprotected) RTP data that we need to deliver to the other peer. This gets protected.
 | ||||
|     uint32_t videoBitrate;                        ///< The bitrate to use for incoming video streams. Can be configured via the signaling channel. Defaults to 6mbit.
 | ||||
|     Util::ResizeablePointer rtpOutBuffer; ///< Buffer into which we copy (unprotected) RTP data that we need to deliver to the other peer. This gets protected.
 | ||||
|     uint32_t videoBitrate; ///< The bitrate to use for incoming video streams. Can be configured via the signaling channel. Defaults to 6mbit.
 | ||||
| 
 | ||||
|     uint32_t audTrack, vidTrack; | ||||
| 
 | ||||
|     bool didReceiveKeyFrame; /* TODO burst delay */ | ||||
|      | ||||
| 
 | ||||
| #if defined(WEBRTC_PCAP) | ||||
|     PCAPWriter pcapOut;                           ///< Used during development to write unprotected packets that can be inspected in e.g. wireshark.
 | ||||
|     PCAPWriter pcapIn;                            ///< Used during development to write unprotected packets that can be inspected in e.g. wireshark.
 | ||||
|     PCAPWriter pcapOut; ///< Used during development to write unprotected packets that can be inspected in e.g. wireshark.
 | ||||
|     PCAPWriter pcapIn; ///< Used during development to write unprotected packets that can be inspected in e.g. wireshark.
 | ||||
| #endif | ||||
| 
 | ||||
|     std::map<uint8_t, uint64_t> payloadTypeToWebRTCTrack; ///< Maps e.g. RED to the corresponding track. Used when input supports RED/ULPFEC; can also be used to map RTX in the future.
 | ||||
|     std::map<uint32_t, nackBuffer> outBuffers; | ||||
|   }; | ||||
| } | ||||
| }// namespace Mist
 | ||||
| 
 | ||||
| typedef Mist::OutWebRTC mistOut; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 DDVTech
						DDVTech