This commit is contained in:
DDVTech 2021-09-10 23:44:31 +02:00 committed by Thulinma
parent 5b79f296d6
commit fccf66fba2
280 changed files with 56975 additions and 71885 deletions

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -13,6 +13,6 @@ namespace Mist{
bool isRecording();
bool keysOnly;
};
}
}// namespace Mist
typedef Mist::OutH264 mistOut;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;