Various fixes, among which:

- Fixed segfault when attempting to initialseek on disconnected streams
- Fix 100% CPU bug in controller's stats code
- WebRTC UDP bind socket improvements
- Several segfault fixes
- Increased packet reordering buffer size from 30 to 150 packets
- Tweaks to default output/buffer behaviour for incoming pushes
- Added message for load balancer checks
- Fixed HLS content type
- Stats fixes
- Exit reason fixes
- Fixed socket IP address detection
- Fixed non-string arguments for stream settings
- Added caching for getConnectedBinHost()
- Added WebRTC playback rate control
- Added/completed VP8/VP9 support to WebRTC/RTSP
- Added live seek option to WebRTC
- Fixed seek to exactly newest timestamp
- Fixed HLS input

# Conflicts:
#	lib/defines.h
#	src/input/input.cpp
This commit is contained in:
Thulinma 2021-04-21 18:11:46 +02:00
parent 2b99f2f5ea
commit 0af992d405
75 changed files with 1512 additions and 790 deletions

View file

@ -802,4 +802,6 @@ target_link_libraries(resolvetest mist)
add_executable(bitwritertest test/bitwriter.cpp ${BINARY_DIR}/mist/.headers) add_executable(bitwritertest test/bitwriter.cpp ${BINARY_DIR}/mist/.headers)
target_link_libraries(bitwritertest mist) target_link_libraries(bitwritertest mist)
add_test(BitWriterTest COMMAND bitwritertest) add_test(BitWriterTest COMMAND bitwritertest)
add_executable(streamstatustest test/status.cpp ${BINARY_DIR}/mist/.headers)
target_link_libraries(streamstatustest mist)

View file

@ -115,7 +115,7 @@ namespace Comms{
do{ do{
for (size_t i = firstValid(); i < endValid(); i++){ for (size_t i = firstValid(); i < endValid(); i++){
if (getStatus(i) == COMM_STATUS_INVALID){continue;} if (getStatus(i) == COMM_STATUS_INVALID){continue;}
setStatus(COMM_STATUS_DISCONNECT, i); setStatus(COMM_STATUS_REQDISCONNECT, i);
} }
while (getStatus(firstValid()) == COMM_STATUS_INVALID){deleteFirst();} while (getStatus(firstValid()) == COMM_STATUS_INVALID){deleteFirst();}
}while (firstValid() < endValid() && ++c < 10); }while (firstValid() < endValid() && ++c < 10);
@ -174,7 +174,7 @@ namespace Comms{
dataAccX.addField("lastsecond", RAX_64UINT); dataAccX.addField("lastsecond", RAX_64UINT);
dataAccX.addField("down", RAX_64UINT); dataAccX.addField("down", RAX_64UINT);
dataAccX.addField("up", RAX_64UINT); dataAccX.addField("up", RAX_64UINT);
dataAccX.addField("host", RAX_STRING, 16); dataAccX.addField("host", RAX_RAW, 16);
dataAccX.addField("stream", RAX_STRING, 100); dataAccX.addField("stream", RAX_STRING, 100);
dataAccX.addField("connector", RAX_STRING, 20); dataAccX.addField("connector", RAX_STRING, 20);
dataAccX.addField("crc", RAX_32UINT); dataAccX.addField("crc", RAX_32UINT);
@ -277,8 +277,13 @@ namespace Comms{
up.set(_up, idx); up.set(_up, idx);
} }
std::string Statistics::getHost() const{return host.string(index);} std::string Statistics::getHost() const{
std::string Statistics::getHost(size_t idx) const{return (master ? host.string(idx) : "");} return std::string(host.ptr(index), 16);
}
std::string Statistics::getHost(size_t idx) const{
if (!master){return std::string((size_t)16, (char)'\000');}
return std::string(host.ptr(idx), 16);
}
void Statistics::setHost(std::string _host){host.set(_host, index);} void Statistics::setHost(std::string _host){host.set(_host, index);}
void Statistics::setHost(std::string _host, size_t idx){ void Statistics::setHost(std::string _host, size_t idx){
if (!master){return;} if (!master){return;}

View file

@ -5,6 +5,7 @@
#define COMM_STATUS_DONOTTRACK 0x40 #define COMM_STATUS_DONOTTRACK 0x40
#define COMM_STATUS_SOURCE 0x80 #define COMM_STATUS_SOURCE 0x80
#define COMM_STATUS_REQDISCONNECT 0xFD
#define COMM_STATUS_DISCONNECT 0xFE #define COMM_STATUS_DISCONNECT 0xFE
#define COMM_STATUS_INVALID 0xFF #define COMM_STATUS_INVALID 0xFF

View file

@ -33,13 +33,22 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <stdarg.h> // for va_list
bool Util::Config::is_active = false; bool Util::Config::is_active = false;
bool Util::Config::is_restarting = false; bool Util::Config::is_restarting = false;
static Socket::Server *serv_sock_pointer = 0; static Socket::Server *serv_sock_pointer = 0;
uint32_t Util::Config::printDebugLevel = DEBUG; // uint32_t Util::Config::printDebugLevel = DEBUG; //
std::string Util::Config::streamName; std::string Util::Config::streamName;
std::string Util::Config::exitReason; char Util::exitReason[256] = {0};
void Util::logExitReason(const char *format, ...){
if (exitReason[0]){return;}
va_list args;
va_start(args, format);
vsnprintf(exitReason, 255, format, args);
va_end(args);
}
std::string Util::listenInterface; std::string Util::listenInterface;
uint32_t Util::listenPort = 0; uint32_t Util::listenPort = 0;
@ -450,7 +459,16 @@ void Util::Config::signal_handler(int signum, siginfo_t *sigInfo, void *ignore){
static int ctr = 0; static int ctr = 0;
if (!is_active && ++ctr > 4){BACKTRACE;} if (!is_active && ++ctr > 4){BACKTRACE;}
#endif #endif
logExitReason("Setting is_active to false due to received signal interrupt"); switch (sigInfo->si_code){
case SI_USER:
case SI_QUEUE:
case SI_TIMER:
case SI_ASYNCIO:
case SI_MESGQ:
logExitReason("signal %s (%d) from process %d", strsignal(signum), signum, sigInfo->si_pid);
break;
default: logExitReason("signal %s (%d)", strsignal(signum), signum);
}
is_active = false; is_active = false;
default: default:
switch (sigInfo->si_code){ switch (sigInfo->si_code){

View file

@ -13,6 +13,8 @@
/// Contains utility code, not directly related to streaming media /// Contains utility code, not directly related to streaming media
namespace Util{ namespace Util{
extern char exitReason[256];
void logExitReason(const char * format, ...);
/// Deals with parsing configuration from commandline options. /// Deals with parsing configuration from commandline options.
class Config{ class Config{
@ -27,10 +29,6 @@ namespace Util{
static bool is_restarting; ///< Set to true when restarting, set to false on boot. static bool is_restarting; ///< Set to true when restarting, set to false on boot.
static uint32_t printDebugLevel; static uint32_t printDebugLevel;
static std::string streamName; ///< Used by debug messages to identify the stream name static std::string streamName; ///< Used by debug messages to identify the stream name
static std::string exitReason;
static void logExitReason(const std::string &reason){
if (!exitReason.size()){exitReason = reason;}
}
// functions // functions
Config(); Config();
Config(std::string cmd); Config(std::string cmd);

View file

@ -163,7 +163,7 @@ static inline void show_stackframe(){}
// assumed // assumed
#define DEFAULT_PAGE_COUNT DEFAULT_KEY_COUNT // Assume every page is a key to ensure enough space #define DEFAULT_PAGE_COUNT DEFAULT_KEY_COUNT // Assume every page is a key to ensure enough space
#define DEFAULT_FRAGMENT_DURATION 5000 #define DEFAULT_FRAGMENT_DURATION 1900
#define META_META_OFFSET 104 #define META_META_OFFSET 104
#define META_META_RECORDSIZE 576 #define META_META_RECORDSIZE 576
@ -214,6 +214,7 @@ static inline void show_stackframe(){}
#define SHM_TRIGGER "MstTRGR%s" //%s trigger name #define SHM_TRIGGER "MstTRGR%s" //%s trigger name
#define SEM_LIVE "/MstLIVE%s" //%s stream name #define SEM_LIVE "/MstLIVE%s" //%s stream name
#define SEM_INPUT "/MstInpt%s" //%s stream name #define SEM_INPUT "/MstInpt%s" //%s stream name
#define SEM_TRACKLIST "/MstTRKS%s" //%s stream name
#define SEM_SESSCACHE "/MstSessCacheLock" #define SEM_SESSCACHE "/MstSessCacheLock"
#define SHM_CAPA "MstCapa" #define SHM_CAPA "MstCapa"
#define SHM_PROTO "MstProt" #define SHM_PROTO "MstProt"
@ -243,6 +244,9 @@ static inline void show_stackframe(){}
// Setting this value to lower than 2 seconds **WILL** cause stuttering in playback due to buffer negotiation. // Setting this value to lower than 2 seconds **WILL** cause stuttering in playback due to buffer negotiation.
#define SIMULATED_LIVE_BUFFER 7000 #define SIMULATED_LIVE_BUFFER 7000
/// The time between virtual audio "keyframes"
#define AUDIO_KEY_INTERVAL 2047
#define STAT_EX_SIZE 177 #define STAT_EX_SIZE 177
#define PLAY_EX_SIZE 2 + 6 * SIMUL_TRACKS #define PLAY_EX_SIZE 2 + 6 * SIMUL_TRACKS

View file

@ -11,10 +11,6 @@
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#define AUDIO_KEY_INTERVAL \
5000 ///< This define controls the keyframe interval for non-video tracks, such as audio and
///< metadata tracks.
namespace DTSC{ namespace DTSC{
char Magic_Header[] = "DTSC"; char Magic_Header[] = "DTSC";
char Magic_Packet[] = "DTPD"; char Magic_Packet[] = "DTPD";
@ -448,6 +444,14 @@ namespace DTSC{
Bit::htobll(data + 12, _time); Bit::htobll(data + 12, _time);
} }
void Packet::nullMember(const std::string & memb){
if (!master){
INFO_MSG("Can't null '%s' for this packet, as it is not master.", memb.c_str());
return;
}
getScan().nullMember(memb);
}
///\brief Returns the track id of the packet. ///\brief Returns the track id of the packet.
///\return The track id of this packet. ///\return The track id of this packet.
size_t Packet::getTrackId() const{ size_t Packet::getTrackId() const{
@ -544,6 +548,32 @@ namespace DTSC{
return Scan(); return Scan();
} }
/// If this is an object type and contains the given indice/len, sets the indice name to all zeroes.
void Scan::nullMember(const std::string & indice){
nullMember(indice.data(), indice.size());
}
/// If this is an object type and contains the given indice/len, sets the indice name to all zeroes.
void Scan::nullMember(const char * indice, const size_t ind_len){
if (getType() != DTSC_OBJ && getType() != DTSC_CON){return;}
char * i = p + 1;
//object, scan contents
while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE)
if (i + 2 >= p + len) {
return;//out of packet!
}
uint16_t strlen = Bit::btohs(i);
i += 2;
if (ind_len == strlen && strncmp(indice, i, strlen) == 0) {
memset(i, 0, strlen);
return;
}
i = skipDTSC(i + strlen, p + len);
if (!i) {return;}
}
return;
}
/// Returns an object representing the named indice of this object. /// Returns an object representing the named indice of this object.
/// Returns an invalid object if this indice doesn't exist or this isn't an object type. /// Returns an invalid object if this indice doesn't exist or this isn't an object type.
bool Scan::hasMember(const std::string &indice) const{ bool Scan::hasMember(const std::string &indice) const{
@ -1153,7 +1183,7 @@ namespace DTSC{
/// Does not clear "tracks" beforehand, so it may contain stale information afterwards if it was /// Does not clear "tracks" beforehand, so it may contain stale information afterwards if it was
/// already populated. /// already populated.
void Meta::refresh(){ void Meta::refresh(){
if (!stream.getPointer("tracks")){ if (!stream.isReady() || !stream.getPointer("tracks")){
INFO_MSG("No track pointer, not refreshing."); INFO_MSG("No track pointer, not refreshing.");
return; return;
} }
@ -1480,6 +1510,15 @@ namespace DTSC{
/// Adds a track to the metadata structure. /// Adds a track to the metadata structure.
/// To be called from the various inputs/outputs whenever they want to add a track. /// To be called from the various inputs/outputs whenever they want to add a track.
size_t Meta::addTrack(size_t fragCount, size_t keyCount, size_t partCount, size_t pageCount, bool setValid){ size_t Meta::addTrack(size_t fragCount, size_t keyCount, size_t partCount, size_t pageCount, bool setValid){
char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SEM_TRACKLIST, streamName.c_str());
IPC::semaphore trackLock(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
if (!trackLock){
FAIL_MSG("Could not open semaphore to add track!");
return -1;
}
trackLock.wait();
size_t pageSize = TRACK_TRACK_OFFSET + TRACK_TRACK_RECORDSIZE + size_t pageSize = TRACK_TRACK_OFFSET + TRACK_TRACK_RECORDSIZE +
(TRACK_FRAGMENT_OFFSET + (TRACK_FRAGMENT_RECORDSIZE * fragCount)) + (TRACK_FRAGMENT_OFFSET + (TRACK_FRAGMENT_RECORDSIZE * fragCount)) +
(TRACK_KEY_OFFSET + (TRACK_KEY_RECORDSIZE * keyCount)) + (TRACK_KEY_OFFSET + (TRACK_KEY_RECORDSIZE * keyCount)) +
@ -1488,7 +1527,6 @@ namespace DTSC{
size_t tNumber = trackList.getPresent(); size_t tNumber = trackList.getPresent();
char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), tNumber); snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), tNumber);
Track &t = tracks[tNumber]; Track &t = tracks[tNumber];
@ -1511,7 +1549,7 @@ namespace DTSC{
trackList.setInt(trackPidField, getpid(), tNumber); trackList.setInt(trackPidField, getpid(), tNumber);
trackList.setInt(trackSourceTidField, INVALID_TRACK_ID, tNumber); trackList.setInt(trackSourceTidField, INVALID_TRACK_ID, tNumber);
if (setValid){validateTrack(tNumber);} if (setValid){validateTrack(tNumber);}
trackLock.post();
return tNumber; return tNumber;
} }
@ -1740,6 +1778,7 @@ namespace DTSC{
} }
std::string Meta::getLang(size_t trackIdx) const{ std::string Meta::getLang(size_t trackIdx) const{
const DTSC::Track &t = tracks.at(trackIdx); const DTSC::Track &t = tracks.at(trackIdx);
if (!t.track.isReady()){return "";}
return t.track.getPointer(t.trackLangField); return t.track.getPointer(t.trackLangField);
} }
@ -1864,8 +1903,14 @@ namespace DTSC{
if (getType(*it) != "video"){continue;} if (getType(*it) != "video"){continue;}
DTSC::Parts p(parts(*it)); DTSC::Parts p(parts(*it));
size_t ctr = 0; size_t ctr = 0;
int64_t prevOffset = 0;
bool firstOffset = true;
for (size_t i = p.getFirstValid(); i < p.getEndValid(); ++i){ for (size_t i = p.getFirstValid(); i < p.getEndValid(); ++i){
if (p.getOffset(i)){return true;} if (firstOffset){
firstOffset = false;
prevOffset = p.getOffset(i);
}
if (p.getOffset(i) != prevOffset){return true;}
if (++ctr >= 100){break;} if (++ctr >= 100){break;}
} }
} }
@ -1903,7 +1948,7 @@ namespace DTSC{
std::string(trackList.getPointer(trackEncryptionField, i)) != ""){ std::string(trackList.getPointer(trackEncryptionField, i)) != ""){
res.erase(trackList.getInt(trackSourceTidField, i)); res.erase(trackList.getInt(trackSourceTidField, i));
} }
if (!tracks.count(i)){res.erase(i);} if (!tracks.count(i) || !tracks.at(i).track.isReady()){res.erase(i);}
if (skipEmpty){ if (skipEmpty){
if (res.count(i) && !tracks.at(i).parts.getPresent()){res.erase(i);} if (res.count(i) && !tracks.at(i).parts.getPresent()){res.erase(i);}
} }
@ -2032,10 +2077,12 @@ namespace DTSC{
curJitter = 0; curJitter = 0;
} }
if (t > lastTime + 2500){ if (t > lastTime + 2500){
if ((x % 4) == 0 && maxJitter > 50 && curJitter < maxJitter - 50){ if ((x % 4) == 0){
HIGH_MSG("Jitter lowered from %" PRIu64 " to %" PRIu64 " ms", maxJitter, curJitter); if (maxJitter > 50 && curJitter < maxJitter - 50){
MEDIUM_MSG("Jitter lowered from %" PRIu64 " to %" PRIu64 " ms", maxJitter, curJitter);
maxJitter = curJitter; maxJitter = curJitter;
curJitter = 0; }
curJitter = maxJitter*0.75;
} }
++x; ++x;
trueTime[x % 8] = curMs; trueTime[x % 8] = curMs;
@ -2055,7 +2102,11 @@ namespace DTSC{
// Postive jitter = packets arriving too late. // Postive jitter = packets arriving too late.
// We need to delay playback at least by this amount to account for it. // We need to delay playback at least by this amount to account for it.
if ((uint64_t)jitter > maxJitter){ if ((uint64_t)jitter > maxJitter){
if (jitter - maxJitter > 420){
INFO_MSG("Jitter increased from %" PRIu64 " to %" PRId64 " ms", maxJitter, jitter);
}else{
HIGH_MSG("Jitter increased from %" PRIu64 " to %" PRId64 " ms", maxJitter, jitter); HIGH_MSG("Jitter increased from %" PRIu64 " to %" PRId64 " ms", maxJitter, jitter);
}
maxJitter = (uint64_t)jitter; maxJitter = (uint64_t)jitter;
} }
if (curJitter < (uint64_t)jitter){curJitter = (uint64_t)jitter;} if (curJitter < (uint64_t)jitter){curJitter = (uint64_t)jitter;}
@ -2291,6 +2342,8 @@ namespace DTSC{
if (streamPage.mapped && stream.isReady()){stream.setExit();} if (streamPage.mapped && stream.isReady()){stream.setExit();}
streamPage.master = true; streamPage.master = true;
} }
stream = Util::RelAccX();
trackList = Util::RelAccX();
streamPage.close(); streamPage.close();
tM.clear(); tM.clear();
tracks.clear(); tracks.clear();
@ -2875,6 +2928,7 @@ namespace DTSC{
const Util::RelAccX &pages = tracks.at(idx).pages; const Util::RelAccX &pages = tracks.at(idx).pages;
size_t res = pages.getStartPos(); size_t res = pages.getStartPos();
for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){ for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){
if (pages.getInt("avail", i) == 0){continue;}
if (pages.getInt("firsttime", i) > time){break;} if (pages.getInt("firsttime", i) > time){break;}
res = i; res = i;
} }
@ -2887,6 +2941,7 @@ namespace DTSC{
const Util::RelAccX &pages = tracks.at(idx).pages; const Util::RelAccX &pages = tracks.at(idx).pages;
size_t res = pages.getStartPos(); size_t res = pages.getStartPos();
for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){ for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){
if (pages.getInt("avail", i) == 0){continue;}
if (pages.getInt("firstkey", i) > keyNum){break;} if (pages.getInt("firstkey", i) > keyNum){break;}
res = i; res = i;
} }

View file

@ -63,6 +63,8 @@ namespace DTSC{
Scan getMember(const std::string &indice) const; Scan getMember(const std::string &indice) const;
Scan getMember(const char *indice) const; Scan getMember(const char *indice) const;
Scan getMember(const char *indice, size_t ind_len) const; Scan getMember(const char *indice, size_t ind_len) const;
void nullMember(const std::string & indice);
void nullMember(const char * indice, size_t ind_len);
Scan getIndice(size_t num) const; Scan getIndice(size_t num) const;
std::string getIndiceName(size_t num) const; std::string getIndiceName(size_t num) const;
size_t getSize() const; size_t getSize() const;
@ -111,6 +113,7 @@ namespace DTSC{
void setKeyFrame(bool kf); void setKeyFrame(bool kf);
virtual uint64_t getTime() const; virtual uint64_t getTime() const;
void setTime(uint64_t _time); void setTime(uint64_t _time);
void nullMember(const std::string & memb);
size_t getTrackId() const; size_t getTrackId() const;
char *getData() const; char *getData() const;
size_t getDataLen() const; size_t getDataLen() const;

View file

@ -1063,8 +1063,8 @@ namespace MP4{
ESDS::ESDS(){memcpy(data + 4, "esds", 4);} ESDS::ESDS(){memcpy(data + 4, "esds", 4);}
ESDS::ESDS(std::string init){ ESDS::ESDS(const DTSC::Meta & M, size_t idx){
///\todo Do this better, in a non-hardcoded way. std::string init = M.getInit(idx);
memcpy(data + 4, "esds", 4); memcpy(data + 4, "esds", 4);
reserve(payloadOffset, 0, init.size() ? init.size() + 28 : 26); reserve(payloadOffset, 0, init.size() ? init.size() + 28 : 26);
unsigned int i = 12; unsigned int i = 12;
@ -1084,14 +1084,10 @@ namespace MP4{
data[i++] = 0; // buffer size data[i++] = 0; // buffer size
data[i++] = 0; // buffer size data[i++] = 0; // buffer size
data[i++] = 0; // buffer size data[i++] = 0; // buffer size
data[i++] = 0; // maxbps Bit::htobl(data+i, M.getMaxBps(idx));//maxbps
data[i++] = 0; // maxbps i += 4;
data[i++] = 0; // maxbps Bit::htobl(data+i, M.getBps(idx));//avgbps
data[i++] = 0; // maxbps i += 4;
data[i++] = 0; // avgbps
data[i++] = 0; // avgbps
data[i++] = 0; // avgbps
data[i++] = 0; // avgbps
if (init.size()){ if (init.size()){
data[i++] = 0x5; // DecSpecificInfoTag data[i++] = 0x5; // DecSpecificInfoTag
data[i++] = init.size(); data[i++] = init.size();
@ -2825,17 +2821,20 @@ namespace MP4{
AudioSampleEntry::AudioSampleEntry(const DTSC::Meta &M, size_t idx){ AudioSampleEntry::AudioSampleEntry(const DTSC::Meta &M, size_t idx){
std::string tCodec = M.getCodec(idx); std::string tCodec = M.getCodec(idx);
initialize(); initialize();
if (tCodec == "AAC" || tCodec == "MP3"){setCodec("mp4a");}
if (tCodec == "AC3"){setCodec("ac-3");}
setDataReferenceIndex(1); setDataReferenceIndex(1);
setSampleRate(M.getRate(idx)); setSampleRate(M.getRate(idx));
setChannelCount(M.getChannels(idx)); setChannelCount(M.getChannels(idx));
setSampleSize(M.getSize(idx)); setSampleSize(M.getSize(idx));
if (tCodec == "AAC" || tCodec == "MP3"){
setCodec("mp4a");
setSampleSize(16);
}
if (tCodec == "AC3"){setCodec("ac-3");}
if (tCodec == "AC3"){ if (tCodec == "AC3"){
MP4::DAC3 dac3Box(M.getRate(idx), M.getChannels(idx)); MP4::DAC3 dac3Box(M.getRate(idx), M.getChannels(idx));
setCodecBox(dac3Box); setCodecBox(dac3Box);
}else{// other codecs use the ESDS box }else{// other codecs use the ESDS box
MP4::ESDS esdsBox(M.getInit(idx)); MP4::ESDS esdsBox(M, idx);
setCodecBox(esdsBox); setCodecBox(esdsBox);
} }
} }

View file

@ -241,7 +241,7 @@ namespace MP4{
class ESDS : public fullBox{ class ESDS : public fullBox{
public: public:
ESDS(); ESDS();
ESDS(std::string init); ESDS(const DTSC::Meta & M, size_t idx);
ESDescriptor getESDescriptor(); ESDescriptor getESDescriptor();
bool isAAC(); bool isAAC();
std::string getCodec(); std::string getCodec();

View file

@ -325,42 +325,50 @@ pid_t Util::Procs::StartPiped(const char *const *argv, int *fdin, int *fdout, in
pid = fork(); pid = fork();
if (pid == 0){// child if (pid == 0){// child
handler_set = false; handler_set = false;
if (!fdin){
dup2(devnull, 100);
}else if (*fdin == -1){
close(pipein[1]); // close unused write end
dup2(pipein[0], 100);
close(pipein[0]);
}else{
dup2(*fdin, 100);
}
if (!fdout){
dup2(devnull, 101);
}else if (*fdout == -1){
close(pipeout[0]); // close unused read end
dup2(pipeout[1], 101);
close(pipeout[1]);
}else{
dup2(*fdout, 101);
}
if (!fderr){
dup2(devnull, 102);
}else if (*fderr == -1){
close(pipeerr[0]); // close unused read end
dup2(pipeerr[1], 102);
close(pipeerr[1]);
}else{
dup2(*fderr, 102);
}
if (fdin && *fdin != -1){close(*fdin);}
if (fdout && *fdout != -1){close(*fdout);}
if (fderr && *fderr != -1){close(*fderr);}
if (devnull != -1){close(devnull);}
// Close all sockets in the socketList // Close all sockets in the socketList
for (std::set<int>::iterator it = Util::Procs::socketList.begin(); for (std::set<int>::iterator it = Util::Procs::socketList.begin();
it != Util::Procs::socketList.end(); ++it){ it != Util::Procs::socketList.end(); ++it){
close(*it); close(*it);
} }
if (!fdin){ //Black magic to make sure if 0/1/2 are not what we think they are, we end up with them not mixed up and weird.
dup2(devnull, STDIN_FILENO); dup2(100, 0);
}else if (*fdin == -1){ dup2(101, 1);
close(pipein[1]); // close unused write end dup2(102, 2);
dup2(pipein[0], STDIN_FILENO); close(100);
close(pipein[0]); close(101);
}else if (*fdin != STDIN_FILENO){ close(102);
dup2(*fdin, STDIN_FILENO); //There! Now we normalized our stdio
}
if (!fdout){
dup2(devnull, STDOUT_FILENO);
}else if (*fdout == -1){
close(pipeout[0]); // close unused read end
dup2(pipeout[1], STDOUT_FILENO);
close(pipeout[1]);
}else if (*fdout != STDOUT_FILENO){
dup2(*fdout, STDOUT_FILENO);
}
if (!fderr){
dup2(devnull, STDERR_FILENO);
}else if (*fderr == -1){
close(pipeerr[0]); // close unused read end
dup2(pipeerr[1], STDERR_FILENO);
close(pipeerr[1]);
}else if (*fderr != STDERR_FILENO){
dup2(*fderr, STDERR_FILENO);
}
if (fdin && *fdin != -1 && *fdin != STDIN_FILENO){close(*fdin);}
if (fdout && *fdout != -1 && *fdout != STDOUT_FILENO){close(*fdout);}
if (fderr && *fderr != -1 && *fderr != STDERR_FILENO){close(*fderr);}
if (devnull != -1){close(devnull);}
// Because execvp requires a char* const* and we have a const char* const* // Because execvp requires a char* const* and we have a const char* const*
execvp(argv[0], (char *const *)argv); execvp(argv[0], (char *const *)argv);
/*LTS-START*/ /*LTS-START*/

View file

@ -354,7 +354,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
switch (headertype){ switch (headertype){
case 0x00: case 0x00:
if (!buffer.available(i + 11)){return false;}// can't read whole header if (!buffer.available(i + 11)){
DONTEVEN_MSG("Cannot read whole header");
return false;
}// can't read whole header
indata = buffer.copy(i + 11); indata = buffer.copy(i + 11);
timestamp = indata[i++] * 256 * 256; timestamp = indata[i++] * 256 * 256;
timestamp += indata[i++] * 256; timestamp += indata[i++] * 256;
@ -372,7 +375,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
msg_stream_id += indata[i++] * 256 * 256 * 256; msg_stream_id += indata[i++] * 256 * 256 * 256;
break; break;
case 0x40: case 0x40:
if (!buffer.available(i + 7)){return false;}// can't read whole header if (!buffer.available(i + 7)){
DONTEVEN_MSG("Cannot read whole header");
return false;
}// can't read whole header
indata = buffer.copy(i + 7); indata = buffer.copy(i + 7);
if (!allow_short){WARN_MSG("Warning: Header type 0x40 with no valid previous chunk!");} if (!allow_short){WARN_MSG("Warning: Header type 0x40 with no valid previous chunk!");}
timestamp = indata[i++] * 256 * 256; timestamp = indata[i++] * 256 * 256;
@ -391,7 +397,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
msg_stream_id = prev.msg_stream_id; msg_stream_id = prev.msg_stream_id;
break; break;
case 0x80: case 0x80:
if (!buffer.available(i + 3)){return false;}// can't read whole header if (!buffer.available(i + 3)){
DONTEVEN_MSG("Cannot read whole header");
return false;
}// can't read whole header
indata = buffer.copy(i + 3); indata = buffer.copy(i + 3);
if (!allow_short){WARN_MSG("Warning: Header type 0x80 with no valid previous chunk!");} if (!allow_short){WARN_MSG("Warning: Header type 0x80 with no valid previous chunk!");}
timestamp = indata[i++] * 256 * 256; timestamp = indata[i++] * 256 * 256;
@ -435,7 +444,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
// read extended timestamp, if necessary // read extended timestamp, if necessary
if (ts_header == 0x00ffffff){ if (ts_header == 0x00ffffff){
if (!buffer.available(i + 4)){return false;}// can't read timestamp if (!buffer.available(i + 4)){
DONTEVEN_MSG("Cannot read timestamp");
return false;
}// can't read timestamp
indata = buffer.copy(i + 4); indata = buffer.copy(i + 4);
timestamp += indata[i++] * 256 * 256 * 256; timestamp += indata[i++] * 256 * 256 * 256;
timestamp += indata[i++] * 256 * 256; timestamp += indata[i++] * 256 * 256;
@ -447,7 +459,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
// read data if length > 0, and allocate it // read data if length > 0, and allocate it
if (real_len > 0){ if (real_len > 0){
if (!buffer.available(i + real_len)){return false;}// can't read all data (yet) if (!buffer.available(i + real_len)){
DONTEVEN_MSG("Cannot read all data yet");
return false;
}// can't read all data (yet)
buffer.remove(i); // remove the header buffer.remove(i); // remove the header
if (prev.len_left > 0){ if (prev.len_left > 0){
data = prev.data + buffer.remove(real_len); // append the data and remove from buffer data = prev.data + buffer.remove(real_len); // append the data and remove from buffer

View file

@ -58,12 +58,13 @@ namespace RTP{
if ((payload[0] & 0x1F) == 12){return;} if ((payload[0] & 0x1F) == 12){return;}
/// \todo This function probably belongs in DMS somewhere. /// \todo This function probably belongs in DMS somewhere.
if (payloadlen + getHsize() + 2 <= maxDataLen){ if (payloadlen + getHsize() + 2 <= maxDataLen){
data[1] &= 0x7F; // setting the RTP marker bit to 0
if (lastOfAccesUnit){ if (lastOfAccesUnit){
data[1] |= 0x80; // setting the RTP marker bit to 1 data[1] |= 0x80; // setting the RTP marker bit to 1
} }
uint8_t nal_type = (payload[0] & 0x1F); uint8_t nal_type = (payload[0] & 0x1F);
if (nal_type < 1 || nal_type > 5){ if (nal_type < 1 || nal_type > 5){
data[1] &= ~0x80; // but not for non-vlc types data[1] &= 0x7F; // but not for non-vlc types
} }
memcpy(data + getHsize(), payload, payloadlen); memcpy(data + getHsize(), payload, payloadlen);
callBack(socket, data, getHsize() + payloadlen, channel); callBack(socket, data, getHsize() + payloadlen, channel);
@ -239,6 +240,10 @@ namespace RTP{
sendVP8(socket, callBack, payload, payloadlen, channel); sendVP8(socket, callBack, payload, payloadlen, channel);
return; return;
} }
if (codec == "VP9"){
sendVP8(socket, callBack, payload, payloadlen, channel);
return;
}
if (codec == "HEVC"){ if (codec == "HEVC"){
unsigned long sent = 0; unsigned long sent = 0;
while (sent < payloadlen){ while (sent < payloadlen){
@ -414,6 +419,18 @@ namespace RTP{
data = (char *)dat; data = (char *)dat;
} }
/// Describes a packet in human-readable terms
std::string Packet::toString() const{
std::stringstream ret;
ret << maxDataLen << "b RTP packet ";
if (getMarker()){ret << "(marked) ";}
ret << "payload type " << getPayloadType() << ", #" << getSequence() << ", @" << getTimeStamp();
ret << " (" << getHsize() << "b header, " << getPayloadSize() << "b payload, " << getPadding() << "b padding)";
return ret.str();
}
MPEGVideoHeader::MPEGVideoHeader(char *d){data = d;} MPEGVideoHeader::MPEGVideoHeader(char *d){data = d;}
uint16_t MPEGVideoHeader::getTotalLen() const{ uint16_t MPEGVideoHeader::getTotalLen() const{
@ -481,8 +498,8 @@ namespace RTP{
/// Calls the callback with packets in sorted order, whenever it becomes possible to do so. /// Calls the callback with packets in sorted order, whenever it becomes possible to do so.
void Sorter::addPacket(const Packet &pack){ void Sorter::addPacket(const Packet &pack){
if (!rtpSeq){rtpSeq = pack.getSequence();} if (!rtpSeq){rtpSeq = pack.getSequence();}
// packet is very early - assume dropped after 30 packets // packet is very early - assume dropped after 150 packets
while ((int16_t)(rtpSeq - ((uint16_t)pack.getSequence())) < -30){ while ((int16_t)(rtpSeq - ((uint16_t)pack.getSequence())) < -150){
WARN_MSG("Giving up on packet %u", rtpSeq); WARN_MSG("Giving up on packet %u", rtpSeq);
++rtpSeq; ++rtpSeq;
++lostTotal; ++lostTotal;
@ -574,7 +591,10 @@ namespace RTP{
if (M.getType(tid) == "video" || M.getCodec(tid) == "MP2" || M.getCodec(tid) == "MP3"){ if (M.getType(tid) == "video" || M.getCodec(tid) == "MP2" || M.getCodec(tid) == "MP3"){
m = 90.0; m = 90.0;
} }
setProperties(tid, M.getCodec(tid), M.getType(tid), M.getInit(tid), m); if (M.getCodec(tid) == "opus"){
m = 48.0;
}
setProperties(M.getID(tid), M.getCodec(tid), M.getType(tid), M.getInit(tid), m);
} }
void toDTSC::setCallbacks(void (*cbP)(const DTSC::Packet &pkt), void toDTSC::setCallbacks(void (*cbP)(const DTSC::Packet &pkt),
@ -627,6 +647,9 @@ namespace RTP{
if (codec == "VP8"){ if (codec == "VP8"){
return handleVP8(msTime, pl, plSize, missed, (pkt.getPadding() == 1) ? true : false); return handleVP8(msTime, pl, plSize, missed, (pkt.getPadding() == 1) ? true : false);
} }
if (codec == "VP9"){
return handleVP8(msTime, pl, plSize, missed, (pkt.getPadding() == 1) ? true : false);
}
// Trivial codecs just fill a packet with raw data and continue. Easy peasy, lemon squeezy. // Trivial codecs just fill a packet with raw data and continue. Easy peasy, lemon squeezy.
if (codec == "ALAW" || codec == "opus" || codec == "PCM" || codec == "ULAW"){ if (codec == "ALAW" || codec == "opus" || codec == "PCM" || codec == "ULAW"){
DTSC::Packet nextPack; DTSC::Packet nextPack;
@ -902,6 +925,51 @@ namespace RTP{
// Header data? Compare to init, set if needed, and throw away // Header data? Compare to init, set if needed, and throw away
uint8_t nalType = (buffer[4] & 0x1F); uint8_t nalType = (buffer[4] & 0x1F);
if (nalType == 9 && len < 20){return;}// ignore delimiter-only packets if (nalType == 9 && len < 20){return;}// ignore delimiter-only packets
if (!h264OutBuffer.size()){
currH264Time = ts;
h264BufferWasKey = isKey;
}
//Send an outPacket every time the timestamp updates
if (currH264Time != ts){
//calculate the "packet" (which might be more than one actual packet) timestamp
uint32_t offset = 0;
uint64_t newTs = currH264Time;
if (fps > 1){
// Assume a steady frame rate, clip the timestamp based on frame number.
uint64_t frameNo = (currH264Time / (1000.0 / fps)) + 0.5;
while (frameNo < packCount){packCount--;}
// More than 32 frames behind? We probably skipped something, somewhere...
if ((frameNo - packCount) > 32){packCount = frameNo;}
// After some experimentation, we found that the time offset is the difference between the
// frame number and the packet counter, times the frame rate in ms
offset = (frameNo - packCount) * (1000.0 / fps);
//... and the timestamp is the packet counter times the frame rate in ms.
newTs = packCount * (1000.0 / fps);
VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64
" -> +%" PRIu64 "/%" PRIu32,
ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset);
}else{
// For non-steady frame rate, assume no offsets are used and the timestamp is already
// correct
VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (variable rate)", currH264Time,
isKey ? "key" : "i", packCount);
}
// Fill the new DTSC packet, buffer it.
DTSC::Packet nextPack;
nextPack.genericFill(newTs, offset, trackId, h264OutBuffer, h264OutBuffer.size(), 0, h264BufferWasKey);
packCount++;
outPacket(nextPack);
//Clear the buffers, reset the time to current
h264OutBuffer.assign(0, 0);
currH264Time = ts;
h264BufferWasKey = isKey;
}
h264BufferWasKey |= isKey;
switch (nalType){ switch (nalType){
case 6: // SEI case 6: // SEI
return; return;
@ -950,80 +1018,26 @@ namespace RTP{
} }
return; return;
case 5:{ case 5:{
// @todo add check if ppsData and spsData are not empty? //If this is a keyframe and we have no buffer yet, prepend the SPS/PPS
static Util::ResizeablePointer tmp; if (!h264OutBuffer.size()){
tmp.assign(0, 0);
char sizeBuffer[4]; char sizeBuffer[4];
Bit::htobl(sizeBuffer, spsData.size()); Bit::htobl(sizeBuffer, spsData.size());
tmp.append(sizeBuffer, 4); h264OutBuffer.append(sizeBuffer, 4);
tmp.append(spsData.data(), spsData.size()); h264OutBuffer.append(spsData.data(), spsData.size());
Bit::htobl(sizeBuffer, ppsData.size()); Bit::htobl(sizeBuffer, ppsData.size());
tmp.append(sizeBuffer, 4); h264OutBuffer.append(sizeBuffer, 4);
tmp.append(ppsData.data(), ppsData.size()); h264OutBuffer.append(ppsData.data(), ppsData.size());
tmp.append(buffer, len);
uint32_t offset = 0;
uint64_t newTs = ts;
if (fps > 1){
// Assume a steady frame rate, clip the timestamp based on frame number.
uint64_t frameNo = (ts / (1000.0 / fps)) + 0.5;
while (frameNo < packCount){packCount--;}
// More than 32 frames behind? We probably skipped something, somewhere...
if ((frameNo - packCount) > 32){packCount = frameNo;}
// After some experimentation, we found that the time offset is the difference between the
// frame number and the packet counter, times the frame rate in ms
offset = (frameNo - packCount) * (1000.0 / fps);
//... and the timestamp is the packet counter times the frame rate in ms.
newTs = packCount * (1000.0 / fps);
VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64
" -> +%" PRIu64 "/%" PRIu32,
ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset);
}else{
// For non-steady frame rate, assume no offsets are used and the timestamp is already
// correct
VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (variable rate)", ts,
isKey ? "key" : "i", packCount);
} }
// Fill the new DTSC packet, buffer it. //Note: no return, we still want to buffer the packet itself, below!
DTSC::Packet nextPack;
nextPack.genericFill(newTs, offset, trackId, tmp, tmp.size(), 0, isKey);
packCount++;
outPacket(nextPack);
return;
} }
default: // others, continue parsing default: // others, continue parsing
break; break;
} }
uint32_t offset = 0; //Buffer the packet
uint64_t newTs = ts; h264OutBuffer.append(buffer, len);
if (fps > 1){
// Assume a steady frame rate, clip the timestamp based on frame number.
uint64_t frameNo = (ts / (1000.0 / fps)) + 0.5;
while (frameNo < packCount){packCount--;}
// More than 32 frames behind? We probably skipped something, somewhere...
if ((frameNo - packCount) > 32){packCount = frameNo;}
// After some experimentation, we found that the time offset is the difference between the
// frame number and the packet counter, times the frame rate in ms
offset = (frameNo - packCount) * (1000.0 / fps);
//... and the timestamp is the packet counter times the frame rate in ms.
newTs = packCount * (1000.0 / fps);
VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (%.2f FPS). Expected %" PRIu64
" -> +%" PRIu64 "/%" PRIu32,
ts, isKey ? "key" : "i", frameNo, fps, packCount, (frameNo - packCount), offset);
}else{
// For non-steady frame rate, assume no offsets are used and the timestamp is already correct
VERYHIGH_MSG("Packing time %" PRIu64 " = %sframe %" PRIu64 " (variable rate)", ts,
isKey ? "key" : "i", packCount);
}
// Fill the new DTSC packet, buffer it.
DTSC::Packet nextPack;
nextPack.genericFill(newTs, offset, trackId, buffer, len, 0, isKey);
packCount++;
outPacket(nextPack);
} }
/// Handles a single H264 packet, checking if others are appended at the end in Annex B format. /// Handles a single H264 packet, checking if others are appended at the end in Annex B format.

View file

@ -77,6 +77,7 @@ namespace RTP{
Packet(const char *dat, uint64_t len); Packet(const char *dat, uint64_t len);
const char *getData(); const char *getData();
char *ptr() const{return data;} char *ptr() const{return data;}
std::string toString() const;
}; };
/// Sorts RTP packets, outputting them through a callback in correct order. /// Sorts RTP packets, outputting them through a callback in correct order.
@ -163,6 +164,9 @@ namespace RTP{
h265::initData hevcInfo; ///< For HEVC init parsing h265::initData hevcInfo; ///< For HEVC init parsing
Util::ResizeablePointer fuaBuffer; ///< For H264/HEVC FU-A packets Util::ResizeablePointer fuaBuffer; ///< For H264/HEVC FU-A packets
Util::ResizeablePointer packBuffer; ///< For H264/HEVC regular and STAP packets Util::ResizeablePointer packBuffer; ///< For H264/HEVC regular and STAP packets
uint64_t currH264Time;//Time of the DTSC packet currently being built (pre-conversion)
Util::ResizeablePointer h264OutBuffer; ///< For collecting multiple timestamps into one packet
bool h264BufferWasKey;
void handleH264(uint64_t msTime, char *pl, uint32_t plSize, bool missed, bool hasPadding); void handleH264(uint64_t msTime, char *pl, uint32_t plSize, bool missed, bool hasPadding);
void handleH264Single(uint64_t ts, const char *buffer, const uint32_t len, bool isKey); void handleH264Single(uint64_t ts, const char *buffer, const uint32_t len, bool isKey);
void handleH264Multi(uint64_t ts, char *buffer, const uint32_t len); void handleH264Multi(uint64_t ts, char *buffer, const uint32_t len);

View file

@ -462,9 +462,18 @@ namespace SDP{
myMeta->setCodec(tid, "HEVC"); myMeta->setCodec(tid, "HEVC");
myMeta->setRate(tid, 90000); myMeta->setRate(tid, 90000);
} }
if (trCodec == "VP8"){
myMeta->setCodec(tid, "VP8");
myMeta->setRate(tid, 90000);
}
if (trCodec == "VP9"){
myMeta->setCodec(tid, "VP9");
myMeta->setRate(tid, 90000);
}
if (trCodec == "OPUS"){ if (trCodec == "OPUS"){
myMeta->setCodec(tid, "opus"); myMeta->setCodec(tid, "opus");
myMeta->setInit(tid, "OpusHead\001\002\170\000\200\273\000\000\000\000\000", 19); myMeta->setInit(tid, "OpusHead\001\002\170\000\200\273\000\000\000\000\000", 19);
myMeta->setRate(tid, 48000);
} }
if (trCodec == "PCMA"){myMeta->setCodec(tid, "ALAW");} if (trCodec == "PCMA"){myMeta->setCodec(tid, "ALAW");}
if (trCodec == "PCMU"){myMeta->setCodec(tid, "ULAW");} if (trCodec == "PCMU"){myMeta->setCodec(tid, "ULAW");}
@ -484,7 +493,10 @@ namespace SDP{
myMeta->setCodec(tid, "PCM"); myMeta->setCodec(tid, "PCM");
myMeta->setSize(tid, 24); myMeta->setSize(tid, 24);
} }
if (trCodec == "MPEG4-GENERIC"){myMeta->setCodec(tid, "AAC");} if (trCodec == "MPEG4-GENERIC"){
myMeta->setCodec(tid, "AAC");
myMeta->setSize(tid, 16);
}
if (!myMeta->getCodec(tid).size()){ if (!myMeta->getCodec(tid).size()){
ERROR_MSG("Unsupported RTP mapping: %s", mediaType.c_str()); ERROR_MSG("Unsupported RTP mapping: %s", mediaType.c_str());
}else{ }else{
@ -669,6 +681,9 @@ namespace SDP{
if (M->getType(tid) == "video" || M->getCodec(tid) == "MP2" || M->getCodec(tid) == "MP3"){ if (M->getType(tid) == "video" || M->getCodec(tid) == "MP2" || M->getCodec(tid) == "MP3"){
return 90.0; return 90.0;
} }
if (M->getCodec(tid) == "opus"){
return 48.0;
}
return ((double)M->getRate(tid) / 1000.0); return ((double)M->getRate(tid) / 1000.0);
} }

View file

@ -12,6 +12,8 @@ namespace SDP{
return "H264"; return "H264";
}else if (codec == "VP8"){ }else if (codec == "VP8"){
return "VP8"; return "VP8";
}else if (codec == "VP9"){
return "VP9";
}else if (codec == "AC3"){ }else if (codec == "AC3"){
return "AC3"; return "AC3";
}else if (codec == "PCMA"){ }else if (codec == "PCMA"){
@ -49,6 +51,8 @@ namespace SDP{
return "H264"; return "H264";
}else if (codec == "VP8"){ }else if (codec == "VP8"){
return "VP8"; return "VP8";
}else if (codec == "VP9"){
return "VP9";
}else if (codec == "AC3"){ }else if (codec == "AC3"){
return "AC3"; return "AC3";
}else if (codec == "ALAW"){ }else if (codec == "ALAW"){
@ -184,7 +188,7 @@ namespace SDP{
return 90000; return 90000;
}else if (encodingName == "VP8"){ }else if (encodingName == "VP8"){
return 90000; return 90000;
}else if (encodingName == "vp9"){ }else if (encodingName == "VP9"){
return 90000; return 90000;
} }

View file

@ -110,12 +110,14 @@ int SRTPReader::shutdown(){
int r = 0; int r = 0;
if (session){
srtp_err_status_t status = srtp_dealloc(session); srtp_err_status_t status = srtp_dealloc(session);
if (srtp_err_status_ok != status){ if (srtp_err_status_ok != status){
ERROR_MSG("Failed to cleanly shutdown the SRTP session. Status: %s", ERROR_MSG("Failed to cleanly shutdown the SRTP session. Status: %s",
srtp_status_to_string(status).c_str()); srtp_status_to_string(status).c_str());
r -= 5; r -= 5;
} }
}
memset((void *)&policy, 0x00, sizeof(policy)); memset((void *)&policy, 0x00, sizeof(policy));
memset((char *)&session, 0x00, sizeof(session)); memset((char *)&session, 0x00, sizeof(session));
@ -293,12 +295,14 @@ int SRTPWriter::shutdown(){
int r = 0; int r = 0;
if (session){
srtp_err_status_t status = srtp_dealloc(session); srtp_err_status_t status = srtp_dealloc(session);
if (srtp_err_status_ok != status){ if (srtp_err_status_ok != status){
ERROR_MSG("Failed to cleanly shutdown the SRTP session. Status: %s", ERROR_MSG("Failed to cleanly shutdown the SRTP session. Status: %s",
srtp_status_to_string(status).c_str()); srtp_status_to_string(status).c_str());
r -= 5; r -= 5;
} }
}
memset((char *)&policy, 0x00, sizeof(policy)); memset((char *)&policy, 0x00, sizeof(policy));
memset((char *)&session, 0x00, sizeof(session)); memset((char *)&session, 0x00, sizeof(session));

View file

@ -462,7 +462,7 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
while (!streamAlive(streamname) && ++waiting < 240){ while (!streamAlive(streamname) && ++waiting < 240){
Util::wait(250); Util::wait(250);
if (!Util::Procs::isRunning(pid)){ if (!Util::Procs::isRunning(pid)){
FAIL_MSG("Input process shut down before stream coming online, aborting."); FAIL_MSG("Input process (PID %d) shut down before stream coming online, aborting.", pid);
break; break;
} }
} }
@ -548,7 +548,7 @@ JSON::Value Util::getInputBySource(const std::string &filename, bool isProvider)
/// streamname MUST be pre-sanitized /// streamname MUST be pre-sanitized
/// target gets variables replaced and may be altered by the PUSH_OUT_START trigger response. /// target gets variables replaced and may be altered by the PUSH_OUT_START trigger response.
/// Attempts to match the altered target to an output that can push to it. /// Attempts to match the altered target to an output that can push to it.
pid_t Util::startPush(const std::string &streamname, std::string &target){ pid_t Util::startPush(const std::string &streamname, std::string &target, int debugLvl){
if (Triggers::shouldTrigger("PUSH_OUT_START", streamname)){ if (Triggers::shouldTrigger("PUSH_OUT_START", streamname)){
std::string payload = streamname + "\n" + target; std::string payload = streamname + "\n" + target;
std::string filepath_response = target; std::string filepath_response = target;
@ -562,6 +562,8 @@ pid_t Util::startPush(const std::string &streamname, std::string &target){
// Set original target string in environment // Set original target string in environment
setenv("MST_ORIG_TARGET", target.c_str(), 1); setenv("MST_ORIG_TARGET", target.c_str(), 1);
//If no debug level set, default to level of starting process
if (debugLvl < 0){debugLvl = Util::Config::printDebugLevel;}
// The target can hold variables like current time etc // The target can hold variables like current time etc
streamVariables(target, streamname); streamVariables(target, streamname);
@ -604,9 +606,13 @@ pid_t Util::startPush(const std::string &streamname, std::string &target){
} }
INFO_MSG("Pushing %s to %s through %s", streamname.c_str(), target.c_str(), output_bin.c_str()); INFO_MSG("Pushing %s to %s through %s", streamname.c_str(), target.c_str(), output_bin.c_str());
// Start output. // Start output.
std::string dLvl = JSON::Value(debugLvl).asString();
char *argv[] ={(char *)output_bin.c_str(), (char *)"--stream", (char *)streamname.c_str(), char *argv[] ={(char *)output_bin.c_str(), (char *)"--stream", (char *)streamname.c_str(),
(char *)target.c_str(), (char *)NULL}; (char *)target.c_str(), 0, 0, 0};
if (debugLvl != DEBUG){
argv[4] = (char*)"-g";
argv[5] = (char*)dLvl.c_str();
}
int stdErr = 2; int stdErr = 2;
// Cache return value so we can do some cleaning before we return // Cache return value so we can do some cleaning before we return
pid_t ret = Util::Procs::StartPiped(argv, 0, 0, &stdErr); pid_t ret = Util::Procs::StartPiped(argv, 0, 0, &stdErr);
@ -688,10 +694,10 @@ DTSC::Scan Util::DTSCShmReader::getScan(){
/// Does not do any checks if the protocol supports these tracks, just selects blindly. /// Does not do any checks if the protocol supports these tracks, just selects blindly.
/// It is necessary to follow up with a selectDefaultTracks() call to strip unsupported /// It is necessary to follow up with a selectDefaultTracks() call to strip unsupported
/// codecs/combinations. /// codecs/combinations.
std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackType, const std::string &trackVal){ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, const std::string &trackVal, const std::string &UA){
std::set<size_t> result; std::set<size_t> result;
if (!trackVal.size()){return result;} if (!trackVal.size()){return result;}
if (trackVal == "-1" | trackVal == "none"){return result;}// don't select anything in particular if (trackVal == "-1" || trackVal == "none"){return result;}// don't select anything in particular
if (trackVal.find(',') != std::string::npos){ if (trackVal.find(',') != std::string::npos){
// Comma-separated list, recurse. // Comma-separated list, recurse.
std::stringstream ss(trackVal); std::stringstream ss(trackVal);
@ -708,7 +714,7 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
WARN_MSG("Track %zu does not exist in stream, cannot select", idx); WARN_MSG("Track %zu does not exist in stream, cannot select", idx);
return result; return result;
} }
if (M.getType(idx) != trackType && M.getCodec(idx) != trackType){ if (trackType.size() && M.getType(idx) != trackType && M.getCodec(idx) != trackType){
WARN_MSG("Track %zu is not %s (%s/%s), cannot select", idx, trackType.c_str(), WARN_MSG("Track %zu is not %s (%s/%s), cannot select", idx, trackType.c_str(),
M.getType(idx).c_str(), M.getCodec(idx).c_str()); M.getType(idx).c_str(), M.getCodec(idx).c_str());
return result; return result;
@ -720,23 +726,22 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
Util::stringToLower(trackLow); Util::stringToLower(trackLow);
if (trackLow == "all" || trackLow == "*"){ if (trackLow == "all" || trackLow == "*"){
// select all tracks of this type // select all tracks of this type
std::set<size_t> validTracks = M.getValidTracks(); std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getType(*it) == trackType || M.getCodec(*it) == trackType){result.insert(*it);} if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){result.insert(*it);}
} }
return result; return result;
} }
if (trackLow == "highbps" || trackLow == "bestbps" || trackLow == "maxbps"){ if (trackLow == "highbps" || trackLow == "bestbps" || trackLow == "maxbps"){
// select highest bit rate track of this type // select highest bit rate track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID; size_t currVal = INVALID_TRACK_ID;
uint32_t currRate = 0; uint32_t currRate = 0;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ if (currRate < M.getBps(*it)){
if (currRate < Trk.bps){
currVal = *it; currVal = *it;
currRate = Trk.bps; currRate = M.getBps(*it);
} }
} }
} }
@ -745,15 +750,14 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
} }
if (trackLow == "lowbps" || trackLow == "worstbps" || trackLow == "minbps"){ if (trackLow == "lowbps" || trackLow == "worstbps" || trackLow == "minbps"){
// select lowest bit rate track of this type // select lowest bit rate track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID; size_t currVal = INVALID_TRACK_ID;
uint32_t currRate = 0xFFFFFFFFul; uint32_t currRate = 0xFFFFFFFFul;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ if (currRate > M.getBps(*it)){
if (currRate > Trk.bps){
currVal = *it; currVal = *it;
currRate = Trk.bps; currRate = M.getBps(*it);
} }
} }
} }
@ -764,33 +768,19 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
if (trackLow[0] == '<' || trackLow[0] == '>'){ if (trackLow[0] == '<' || trackLow[0] == '>'){
unsigned int bpsVal; unsigned int bpsVal;
uint64_t targetBps = 0; uint64_t targetBps = 0;
if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), "<%ubps", &bpsVal) == 1){ if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), "<%ubps", &bpsVal) == 1){targetBps = bpsVal;}
targetBps = bpsVal; if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), "<%ukbps", &bpsVal) == 1){targetBps = bpsVal*1024;}
} if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), "<%umbps", &bpsVal) == 1){targetBps = bpsVal*1024*1024;}
if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), "<%ukbps", &bpsVal) == 1){ if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), ">%ubps", &bpsVal) == 1){targetBps = bpsVal;}
targetBps = bpsVal * 1024; if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), ">%ukbps", &bpsVal) == 1){targetBps = bpsVal*1024;}
} if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), ">%umbps", &bpsVal) == 1){targetBps = bpsVal*1024*1024;}
if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), "<%umbps", &bpsVal) == 1){
targetBps = bpsVal * 1024 * 1024;
}
if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), ">%ubps", &bpsVal) == 1){
targetBps = bpsVal;
}
if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), ">%ukbps", &bpsVal) == 1){
targetBps = bpsVal * 1024;
}
if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), ">%umbps", &bpsVal) == 1){
targetBps = bpsVal * 1024 * 1024;
}
if (targetBps){ if (targetBps){
targetBps >>= 3;
// select all tracks of this type that match the requirements // select all tracks of this type that match the requirements
std::set<size_t> validTracks = getSupportedTracks(M, capa); std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ if (trackLow[0] == '>' && M.getBps(*it) > targetBps){result.insert(*it);}
if (trackLow[0] == '>' && Trk.bps > targetBps){result.insert(*it);} if (trackLow[0] == '<' && M.getBps(*it) < targetBps){result.insert(*it);}
if (trackLow[0] == '<' && Trk.bps < targetBps){result.insert(*it);}
} }
} }
return result; return result;
@ -800,11 +790,10 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
if (sscanf(trackLow.c_str(), "<%ux%u", &resX, &resY) == 2){targetArea = resX*resY;} if (sscanf(trackLow.c_str(), "<%ux%u", &resX, &resY) == 2){targetArea = resX*resY;}
if (sscanf(trackLow.c_str(), ">%ux%u", &resX, &resY) == 2){targetArea = resX*resY;} if (sscanf(trackLow.c_str(), ">%ux%u", &resX, &resY) == 2){targetArea = resX*resY;}
if (targetArea){ if (targetArea){
std::set<size_t> validTracks = getSupportedTracks(M, capa); std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ uint64_t trackArea = M.getWidth(*it)*M.getHeight(*it);
uint64_t trackArea = Trk.width * Trk.height;
if (trackLow[0] == '>' && trackArea > targetArea){result.insert(*it);} if (trackLow[0] == '>' && trackArea > targetArea){result.insert(*it);}
if (trackLow[0] == '<' && trackArea < targetArea){result.insert(*it);} if (trackLow[0] == '<' && trackArea < targetArea){result.insert(*it);}
} }
@ -816,28 +805,19 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
{ {
unsigned int bpsVal; unsigned int bpsVal;
uint64_t targetBps = 0; uint64_t targetBps = 0;
if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), "%ubps", &bpsVal) == 1){ if (trackLow.find("bps") != std::string::npos && sscanf(trackLow.c_str(), "%ubps", &bpsVal) == 1){targetBps = bpsVal;}
targetBps = bpsVal; if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), "%ukbps", &bpsVal) == 1){targetBps = bpsVal*1024;}
} if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), "%umbps", &bpsVal) == 1){targetBps = bpsVal*1024*1024;}
if (trackLow.find("kbps") != std::string::npos && sscanf(trackLow.c_str(), "%ukbps", &bpsVal) == 1){
targetBps = bpsVal * 1024;
}
if (trackLow.find("mbps") != std::string::npos && sscanf(trackLow.c_str(), "%umbps", &bpsVal) == 1){
targetBps = bpsVal * 1024 * 1024;
}
if (targetBps){ if (targetBps){
targetBps >>= 3;
// select nearest bit rate track of this type // select nearest bit rate track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID; size_t currVal = INVALID_TRACK_ID;
uint32_t currDist = 0; uint32_t currDist = 0;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ if (currVal == INVALID_TRACK_ID || (M.getBps(*it) >= targetBps && currDist > (M.getBps(*it)-targetBps)) || (M.getBps(*it) < targetBps && currDist > (targetBps-M.getBps(*it)))){
if (currVal == INVALID_TRACK_ID || (Trk.bps >= targetBps && currDist > (Trk.bps - targetBps)) ||
(Trk.bps < targetBps && currDist > (targetBps - Trk.bps))){
currVal = *it; currVal = *it;
currDist = (Trk.bps >= targetBps) ? (Trk.bps - targetBps) : (targetBps - Trk.bps); currDist = (M.getBps(*it) >= targetBps)?(M.getBps(*it)-targetBps):(targetBps-M.getBps(*it));
} }
} }
} }
@ -880,13 +860,12 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
if (!trackType.size() || trackType == "video"){ if (!trackType.size() || trackType == "video"){
if (trackLow == "highres" || trackLow == "bestres" || trackLow == "maxres"){ if (trackLow == "highres" || trackLow == "bestres" || trackLow == "maxres"){
// select highest resolution track of this type // select highest resolution track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID; size_t currVal = INVALID_TRACK_ID;
uint64_t currRes = 0; uint64_t currRes = 0;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ uint64_t trackRes = M.getWidth(*it)*M.getHeight(*it);
uint64_t trackRes = Trk.width * Trk.height;
if (currRes < trackRes){ if (currRes < trackRes){
currVal = *it; currVal = *it;
currRes = trackRes; currRes = trackRes;
@ -898,13 +877,12 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
} }
if (trackLow == "lowres" || trackLow == "worstres" || trackLow == "minres"){ if (trackLow == "lowres" || trackLow == "worstres" || trackLow == "minres"){
// select lowest resolution track of this type // select lowest resolution track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID; size_t currVal = INVALID_TRACK_ID;
uint64_t currRes = 0xFFFFFFFFFFFFFFFFull; uint64_t currRes = 0xFFFFFFFFFFFFFFFFull;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ uint64_t trackRes = M.getWidth(*it)*M.getHeight(*it);
uint64_t trackRes = Trk.width * Trk.height;
if (currRes > trackRes){ if (currRes > trackRes){
currVal = *it; currVal = *it;
currRes = trackRes; currRes = trackRes;
@ -918,16 +896,14 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
unsigned int resX, resY; unsigned int resX, resY;
if (sscanf(trackLow.c_str(), "~%ux%u", &resX, &resY) == 2){ if (sscanf(trackLow.c_str(), "~%ux%u", &resX, &resY) == 2){
// select nearest resolution track of this type // select nearest resolution track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID; size_t currVal = INVALID_TRACK_ID;
uint64_t currDist = 0; uint64_t currDist = 0;
uint64_t targetArea = resX*resY; uint64_t targetArea = resX*resY;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it); if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){ uint64_t trackArea = M.getWidth(*it)*M.getHeight(*it);
uint64_t trackArea = Trk.width * Trk.height; if (currVal == INVALID_TRACK_ID || (trackArea >= targetArea && currDist > (trackArea-targetArea)) || (trackArea < targetArea && currDist > (targetArea-trackArea))){
if (currVal == INVALID_TRACK_ID || (trackArea >= targetArea && currDist > (trackArea - targetArea)) ||
(trackArea < targetArea && currDist > (targetArea - trackArea))){
currVal = *it; currVal = *it;
currDist = (trackArea >= targetArea)?(trackArea-targetArea):(targetArea-trackArea); currDist = (trackArea >= targetArea)?(trackArea-targetArea):(targetArea-trackArea);
} }
@ -941,12 +917,26 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
// attempt to do language/codec matching // attempt to do language/codec matching
// convert 2-character language codes into 3-character language codes // convert 2-character language codes into 3-character language codes
if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);} if (trackLow.size() == 2){trackLow = Encodings::ISO639::twoToThree(trackLow);}
std::set<size_t> validTracks = M.getValidTracks(); std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getType(*it) == trackType || M.getCodec(*it) == trackType){ if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
std::string codecLow = M.getCodec(*it); std::string codecLow = M.getCodec(*it);
Util::stringToLower(codecLow); Util::stringToLower(codecLow);
if (M.getLang(*it) == trackLow || trackLow == codecLow){result.insert(*it);} if (M.getLang(*it) == trackLow || trackLow == codecLow){result.insert(*it);}
if (!trackType.size() || trackType == "video"){
unsigned int resX, resY;
if (trackLow == "720p" && M.getWidth(*it) == 1280 && M.getHeight(*it) == 720){result.insert(*it);}
if (trackLow == "1080p" && M.getWidth(*it) == 1920 && M.getHeight(*it) == 1080){result.insert(*it);}
if (trackLow == "1440p" && M.getWidth(*it) == 2560 && M.getHeight(*it) == 1440){result.insert(*it);}
if (trackLow == "2k" && M.getWidth(*it) == 2048 && M.getHeight(*it) == 1080){result.insert(*it);}
if (trackLow == "4k" && M.getWidth(*it) == 3840 && M.getHeight(*it) == 2160){result.insert(*it);}
if (trackLow == "5k" && M.getWidth(*it) == 5120 && M.getHeight(*it) == 2880){result.insert(*it);}
if (trackLow == "8k" && M.getWidth(*it) == 7680 && M.getHeight(*it) == 4320){result.insert(*it);}
//match "XxY" format
if (sscanf(trackLow.c_str(), "%ux%u", &resX, &resY) == 2){
if (M.getWidth(*it) == resX && M.getHeight(*it) == resY){result.insert(*it);}
}
}
} }
} }
return result; return result;
@ -1021,7 +1011,7 @@ std::set<size_t> Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value
std::string encryptionType = M.getEncryption(*it); std::string encryptionType = M.getEncryption(*it);
encryptionType = encryptionType.substr(0, encryptionType.find('/')); encryptionType = encryptionType.substr(0, encryptionType.find('/'));
bool found = false; bool found = false;
jsonForEach(capa["encryption"], itb){ jsonForEachConst(capa["encryption"], itb){
if (itb->asStringRef() == encryptionType){ if (itb->asStringRef() == encryptionType){
found = true; found = true;
break; break;
@ -1067,7 +1057,8 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
} }
/*LTS-END*/ /*LTS-END*/
std::set<size_t> validTracks = getSupportedTracks(M, capa); std::set<size_t> validTracks = M.getValidTracks();
if (capa){validTracks = getSupportedTracks(M, capa);}
// check which tracks don't actually exist // check which tracks don't actually exist
std::set<size_t> toRemove; std::set<size_t> toRemove;
@ -1076,10 +1067,6 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
toRemove.insert(*it); toRemove.insert(*it);
continue; continue;
} }
// autoSeeking and target not in bounds? Drop it too.
if (seekTarget && M.tracks.at(*it).lastms < std::max(seekTarget, (uint64_t)6000lu) - 6000){
toRemove.insert(*it);
}
} }
// remove those from selectedtracks // remove those from selectedtracks
for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){ for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
@ -1103,21 +1090,21 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
/*LTS-START*/ /*LTS-START*/
if (!capa.isMember("codecs")){ if (!capa.isMember("codecs")){
for (std::set<size_t>::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){ for (std::set<size_t>::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){
const DTSC::Track &Trk = M.tracks.at(*trit);
bool problems = false; bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ if (capa.isMember("exceptions") && capa["exceptions"].isObject() &&
capa["exceptions"].size()){
jsonForEachConst(capa["exceptions"], ex){ jsonForEachConst(capa["exceptions"], ex){
if (ex.key() == "codec:" + Trk.codec){ if (ex.key() == "codec:" + M.getCodec(*trit)){
problems = !Util::checkException(*ex, UA); problems = !Util::checkException(*ex, UA);
break; break;
} }
} }
} }
// if (!allowBFrames && M.hasBFrames(*trit)){problems = true;} if (!allowBFrames && M.hasBFrames(*trit)){problems = true;}
if (problems){continue;} if (problems){continue;}
if (noSelAudio && Trk.type == "audio"){continue;} if (noSelAudio && M.getType(*trit) == "audio"){continue;}
if (noSelVideo && Trk.type == "video"){continue;} if (noSelVideo && M.getType(*trit) == "video"){continue;}
if (noSelSub && (Trk.type == "subtitle" || Trk.codec == "subtitle")){continue;} if (noSelSub && (M.getType(*trit) == "subtitle" || M.getCodec(*trit) == "subtitle")){continue;}
result.insert(*trit); result.insert(*trit);
} }
return result; return result;

View file

@ -9,6 +9,8 @@
#include "util.h" #include "util.h"
#include <string> #include <string>
const JSON::Value empty;
namespace Util{ namespace Util{
void streamVariables(std::string &str, const std::string &streamname, const std::string &source = ""); void streamVariables(std::string &str, const std::string &streamname, const std::string &source = "");
std::string getTmpFolder(); std::string getTmpFolder();
@ -18,7 +20,7 @@ namespace Util{
bool isProvider = false, bool isProvider = false,
const std::map<std::string, std::string> &overrides = std::map<std::string, std::string>(), const std::map<std::string, std::string> &overrides = std::map<std::string, std::string>(),
pid_t *spawn_pid = NULL); pid_t *spawn_pid = NULL);
int startPush(const std::string &streamname, std::string &target); int startPush(const std::string &streamname, std::string &target, int debugLvl = -1);
JSON::Value getStreamConfig(const std::string &streamname); JSON::Value getStreamConfig(const std::string &streamname);
JSON::Value getGlobalConfig(const std::string &optionName); JSON::Value getGlobalConfig(const std::string &optionName);
JSON::Value getInputBySource(const std::string &filename, bool isProvider = false); JSON::Value getInputBySource(const std::string &filename, bool isProvider = false);
@ -26,13 +28,13 @@ namespace Util{
bool checkException(const JSON::Value &ex, const std::string &useragent); bool checkException(const JSON::Value &ex, const std::string &useragent);
std::string codecString(const std::string &codec, const std::string &initData = ""); std::string codecString(const std::string &codec, const std::string &initData = "");
std::set<size_t> getSupportedTracks(const DTSC::Meta &M, JSON::Value &capa, std::set<size_t> getSupportedTracks(const DTSC::Meta &M, const JSON::Value &capa,
const std::string &type = "", const std::string &UA = ""); const std::string &type = "", const std::string &UA = "");
std::set<size_t> findTracks(const DTSC::Meta &M, const std::string &trackType, const std::string &trackVal); std::set<size_t> findTracks(const DTSC::Meta &M, const JSON::Value &capa, const std::string &trackType, const std::string &trackVal, const std::string &UA = "");
std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::string &trackSelector = "", std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::string &trackSelector = "",
JSON::Value capa = JSON::Value(), const std::string &UA = ""); const JSON::Value &capa = empty, const std::string &UA = "");
std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::map<std::string, std::string> &targetParams, std::set<size_t> wouldSelect(const DTSC::Meta &M, const std::map<std::string, std::string> &targetParams,
JSON::Value capa = JSON::Value(), const std::string &UA = ""); const JSON::Value &capa = empty, const std::string &UA = "", uint64_t seekTarget = 0);
class DTSCShmReader{ class DTSCShmReader{
public: public:

View file

@ -1162,6 +1162,14 @@ namespace TS{
return output.str(); return output.str();
} }
size_t getUniqTrackID(const DTSC::Meta &M, size_t idx){
return idx+255;
//size_t ret = M.getID(idx);
//if (ret < 255){ret += 255;}
//return ret;
}
/// Construct a PMT (special 188B ts packet) from a set of selected tracks and metadata. /// Construct a PMT (special 188B ts packet) from a set of selected tracks and metadata.
/// This function is not part of the packet class, but it is in the TS namespace. /// This function is not part of the packet class, but it is in the TS namespace.
/// It uses an internal static TS packet for PMT storage. /// It uses an internal static TS packet for PMT storage.
@ -1201,16 +1209,12 @@ namespace TS{
} }
} }
if (vidTrack == -1){vidTrack = *(selectedTracks.begin());} if (vidTrack == -1){vidTrack = *(selectedTracks.begin());}
size_t pcrPid = M.getID(vidTrack); PMT.setPCRPID(getUniqTrackID(M, vidTrack));
if (pcrPid < 255){pcrPid += 255;}
PMT.setPCRPID(pcrPid);
PMT.setProgramInfoLength(0); PMT.setProgramInfoLength(0);
ProgramMappingEntry entry = PMT.getEntry(0); ProgramMappingEntry entry = PMT.getEntry(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++){
std::string codec = M.getCodec(*it); std::string codec = M.getCodec(*it);
size_t pkgId = M.getID(*it); entry.setElementaryPid(getUniqTrackID(M, *it));
if (pkgId < 255){pkgId += 255;}
entry.setElementaryPid(pkgId);
entry.setESInfo(""); entry.setESInfo("");
if (codec == "H264"){ if (codec == "H264"){
entry.setStreamType(0x1B); entry.setStreamType(0x1B);

View file

@ -245,6 +245,8 @@ namespace TS{
extern char PAT[188]; extern char PAT[188];
size_t getUniqTrackID(const DTSC::Meta &M, size_t idx);
const char *createPMT(std::set<unsigned long> &selectedTracks, const DTSC::Meta &M, int contCounter = 0); const char *createPMT(std::set<unsigned long> &selectedTracks, const DTSC::Meta &M, int contCounter = 0);
const char *createSDT(const std::string &streamName, int contCounter = 0); const char *createSDT(const std::string &streamName, int contCounter = 0);

View file

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <stdlib.h> #include <stdlib.h>
#include <string> #include <string>
#include <inttypes.h>
/// Holds all HTTP processing related code. /// Holds all HTTP processing related code.
namespace HTTP{ namespace HTTP{

View file

@ -276,7 +276,7 @@ namespace Util{
/// Parses log messages from the given file descriptor in, printing them to out, optionally /// Parses log messages from the given file descriptor in, printing them to out, optionally
/// calling the given callback for each valid message. Closes the file descriptor on read error /// calling the given callback for each valid message. Closes the file descriptor on read error
void logParser(int in, int out, bool colored, void logParser(int in, int out, bool colored,
void callback(const std::string &, const std::string &, const std::string &, bool)){ void callback(const std::string &, const std::string &, const std::string &, uint64_t, bool)){
char buf[1024]; char buf[1024];
FILE *output = fdopen(in, "r"); FILE *output = fdopen(in, "r");
@ -347,7 +347,7 @@ namespace Util{
while (j < 1023 && buf[j] != '\n' && buf[j] != 0){++j;} while (j < 1023 && buf[j] != '\n' && buf[j] != 0){++j;}
buf[j] = 0; buf[j] = 0;
// print message // print message
if (callback){callback(kind, message, strmNm, true);} if (callback){callback(kind, message, strmNm, JSON::Value(progpid).asInt(), true);}
color_msg = color_end; color_msg = color_end;
if (colored){ if (colored){
if (!strcmp(kind, "CONF")){color_msg = CONF_msg;} if (!strcmp(kind, "CONF")){color_msg = CONF_msg;}
@ -386,9 +386,11 @@ namespace Util{
uint64_t FieldAccX::uint(size_t recordNo) const{return src->getInt(field, recordNo);} uint64_t FieldAccX::uint(size_t recordNo) const{return src->getInt(field, recordNo);}
std::string FieldAccX::string(size_t recordNo) const{ std::string FieldAccX::string(size_t recordNo) const{
std::string res(src->getPointer(field, recordNo)); return std::string(src->getPointer(field, recordNo));
if (res.size() > field.size){res.resize(field.size);} }
return res;
const char * FieldAccX::ptr(size_t recordNo) const{
return src->getPointer(field, recordNo);
} }
void FieldAccX::set(uint64_t val, size_t recordNo){src->setInt(field, val, recordNo);} void FieldAccX::set(uint64_t val, size_t recordNo){src->setInt(field, val, recordNo);}
@ -396,8 +398,10 @@ namespace Util{
void FieldAccX::set(const std::string &val, size_t recordNo){ void FieldAccX::set(const std::string &val, size_t recordNo){
char *place = src->getPointer(field, recordNo); char *place = src->getPointer(field, recordNo);
memcpy(place, val.data(), std::min((size_t)field.size, val.size())); memcpy(place, val.data(), std::min((size_t)field.size, val.size()));
if ((field.type & 0xF0) == RAX_STRING){
place[std::min((size_t)field.size - 1, val.size())] = 0; place[std::min((size_t)field.size - 1, val.size())] = 0;
} }
}
/// If waitReady is true (default), waits for isReady() to return true in 50ms sleep increments. /// If waitReady is true (default), waits for isReady() to return true in 50ms sleep increments.
RelAccX::RelAccX(char *data, bool waitReady){ RelAccX::RelAccX(char *data, bool waitReady){
@ -597,7 +601,7 @@ namespace Util{
char *ptr = getPointer(it->first, i); char *ptr = getPointer(it->first, i);
size_t sz = getSize(it->first, i); size_t sz = getSize(it->first, i);
size_t zeroCount = 0; size_t zeroCount = 0;
for (size_t j = 0; j < sz && j < 100 && zeroCount < 10; ++j){ for (size_t j = 0; j < sz && j < 100 && zeroCount < 16; ++j){
r << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)ptr[j] << std::dec << " "; r << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)ptr[j] << std::dec << " ";
if (ptr[j] == 0x00){ if (ptr[j] == 0x00){
zeroCount++; zeroCount++;
@ -770,13 +774,13 @@ namespace Util{
} }
void RelAccX::setString(const RelAccXFieldData &fd, const std::string &val, uint64_t recordNo){ void RelAccX::setString(const RelAccXFieldData &fd, const std::string &val, uint64_t recordNo){
if ((fd.type & 0xF0) != RAX_STRING){ if ((fd.type & 0xF0) != RAX_STRING && (fd.type & 0xF0) != RAX_RAW){
WARN_MSG("Setting non-string"); WARN_MSG("Setting non-string data type to a string value");
return; return;
} }
char *ptr = RECORD_POINTER; char *ptr = RECORD_POINTER;
memcpy(ptr, val.data(), std::min((uint32_t)val.size(), fd.size)); memcpy(ptr, val.data(), std::min((uint32_t)val.size(), fd.size));
ptr[std::min((uint32_t)val.size(), fd.size - 1)] = 0; if ((fd.type & 0xF0) == RAX_STRING){ptr[std::min((uint32_t)val.size(), fd.size - 1)] = 0;}
} }
/// Writes the given int to the given field in the given record. /// Writes the given int to the given field in the given record.

View file

@ -20,7 +20,7 @@ namespace Util{
class DataCallback{ class DataCallback{
public: public:
virtual void dataCallback(const char *ptr, size_t size){ virtual void dataCallback(const char *ptr, size_t size){
INFO_MSG("default callback, size: %llu", size); INFO_MSG("default callback, size: %zu", size);
} }
}; };
@ -54,7 +54,7 @@ namespace Util{
}; };
void logParser(int in, int out, bool colored, void logParser(int in, int out, bool colored,
void callback(const std::string &, const std::string &, const std::string &, bool) = 0); void callback(const std::string &, const std::string &, const std::string &, uint64_t, bool) = 0);
void redirectLogsIfNeeded(); void redirectLogsIfNeeded();
/// Holds type, size and offset for RelAccX class internal data fields. /// Holds type, size and offset for RelAccX class internal data fields.
@ -194,6 +194,7 @@ namespace Util{
FieldAccX(RelAccX *_src = NULL, RelAccXFieldData _field = RelAccXFieldData()); FieldAccX(RelAccX *_src = NULL, RelAccXFieldData _field = RelAccXFieldData());
uint64_t uint(size_t recordNo) const; uint64_t uint(size_t recordNo) const;
std::string string(size_t recordNo) const; std::string string(size_t recordNo) const;
const char * ptr(size_t recordNo) const;
void set(uint64_t val, size_t recordNo = 0); void set(uint64_t val, size_t recordNo = 0);
void set(const std::string &val, size_t recordNo = 0); void set(const std::string &val, size_t recordNo = 0);

View file

@ -9,6 +9,10 @@ void AnalyserDTSC::init(Util::Config &conf){
opt["short"] = "H"; opt["short"] = "H";
opt["help"] = "Parse entire file or streams as a single headless DTSC packet"; opt["help"] = "Parse entire file or streams as a single headless DTSC packet";
conf.addOption("headless", opt); conf.addOption("headless", opt);
opt["long"] = "sizeprepended";
opt["short"] = "s";
opt["help"] = "If set, data of packets is considered to be size-prepended";
conf.addOption("sizeprepended", opt);
opt.null(); opt.null();
} }
@ -21,6 +25,7 @@ bool AnalyserDTSC::open(const std::string &filename){
AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){ AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){
headLess = conf.getBool("headless"); headLess = conf.getBool("headless");
sizePrepended = conf.getBool("sizeprepended");
} }
bool AnalyserDTSC::parsePacket(){ bool AnalyserDTSC::parsePacket(){
@ -60,10 +65,19 @@ bool AnalyserDTSC::parsePacket(){
char *payDat; char *payDat;
size_t payLen; size_t payLen;
P.getString("data", payDat, payLen); P.getString("data", payDat, payLen);
uint32_t currLen = 0;
uint64_t byteCounter = 0;
for (uint64_t i = 0; i < payLen; ++i){ for (uint64_t i = 0; i < payLen; ++i){
if ((i % 32) == 0){std::cout << std::endl;} if (sizePrepended && !currLen){
std::cout << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i]; currLen = 4+Bit::btohl(payDat+i);
byteCounter = 0;
} }
if ((byteCounter % 32) == 0){std::cout << std::endl;}
std::cout << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
++byteCounter;
--currLen;
}
std::cout << std::dec << std::endl;
} }
break; break;
} }

View file

@ -10,6 +10,7 @@ public:
private: private:
bool headLess; bool headLess;
bool sizePrepended;
DTSC::Packet P; DTSC::Packet P;
Socket::Connection conn; Socket::Connection conn;
uint64_t totalBytes; uint64_t totalBytes;

View file

@ -16,6 +16,11 @@ void AnalyserRTMP::init(Util::Config &conf){
opt.null(); opt.null();
} }
/// Checks if standard input is still valid.
bool AnalyserRTMP::isOpen(){
return (*isActive) && (std::cin.good() || strbuf.size());
}
AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){ AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){
if (conf.getString("reconstruct") != ""){ if (conf.getString("reconstruct") != ""){
reconstruct.open(conf.getString("reconstruct").c_str()); reconstruct.open(conf.getString("reconstruct").c_str());
@ -43,7 +48,10 @@ bool AnalyserRTMP::parsePacket(){
// While we can't parse a packet, // While we can't parse a packet,
while (!next.Parse(strbuf)){ while (!next.Parse(strbuf)){
// fill our internal buffer "strbuf" in (up to) 1024 byte chunks // fill our internal buffer "strbuf" in (up to) 1024 byte chunks
if (!std::cin.good()){return false;} if (!std::cin.good()){
strbuf.clear();
return false;
}
size_t charCount = 0; size_t charCount = 0;
std::string tmpbuffer; std::string tmpbuffer;
tmpbuffer.reserve(1024); tmpbuffer.reserve(1024);

View file

@ -18,4 +18,5 @@ public:
static void init(Util::Config &conf); static void init(Util::Config &conf);
bool parsePacket(); bool parsePacket();
virtual bool open(const std::string &filename); virtual bool open(const std::string &filename);
virtual bool isOpen();
}; };

View file

@ -343,7 +343,7 @@ int main_loop(int argc, char **argv){
WARN_MSG("You have very little free RAM available (%" PRIu64 WARN_MSG("You have very little free RAM available (%" PRIu64
" MiB). While Mist will run just fine with this amount, do note that random crashes " " MiB). While Mist will run just fine with this amount, do note that random crashes "
"may occur should you ever run out of free RAM. Please be pro-active and keep an " "may occur should you ever run out of free RAM. Please be pro-active and keep an "
"eye on the RAM usage!"); "eye on the RAM usage!", (mem_free + mem_bufcache)/1024);
} }
if (shm_free < 1024 * 1024 && mem_total > 1024 * 1024 * 1.12){ if (shm_free < 1024 * 1024 && mem_total > 1024 * 1024 * 1.12){
WARN_MSG("You have very little shared memory available (%" PRIu64 WARN_MSG("You have very little shared memory available (%" PRIu64

View file

@ -502,6 +502,12 @@ void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){
Controller::triggerStats[Request["trigger_fail"].asStringRef()].failCount++; Controller::triggerStats[Request["trigger_fail"].asStringRef()].failCount++;
return; return;
} }
if (Request.isMember("push_status_update")){
JSON::Value &statUp = Request["push_status_update"];
if (statUp.isMember("id") && statUp.isMember("status")){
setPushStatus(statUp["id"].asInt(), statUp["status"]);
}
}
/*LTS-END*/ /*LTS-END*/
// Parse config and streams from the request. // Parse config and streams from the request.
if (Request.isMember("config") && Request["config"].isObject()){ if (Request.isMember("config") && Request["config"].isObject()){

View file

@ -200,6 +200,12 @@ namespace Controller{
trgs["DEFAULT_STREAM"]["response_action"] = trgs["DEFAULT_STREAM"]["response_action"] =
"Overrides the default stream setting (for this view) to the response value. If empty, " "Overrides the default stream setting (for this view) to the response value. If empty, "
"fails loading the stream and returns an error to the viewer/user."; "fails loading the stream and returns an error to the viewer/user.";
trgs["PUSH_END"]["when"] = "Every time a push stops, for any reason";
trgs["PUSH_END"]["stream_specific"] = true;
trgs["PUSH_END"]["payload"] = "push ID (integer)\nstream name (string)\ntarget URI, before variables/triggers affected it (string)\ntarget URI, afterwards, as actually used (string)\nlast 10 log messages (JSON array string)\nmost recent push status (JSON object string)";
trgs["PUSH_END"]["response"] = "ignored";
trgs["PUSH_END"]["response_action"] = "None.";
} }
/// Aquire list of available protocols, storing in global 'capabilities' JSON::Value. /// Aquire list of available protocols, storing in global 'capabilities' JSON::Value.

View file

@ -164,8 +164,6 @@ namespace Controller{
std::set<std::string> runningConns; std::set<std::string> runningConns;
// used for building args // used for building args
int zero = 0;
int out = fileno(stdout);
int err = fileno(stderr); int err = fileno(stderr);
char *argarr[15]; // approx max # of args (with a wide margin) char *argarr[15]; // approx max # of args (with a wide margin)
int i; int i;
@ -259,7 +257,7 @@ namespace Controller{
JSON::Value p = JSON::fromString(*runningConns.begin()); JSON::Value p = JSON::fromString(*runningConns.begin());
buildPipedArguments(p, (char **)&argarr, capabilities); buildPipedArguments(p, (char **)&argarr, capabilities);
// start piped w/ generated args // start piped w/ generated args
currentConnectors[*runningConns.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err); currentConnectors[*runningConns.begin()] = Util::Procs::StartPiped(argarr, 0, 0, &err);
Triggers::doTrigger("OUTPUT_START", *runningConns.begin()); // LTS Triggers::doTrigger("OUTPUT_START", *runningConns.begin()); // LTS
} }
runningConns.erase(runningConns.begin()); runningConns.erase(runningConns.begin());

View file

@ -7,6 +7,7 @@
#include <mist/procs.h> #include <mist/procs.h>
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/tinythread.h> #include <mist/tinythread.h>
#include <mist/triggers.h>
#include <string> #include <string>
namespace Controller{ namespace Controller{
@ -38,6 +39,38 @@ namespace Controller{
} }
} }
void setPushStatus(uint64_t id, const JSON::Value & status){
if (!activePushes.count(id)){return;}
activePushes[id][5].extend(status);
}
void pushLogMessage(uint64_t id, const JSON::Value & msg){
JSON::Value &log = activePushes[id][4];
log.append(msg);
log.shrink(10);
}
bool isPushActive(uint64_t id){
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
return activePushes.count(id);
}
/// Only used internally, to remove pushes
static void removeActivePush(pid_t id){
//ignore if the push does not exist
if (!activePushes.count(id)){return;}
JSON::Value p = activePushes[id];
if (Triggers::shouldTrigger("PUSH_END", p[1].asStringRef())){
std::string payload = p[0u].asString() + "\n" + p[1u].asString() + "\n" + p[2u].asString() + "\n" + p[3u].asString() + "\n" + p[4u].toString() + "\n" + p[5u].toString();
Triggers::doTrigger("PUSH_END", payload, p[1].asStringRef());
}
//actually remove, make sure next pass the new list is written out too
activePushes.erase(id);
mustWritePushList = true;
}
/// Returns true if the push is currently active, false otherwise. /// Returns true if the push is currently active, false otherwise.
bool isPushActive(const std::string &streamname, const std::string &target){ bool isPushActive(const std::string &streamname, const std::string &target){
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);} while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
@ -52,8 +85,7 @@ namespace Controller{
} }
} }
while (toWipe.size()){ while (toWipe.size()){
activePushes.erase(*toWipe.begin()); removeActivePush(*toWipe.begin());
mustWritePushList = true;
toWipe.erase(toWipe.begin()); toWipe.erase(toWipe.begin());
} }
return false; return false;
@ -75,8 +107,7 @@ namespace Controller{
} }
} }
while (toWipe.size()){ while (toWipe.size()){
activePushes.erase(*toWipe.begin()); removeActivePush(*toWipe.begin());
mustWritePushList = true;
toWipe.erase(toWipe.begin()); toWipe.erase(toWipe.begin());
} }
} }
@ -198,6 +229,17 @@ namespace Controller{
break; break;
} }
} }
//Check if any pushes have ended, clean them up
std::set<pid_t> toWipe;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
if (!Util::Procs::isActive(it->first)){toWipe.insert(it->first);}
}
while (toWipe.size()){
removeActivePush(*toWipe.begin());
toWipe.erase(toWipe.begin());
mustWritePushList = true;
}
//write push list to shared memory, for restarting/crash recovery/etc
if (mustWritePushList && pushPage.mapped){ if (mustWritePushList && pushPage.mapped){
writePushList(pushPage.mapped); writePushList(pushPage.mapped);
mustWritePushList = false; mustWritePushList = false;
@ -227,8 +269,7 @@ namespace Controller{
} }
} }
while (toWipe.size()){ while (toWipe.size()){
activePushes.erase(*toWipe.begin()); removeActivePush(*toWipe.begin());
mustWritePushList = true;
toWipe.erase(toWipe.begin()); toWipe.erase(toWipe.begin());
} }
} }

View file

@ -8,6 +8,9 @@ namespace Controller{
void startPush(const std::string &streamname, std::string &target); void startPush(const std::string &streamname, std::string &target);
void stopPush(unsigned int ID); void stopPush(unsigned int ID);
void listPush(JSON::Value &output); void listPush(JSON::Value &output);
void pushLogMessage(uint64_t id, const JSON::Value & msg);
void setPushStatus(uint64_t id, const JSON::Value & status);
bool isPushActive(uint64_t id);
// Functions for automated pushes, add/remove // Functions for automated pushes, add/remove
void addPush(JSON::Value &request); void addPush(JSON::Value &request);

View file

@ -50,6 +50,7 @@ bool Controller::killOnExit = KILL_ON_EXIT;
tthread::mutex Controller::statsMutex; tthread::mutex Controller::statsMutex;
unsigned int Controller::maxConnsPerIP = 0; unsigned int Controller::maxConnsPerIP = 0;
uint64_t Controller::statDropoff = 0; uint64_t Controller::statDropoff = 0;
static uint64_t cpu_use = 0;
char noBWCountMatches[1717]; char noBWCountMatches[1717];
uint64_t bwLimit = 128 * 1024 * 1024; // gigabit default limit uint64_t bwLimit = 128 * 1024 * 1024; // gigabit default limit
@ -76,6 +77,21 @@ void Controller::updateBandwidthConfig(){
} }
} }
} }
//Localhost is always excepted from counts
{
std::string newbins = Socket::getBinForms("::1");
if (offset + newbins.size() < 1700){
memcpy(noBWCountMatches + offset, newbins.data(), newbins.size());
offset += newbins.size();
}
}
{
std::string newbins = Socket::getBinForms("127.0.0.1/8");
if (offset + newbins.size() < 1700){
memcpy(noBWCountMatches + offset, newbins.data(), newbins.size());
offset += newbins.size();
}
}
} }
// For server-wide totals. Local to this file only. // For server-wide totals. Local to this file only.
@ -107,7 +123,7 @@ Controller::sessIndex::sessIndex(){
/// into strings. This extracts the host, stream name, connector and crc field, ignoring everything /// into strings. This extracts the host, stream name, connector and crc field, ignoring everything
/// else. /// else.
Controller::sessIndex::sessIndex(const Comms::Statistics &statComm, size_t id){ Controller::sessIndex::sessIndex(const Comms::Statistics &statComm, size_t id){
host = statComm.getHost(id); Socket::hostBytesToStr(statComm.getHost(id).data(), 16, host);
streamName = statComm.getStream(id); streamName = statComm.getStream(id);
connector = statComm.getConnector(id); connector = statComm.getConnector(id);
crc = statComm.getCRC(id); crc = statComm.getCRC(id);
@ -336,6 +352,28 @@ void Controller::SharedMemStats(void *config){
bool firstRun = true; bool firstRun = true;
while (((Util::Config *)config)->is_active){ while (((Util::Config *)config)->is_active){
{ {
std::ifstream cpustat("/proc/stat");
if (cpustat){
char line[300];
while (cpustat.getline(line, 300)){
static unsigned long long cl_total = 0, cl_idle = 0;
unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
c_total = c_user + c_nice + c_syst + c_idle;
if (c_total > cl_total){
cpu_use = (long long)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
}else{
cpu_use = 0;
}
cl_total = c_total;
cl_idle = c_idle;
break;
}
}
}
}
{
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex); tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
tthread::lock_guard<tthread::mutex> guard2(statsMutex); tthread::lock_guard<tthread::mutex> guard2(statsMutex);
cacheLock->wait(); /*LTS*/ cacheLock->wait(); /*LTS*/
@ -522,7 +560,8 @@ uint32_t Controller::statSession::kill(){
/// Updates the given active connection with new stats data. /// Updates the given active connection with new stats data.
void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm){ void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm){
std::string myHost = statComm.getHost(index); std::string myHost;
Socket::hostBytesToStr(statComm.getHost(index).data(), 16, myHost);
std::string myStream = statComm.getStream(index); std::string myStream = statComm.getStream(index);
std::string myConnector = statComm.getConnector(index); std::string myConnector = statComm.getConnector(index);
// update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 = // update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 =
@ -613,12 +652,12 @@ void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm
} }
if (currDown + currUp >= COUNTABLE_BYTES){ if (currDown + currUp >= COUNTABLE_BYTES){
if (sessionType == SESS_UNSET){ if (sessionType == SESS_UNSET){
if (myConnector == "INPUT"){ if (myConnector.size() >= 5 && myConnector.substr(0, 5) == "INPUT"){
++servInputs; ++servInputs;
streamStats[myStream].inputs++; streamStats[myStream].inputs++;
streamStats[myStream].currIns++; streamStats[myStream].currIns++;
sessionType = SESS_INPUT; sessionType = SESS_INPUT;
}else if (myConnector == "OUTPUT"){ }else if (myConnector.size() >= 6 && myConnector.substr(0, 6) == "OUTPUT"){
++servOutputs; ++servOutputs;
streamStats[myStream].outputs++; streamStats[myStream].outputs++;
streamStats[myStream].currOuts++; streamStats[myStream].currOuts++;
@ -632,6 +671,7 @@ void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm
} }
// If previous < COUNTABLE_BYTES, we haven't counted any data so far. // If previous < COUNTABLE_BYTES, we haven't counted any data so far.
// We need to count all the data in that case, otherwise we only count the difference. // We need to count all the data in that case, otherwise we only count the difference.
if (noBWCount != 2){ //only count connections that are countable
if (prevUp + prevDown < COUNTABLE_BYTES){ if (prevUp + prevDown < COUNTABLE_BYTES){
if (!myStream.size() || myStream[0] == 0){ if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(myStream)){streamStats.erase(myStream);} if (streamStats.count(myStream)){streamStats.erase(myStream);}
@ -649,6 +689,7 @@ void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm
} }
} }
} }
}
Controller::sessType Controller::statSession::getSessType(){ Controller::sessType Controller::statSession::getSessType(){
return sessionType; return sessionType;
@ -1437,29 +1478,9 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
H.StartResponse("200", "OK", H, conn, true); H.StartResponse("200", "OK", H, conn, true);
// Collect core server stats // Collect core server stats
uint64_t cpu_use = 0;
uint64_t mem_total = 0, mem_free = 0, mem_bufcache = 0; uint64_t mem_total = 0, mem_free = 0, mem_bufcache = 0;
uint64_t bw_up_total = 0, bw_down_total = 0; uint64_t bw_up_total = 0, bw_down_total = 0;
{ {
std::ifstream cpustat("/proc/stat");
if (cpustat){
char line[300];
while (cpustat.getline(line, 300)){
static unsigned long long cl_total = 0, cl_idle = 0;
unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
c_total = c_user + c_nice + c_syst + c_idle;
if (c_total > cl_total){
cpu_use = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
}else{
cpu_use = 0;
}
cl_total = c_total;
cl_idle = c_idle;
break;
}
}
}
std::ifstream meminfo("/proc/meminfo"); std::ifstream meminfo("/proc/meminfo");
if (meminfo){ if (meminfo){
char line[300]; char line[300];

View file

@ -1,5 +1,6 @@
#include "controller_capabilities.h" #include "controller_capabilities.h"
#include "controller_storage.h" #include "controller_storage.h"
#include "controller_push.h" //LTS
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@ -41,7 +42,7 @@ namespace Controller{
///\brief Store and print a log message. ///\brief Store and print a log message.
///\param kind The type of message. ///\param kind The type of message.
///\param message The message to be logged. ///\param message The message to be logged.
void Log(const std::string &kind, const std::string &message, const std::string &stream, bool noWriteToLog){ void Log(const std::string &kind, const std::string &message, const std::string &stream, uint64_t progPid, bool noWriteToLog){
if (noWriteToLog){ if (noWriteToLog){
tthread::lock_guard<tthread::mutex> guard(logMutex); tthread::lock_guard<tthread::mutex> guard(logMutex);
JSON::Value m; JSON::Value m;
@ -52,6 +53,7 @@ namespace Controller{
m.append(stream); m.append(stream);
Storage["log"].append(m); Storage["log"].append(m);
Storage["log"].shrink(100); // limit to 100 log messages Storage["log"].shrink(100); // limit to 100 log messages
if (isPushActive(progPid)){pushLogMessage(progPid, m);} //LTS
logCounter++; logCounter++;
if (rlxLogs && rlxLogs->isReady()){ if (rlxLogs && rlxLogs->isReady()){
if (!firstLog){firstLog = logCounter;} if (!firstLog){firstLog = logCounter;}

View file

@ -22,7 +22,7 @@ namespace Controller{
Util::RelAccX *streamsAccessor(); Util::RelAccX *streamsAccessor();
/// Store and print a log message. /// Store and print a log message.
void Log(const std::string &kind, const std::string &message, const std::string &stream = "", void Log(const std::string &kind, const std::string &message, const std::string &stream = "", uint64_t progPid = 0,
bool noWriteToLog = false); bool noWriteToLog = false);
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn, void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
const std::string &host, uint64_t duration, uint64_t up, uint64_t down, const std::string &host, uint64_t duration, uint64_t up, uint64_t down,

View file

@ -77,7 +77,8 @@ namespace Mist{
option["help"] = "Generate .dtsh, then exit"; option["help"] = "Generate .dtsh, then exit";
config->addOption("headeronly", option); config->addOption("headeronly", option);
/*LTS-START /*LTS-START*/
/*
//Encryption //Encryption
option.null(); option.null();
option["arg"] = "string"; option["arg"] = "string";
@ -86,31 +87,6 @@ namespace Mist{
option["help"] = "a KID:KEY combo for auto-encrypting tracks"; option["help"] = "a KID:KEY combo for auto-encrypting tracks";
config->addOption("encryption", option); config->addOption("encryption", option);
option.null();
option["long"] = "realtime";
option["short"] = "r";
option["help"] = "Feed the results of this input in realtime to the buffer";
config->addOption("realtime", option);
capa["optional"]["realtime"]["name"] = "Simulated Live";
capa["optional"]["realtime"]["help"] = "Make this input run as a simulated live stream";
capa["optional"]["realtime"]["option"] = "--realtime";
option.null();
option["long"] = "simulated-starttime";
option["arg"] = "integer";
option["short"] = "S";
option["help"] = "Unix timestamp on which the simulated start of the stream is based.";
option["value"].append(0);
config->addOption("simulated-starttime", option);
capa["optional"]["simulated-starttime"]["name"] = "Simulated start time";
capa["optional"]["simulated-starttime"]["help"] =
"The unix timestamp on which this stream is assumed to have started playback, or 0 for "
"automatic";
capa["optional"]["simulated-starttime"]["option"] = "--simulated-starttime";
capa["optional"]["simulated-starttime"]["type"] = "uint";
capa["optional"]["simulated-starttime"]["default"] = 0;
option.null(); option.null();
option["arg"] = "string"; option["arg"] = "string";
option["short"] = "B"; option["short"] = "B";
@ -175,8 +151,33 @@ namespace Mist{
capa["optional"]["playready"]["help"] = "The header to use for PlayReady encryption."; capa["optional"]["playready"]["help"] = "The header to use for PlayReady encryption.";
capa["optional"]["playready"]["option"] = "--playready"; capa["optional"]["playready"]["option"] = "--playready";
capa["optional"]["playready"]["type"] = "string"; capa["optional"]["playready"]["type"] = "string";
LTS-END*/ */
option.null();
option["long"] = "realtime";
option["short"] = "r";
option["help"] = "Feed the results of this input in realtime to the buffer";
config->addOption("realtime", option);
capa["optional"]["realtime"]["name"] = "Simulated Live";
capa["optional"]["realtime"]["help"] = "Make this input run as a simulated live stream";
capa["optional"]["realtime"]["option"] = "--realtime";
option.null();
option["long"] = "simulated-starttime";
option["arg"] = "integer";
option["short"] = "S";
option["help"] = "Unix timestamp on which the simulated start of the stream is based.";
option["value"].append(0);
config->addOption("simulated-starttime", option);
capa["optional"]["simulated-starttime"]["name"] = "Simulated start time";
capa["optional"]["simulated-starttime"]["help"] =
"The unix timestamp on which this stream is assumed to have started playback, or 0 for "
"automatic";
capa["optional"]["simulated-starttime"]["option"] = "--simulated-starttime";
capa["optional"]["simulated-starttime"]["type"] = "uint";
capa["optional"]["simulated-starttime"]["default"] = 0;
/*LTS-END*/
capa["optional"]["debug"]["name"] = "debug"; capa["optional"]["debug"]["name"] = "debug";
capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed."; capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed.";
capa["optional"]["debug"]["option"] = "--debug"; capa["optional"]["debug"]["option"] = "--debug";
@ -637,7 +638,7 @@ namespace Mist{
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;} if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
config->is_active = false; config->is_active = false;
finish(); finish();
INFO_MSG("Input for stream %s closing clean", streamName.c_str()); INFO_MSG("Input closing clean, reason: %s", Util::exitReason);
userSelect.clear(); userSelect.clear();
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_OFF;} if (streamStatus){streamStatus.mapped[0] = STRMSTAT_OFF;}
} }
@ -669,6 +670,9 @@ namespace Mist{
} }
} }
/*LTS-END*/ /*LTS-END*/
if (!ret && ((Util::bootSecs() - activityCounter) >= INPUT_TIMEOUT)){
Util::logExitReason("no activity for %u seconds", Util::bootSecs() - activityCounter);
}
return ret; return ret;
} }
@ -682,29 +686,30 @@ namespace Mist{
/// - call getNext() in a loop, buffering packets /// - call getNext() in a loop, buffering packets
void Input::stream(){ void Input::stream(){
if (!config->getBool("realtime") && Util::streamAlive(streamName)){
WARN_MSG("Stream already online, cancelling");
return;
}
std::map<std::string, std::string> overrides; std::map<std::string, std::string> overrides;
overrides["throughboot"] = ""; overrides["throughboot"] = "";
if (isSingular()){
if (Util::streamAlive(streamName)){
WARN_MSG("Stream already online, cancelling");
return;
}
overrides["singular"] = "";
}
if (config->getBool("realtime") || if (config->getBool("realtime") ||
(capa.isMember("hardcoded") && capa["hardcoded"].isMember("resume") && capa["hardcoded"]["resume"])){ (capa.isMember("hardcoded") && capa["hardcoded"].isMember("resume") && capa["hardcoded"]["resume"])){
overrides["resume"] = "1"; overrides["resume"] = "1";
} }
if (isSingular()){
if (!config->getBool("realtime") && Util::streamAlive(streamName)){
WARN_MSG("Stream already online, cancelling");
return;
}
overrides["singular"] = "";
if (!Util::startInput(streamName, "push://INTERNAL_ONLY:" + config->getString("input"), true, if (!Util::startInput(streamName, "push://INTERNAL_ONLY:" + config->getString("input"), true,
true, overrides)){// manually override stream url to start the buffer true, overrides)){// manually override stream url to start the buffer
WARN_MSG("Could not start buffer, cancelling"); WARN_MSG("Could not start buffer, cancelling");
return; return;
} }
}else{
if (!Util::startInput(streamName, "push://INTERNAL_PUSH:" + capa["name"].asStringRef(), true,
true, overrides)){// manually override stream url to start the buffer
WARN_MSG("Could not start buffer, cancelling");
return;
}
}
INFO_MSG("Input started"); INFO_MSG("Input started");
meta.reInit(streamName, false); meta.reInit(streamName, false);
@ -715,19 +720,23 @@ namespace Mist{
} }
parseStreamHeader(); parseStreamHeader();
std::set<size_t> validTracks = M.getMySourceTracks(getpid()); std::set<size_t> validTracks;
if (publishesTracks()){
validTracks = M.getMySourceTracks(getpid());
if (!validTracks.size()){ if (!validTracks.size()){
userSelect.clear(); userSelect.clear();
finish(); finish();
INFO_MSG("No tracks found, cancelling"); INFO_MSG("No tracks found, cancelling");
return; return;
} }
}
timeOffset = 0; timeOffset = 0;
uint64_t minFirstMs = 0; uint64_t minFirstMs = 0;
// If resume mode is on, find matching tracks and set timeOffset values to make sure we append to the tracks. // If resume mode is on, find matching tracks and set timeOffset values to make sure we append to the tracks.
if (config->getBool("realtime")){ if (publishesTracks() && config->getBool("realtime")){
seek(0); seek(0);
minFirstMs = 0xFFFFFFFFFFFFFFFFull; minFirstMs = 0xFFFFFFFFFFFFFFFFull;
@ -736,12 +745,11 @@ namespace Mist{
uint64_t maxLastMs = 0; uint64_t maxLastMs = 0;
// track lowest firstms value // track lowest firstms value
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
it != myMeta.tracks.end(); ++it){ if (meta.getFirstms(*it) < minFirstMs){minFirstMs = meta.getFirstms(*it);}
if (it->second.firstms < minFirstMs){minFirstMs = it->second.firstms;} if (meta.getFirstms(*it) > maxFirstMs){maxFirstMs = meta.getFirstms(*it);}
if (it->second.firstms > maxFirstMs){maxFirstMs = it->second.firstms;} if (meta.getLastms(*it) < minLastMs){minLastMs = meta.getLastms(*it);}
if (it->second.lastms < minLastMs){minLastMs = it->second.lastms;} if (meta.getLastms(*it) > maxLastMs){maxLastMs = meta.getLastms(*it);}
if (it->second.lastms > maxLastMs){maxLastMs = it->second.lastms;}
} }
if (maxFirstMs - minFirstMs > 500){ if (maxFirstMs - minFirstMs > 500){
WARN_MSG("Begin timings of tracks for this file are %" PRIu64 WARN_MSG("Begin timings of tracks for this file are %" PRIu64
@ -756,10 +764,8 @@ namespace Mist{
maxLastMs - minLastMs, minLastMs, maxLastMs); maxLastMs - minLastMs, minLastMs, maxLastMs);
} }
// find highest current time // find highest current time
for (std::map<unsigned int, DTSC::Track>::iterator secondIt = tmpM.tracks.begin(); for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
secondIt != tmpM.tracks.end(); ++secondIt){ timeOffset = std::max(timeOffset, (int64_t)meta.getLastms(*it));
VERYHIGH_MSG("Track %u starts at %" PRIu64, secondIt->first, secondIt->second.lastms);
timeOffset = std::max(timeOffset, (int64_t)secondIt->second.lastms);
} }
if (timeOffset){ if (timeOffset){
@ -771,13 +777,11 @@ namespace Mist{
timeOffset -= minFirstMs; // we don't need to add the lowest firstms value to the offset, as it's already there timeOffset -= minFirstMs; // we don't need to add the lowest firstms value to the offset, as it's already there
} }
} }
if (publishesTracks()){
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
it != myMeta.tracks.end(); it++){ meta.setFirstms(*it, meta.getFirstms(*it)+timeOffset);
it->second.firstms += timeOffset; meta.setLastms(*it, 0);
it->second.lastms = 0; }
selectedTracks.insert(it->first);
it->second.minKeepAway = SIMULATED_LIVE_BUFFER;
} }
simStartTime = config->getInteger("simulated-starttime"); simStartTime = config->getInteger("simulated-starttime");
@ -785,9 +789,9 @@ namespace Mist{
std::string reason; std::string reason;
if (config->getBool("realtime")){ if (config->getBool("realtime")){
reason = realtimeMainLoop(); realtimeMainLoop();
}else{ }else{
reason = streamMainLoop(); streamMainLoop();
} }
closeStreamSource(); closeStreamSource();
@ -795,11 +799,68 @@ namespace Mist{
userSelect.clear(); userSelect.clear();
finish(); finish();
INFO_MSG("Input closing clean; reason: %s", reason.c_str()); INFO_MSG("Input closing clean; reason: %s", Util::exitReason);
return; return;
} }
std::string Input::streamMainLoop(){ void Input::streamMainLoop(){
uint64_t statTimer = 0;
uint64_t startTime = Util::bootSecs();
size_t tid;
size_t idx;
Comms::Statistics statComm;
getNext();
tid = thisPacket.getTrackId();
idx = M.trackIDToIndex(tid, getpid());
if (thisPacket && !userSelect.count(idx)){
userSelect[idx].reload(streamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
}
while (thisPacket && config->is_active && userSelect[idx].isAlive()){
if (userSelect[idx].getStatus() == COMM_STATUS_REQDISCONNECT){
Util::logExitReason("buffer requested shutdown");
break;
}
bufferLivePacket(thisPacket);
userSelect[idx].keepAlive();
getNext();
if (!thisPacket){
Util::logExitReason("invalid packet from getNext");
break;
}
tid = thisPacket.getTrackId();
idx = M.trackIDToIndex(tid, getpid());
if (thisPacket && !userSelect.count(idx)){
userSelect[idx].reload(streamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
}
if (Util::bootSecs() - statTimer > 1){
// Connect to stats for INPUT detection
if (!statComm){statComm.reload();}
if (statComm){
if (!statComm.isAlive()){
config->is_active = false;
Util::logExitReason("received shutdown request from controller");
return;
}
uint64_t now = Util::bootSecs();
statComm.setNow(now);
statComm.setCRC(getpid());
statComm.setStream(streamName);
statComm.setConnector("INPUT:" + capa["name"].asStringRef());
statComm.setUp(0);
statComm.setDown(streamByteCount());
statComm.setTime(now - startTime);
statComm.setLastSecond(0);
statComm.setHost(getConnectedBinHost());
statComm.keepAlive();
}
statTimer = Util::bootSecs();
}
}
}
void Input::realtimeMainLoop(){
uint64_t statTimer = 0; uint64_t statTimer = 0;
uint64_t startTime = Util::bootSecs(); uint64_t startTime = Util::bootSecs();
Comms::Statistics statComm; Comms::Statistics statComm;
@ -809,7 +870,18 @@ namespace Mist{
userSelect[tid].reload(streamName, tid, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); userSelect[tid].reload(streamName, tid, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
} }
while (thisPacket && config->is_active && userSelect[thisPacket.getTrackId()].isAlive()){ while (thisPacket && config->is_active && userSelect[thisPacket.getTrackId()].isAlive()){
thisPacket.nullMember("bpos");
while (config->is_active && userSelect[thisPacket.getTrackId()].isAlive() &&
Util::bootMS() + SIMULATED_LIVE_BUFFER < (thisPacket.getTime() + timeOffset) + simStartTime){
Util::sleep(std::min(((thisPacket.getTime() + timeOffset) + simStartTime) - (Util::getMS() + SIMULATED_LIVE_BUFFER),
(uint64_t)1000));
userSelect[thisPacket.getTrackId()].keepAlive();
}
uint64_t originalTime = thisPacket.getTime();
thisPacket.setTime(originalTime + timeOffset);
bufferLivePacket(thisPacket); bufferLivePacket(thisPacket);
thisPacket.setTime(originalTime);
userSelect[thisPacket.getTrackId()].keepAlive(); userSelect[thisPacket.getTrackId()].keepAlive();
getNext(); getNext();
if (thisPacket && !userSelect.count(thisPacket.getTrackId())){ if (thisPacket && !userSelect.count(thisPacket.getTrackId())){
@ -823,50 +895,31 @@ namespace Mist{
if (statComm){ if (statComm){
if (!statComm.isAlive()){ if (!statComm.isAlive()){
config->is_active = false; config->is_active = false;
return "received shutdown request from controller"; Util::logExitReason("received shutdown request from controller");
return;
} }
uint64_t now = Util::bootSecs(); uint64_t now = Util::bootSecs();
statComm.setNow(now); statComm.setNow(now);
statComm.setCRC(getpid()); statComm.setCRC(getpid());
statComm.setStream(streamName); statComm.setStream(streamName);
statComm.setConnector("INPUT"); statComm.setConnector("INPUT:" + capa["name"].asStringRef());
statComm.setUp(0); statComm.setUp(0);
statComm.setDown(streamByteCount()); statComm.setDown(streamByteCount());
statComm.setTime(now - startTime); statComm.setTime(now - startTime);
statComm.setLastSecond(0); statComm.setLastSecond(0);
statComm.setHost(getConnectedBinHost());
statComm.keepAlive(); statComm.keepAlive();
} }
statTimer = Util::bootSecs(); statTimer = Util::bootSecs();
} }
} }
if (!nProxy.userClient.isAlive()){return "buffer shutdown";} if (!thisPacket){
if (!config->is_active){return "received deactivate signal";} Util::logExitReason("invalid packet from getNext");
if (!thisPacket){return "Invalid packet";}
return "Unknown";
} }
if (thisPacket && !userSelect[thisPacket.getTrackId()].isAlive()){
std::string Input::realtimeMainLoop(){ Util::logExitReason("buffer shutdown");
getNext();
while (thisPacket && config->is_active && nProxy.userClient.isAlive()){
thisPacket.nullMember("bpos");
while (config->is_active && nProxy.userClient.isAlive() &&
Util::bootMS() + SIMULATED_LIVE_BUFFER < (thisPacket.getTime() + timeOffset) + simStartTime){
Util::sleep(std::min(((thisPacket.getTime() + timeOffset) + simStartTime) - (Util::getMS() + SIMULATED_LIVE_BUFFER),
(uint64_t)1000));
nProxy.userClient.keepAlive();
} }
uint64_t originalTime = thisPacket.getTime();
thisPacket.setTime(originalTime + timeOffset);
nProxy.bufferLivePacket(thisPacket, myMeta);
thisPacket.setTime(originalTime);
getNext();
nProxy.userClient.keepAlive();
}
if (!thisPacket){return "end of file";}
if (!config->is_active){return "received deactivate signal";}
if (!userSelect[thisPacket.getTrackId()].isAlive()){return "buffer shutdown";}
return "Unknown";
} }
void Input::finish(){ void Input::finish(){

View file

@ -33,6 +33,7 @@ namespace Mist{
bool hasMeta() const; bool hasMeta() const;
static Util::Config *config; static Util::Config *config;
virtual bool needsLock(){return !config->getBool("realtime");} virtual bool needsLock(){return !config->getBool("realtime");}
virtual bool publishesTracks(){return true;}
protected: protected:
virtual bool checkArguments() = 0; virtual bool checkArguments() = 0;
@ -54,11 +55,12 @@ namespace Mist{
virtual void convert(); virtual void convert();
virtual void serve(); virtual void serve();
virtual void stream(); virtual void stream();
virtual std::string getConnectedBinHost(){return std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001", 16);}
virtual size_t streamByteCount(){ virtual size_t streamByteCount(){
return 0; return 0;
}; // For live streams: to update the stats with correct values. }; // For live streams: to update the stats with correct values.
virtual std::string streamMainLoop(); virtual void streamMainLoop();
virtual std::string realtimeMainLoop(); virtual void realtimeMainLoop();
bool isAlwaysOn(); bool isAlwaysOn();
virtual void userLeadIn(); virtual void userLeadIn();

View file

@ -16,6 +16,73 @@ namespace Mist{
capa["source_match"] = "balance:*"; capa["source_match"] = "balance:*";
capa["priority"] = 9; capa["priority"] = 9;
capa["morphic"] = 1; capa["morphic"] = 1;
JSON::Value option;
option["arg"] = "integer";
option["long"] = "buffer";
option["short"] = "b";
option["help"] = "DVR buffer time in ms";
option["value"].append(50000);
config->addOption("bufferTime", option);
capa["optional"]["DVR"]["name"] = "Buffer time (ms)";
capa["optional"]["DVR"]["help"] =
"The target available buffer time for this live stream, in milliseconds. This is the time "
"available to seek around in, and will automatically be extended to fit whole keyframes as "
"well as the minimum duration needed for stable playback.";
capa["optional"]["DVR"]["option"] = "--buffer";
capa["optional"]["DVR"]["type"] = "uint";
capa["optional"]["DVR"]["default"] = 50000;
option.null();
option["arg"] = "integer";
option["long"] = "cut";
option["short"] = "c";
option["help"] = "Any timestamps before this will be cut from the live buffer";
option["value"].append(0);
config->addOption("cut", option);
capa["optional"]["cut"]["name"] = "Cut time (ms)";
capa["optional"]["cut"]["help"] =
"Any timestamps before this will be cut from the live buffer.";
capa["optional"]["cut"]["option"] = "--cut";
capa["optional"]["cut"]["type"] = "uint";
capa["optional"]["cut"]["default"] = 0;
option.null();
option["arg"] = "integer";
option["long"] = "resume";
option["short"] = "R";
option["help"] = "Enable resuming support (1) or disable resuming support (0, default)";
option["value"].append(0);
config->addOption("resume", option);
capa["optional"]["resume"]["name"] = "Resume support";
capa["optional"]["resume"]["help"] =
"If enabled, the buffer will linger after source disconnect to allow resuming the stream "
"later. If disabled, the buffer will instantly close on source disconnect.";
capa["optional"]["resume"]["option"] = "--resume";
capa["optional"]["resume"]["type"] = "select";
capa["optional"]["resume"]["select"][0u][0u] = "0";
capa["optional"]["resume"]["select"][0u][1u] = "Disabled";
capa["optional"]["resume"]["select"][1u][0u] = "1";
capa["optional"]["resume"]["select"][1u][1u] = "Enabled";
capa["optional"]["resume"]["default"] = 0;
option.null();
option["arg"] = "integer";
option["long"] = "segment-size";
option["short"] = "S";
option["help"] = "Target time duration in milliseconds for segments";
option["value"].append(5000);
config->addOption("segmentsize", option);
capa["optional"]["segmentsize"]["name"] = "Segment size (ms)";
capa["optional"]["segmentsize"]["help"] = "Target time duration in milliseconds for segments.";
capa["optional"]["segmentsize"]["option"] = "--segment-size";
capa["optional"]["segmentsize"]["type"] = "uint";
capa["optional"]["segmentsize"]["default"] = 5000;
capa["codecs"][0u][0u].append("*");
capa["codecs"][0u][1u].append("*");
capa["codecs"][0u][2u].append("*");
} }
int inputBalancer::boot(int argc, char *argv[]){ int inputBalancer::boot(int argc, char *argv[]){

View file

@ -30,6 +30,7 @@ namespace Mist{
capa["optional"].removeMember("realtime"); capa["optional"].removeMember("realtime");
lastReTime = 0; /*LTS*/
finalMillis = 0; finalMillis = 0;
liveMeta = 0; liveMeta = 0;
capa["name"] = "Buffer"; capa["name"] = "Buffer";
@ -118,6 +119,7 @@ namespace Mist{
cutTime = 0; cutTime = 0;
segmentSize = 1900; segmentSize = 1900;
hasPush = false; hasPush = false;
everHadPush = false;
resumeMode = false; resumeMode = false;
} }
@ -360,7 +362,7 @@ namespace Mist{
if (!(users.getStatus(i) & COMM_STATUS_SOURCE)){continue;} if (!(users.getStatus(i) & COMM_STATUS_SOURCE)){continue;}
if (users.getTrack(i) != tid){continue;} if (users.getTrack(i) != tid){continue;}
// We have found the right track here (pid matches, and COMM_STATUS_SOURCE set) // We have found the right track here (pid matches, and COMM_STATUS_SOURCE set)
users.setStatus(COMM_STATUS_DISCONNECT, i); users.setStatus(COMM_STATUS_REQDISCONNECT, i);
break; break;
} }
@ -450,7 +452,7 @@ namespace Mist{
// firstVideo = 1 happens when there are no tracks, in which case we don't care any more // firstVideo = 1 happens when there are no tracks, in which case we don't care any more
uint32_t firstKey = keys.getFirstValid(); uint32_t firstKey = keys.getFirstValid();
uint32_t endKey = keys.getEndValid(); uint32_t endKey = keys.getEndValid();
if (type != "video"){ if (type != "video" && videoFirstms != 0xFFFFFFFFFFFFFFFFull){
if ((endKey - firstKey) < 2 || keys.getTime(firstKey + 1) > videoFirstms){continue;} if ((endKey - firstKey) < 2 || keys.getTime(firstKey + 1) > videoFirstms){continue;}
} }
// Buffer cutting // Buffer cutting
@ -464,19 +466,6 @@ namespace Mist{
} }
} }
updateMeta(); updateMeta();
if (config->is_active){
if (streamStatus){streamStatus.mapped[0] = hasPush ? STRMSTAT_READY : STRMSTAT_WAIT;}
}
static bool everHadPush = false;
if (hasPush){
hasPush = false;
everHadPush = true;
}else if (everHadPush && !resumeMode && config->is_active){
INFO_MSG("Shutting down buffer because resume mode is disabled and the source disconnected");
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
config->is_active = false;
userSelect.clear();
}
} }
void inputBuffer::userLeadIn(){ void inputBuffer::userLeadIn(){
@ -487,22 +476,21 @@ namespace Mist{
/*LTS-END*/ /*LTS-END*/
connectedUsers = 0; connectedUsers = 0;
//Store child process PIDs in generatePids.
//These are controlled by the buffer (usually processes) and should not count towards incoming pushes
generatePids.clear(); generatePids.clear();
for (std::map<std::string, pid_t>::iterator it = runningProcs.begin(); it != runningProcs.end(); it++){ for (std::map<std::string, pid_t>::iterator it = runningProcs.begin(); it != runningProcs.end(); it++){
generatePids.insert(it->second); generatePids.insert(it->second);
} }
hasPush = false;
} }
void inputBuffer::userOnActive(size_t id){ void inputBuffer::userOnActive(size_t id){
///\todo Add tracing of earliest watched keys, to prevent data going out of memory for ///\todo Add tracing of earliest watched keys, to prevent data going out of memory for
/// still-watching viewers /// still-watching viewers
if (users.getStatus(id) != COMM_STATUS_DISCONNECT && users.getStatus(id) & COMM_STATUS_SOURCE){ if (users.getStatus(id) != COMM_STATUS_DISCONNECT && users.getStatus(id) & COMM_STATUS_SOURCE){
sourcePids[users.getPid(id)].insert(users.getTrack(id)); sourcePids[users.getPid(id)].insert(users.getTrack(id));
if (!M.trackValid(users.getTrack(id))){
users.setStatus(COMM_STATUS_DISCONNECT, id);
return;
}
// GeneratePids holds the pids of the process that generate data, so ignore those for determining if a push is ingested. // GeneratePids holds the pids of the process that generate data, so ignore those for determining if a push is ingested.
if (!generatePids.count(users.getPid(id))){hasPush = true;} if (M.trackValid(users.getTrack(id)) && !generatePids.count(users.getPid(id))){hasPush = true;}
} }
if (!(users.getStatus(id) & COMM_STATUS_DONOTTRACK)){++connectedUsers;} if (!(users.getStatus(id) & COMM_STATUS_DONOTTRACK)){++connectedUsers;}
@ -516,11 +504,20 @@ namespace Mist{
} }
} }
void inputBuffer::userLeadOut(){ void inputBuffer::userLeadOut(){
if (config->is_active && streamStatus){streamStatus.mapped[0] = hasPush ? STRMSTAT_READY : STRMSTAT_WAIT;}
if (hasPush){everHadPush = true;}
if (!hasPush && everHadPush && !resumeMode && config->is_active){
Util::logExitReason("source disconnected for non-resumable stream");
if (streamStatus){streamStatus.mapped[0] = STRMSTAT_SHUTDOWN;}
config->is_active = false;
userSelect.clear();
}
/*LTS-START*/ /*LTS-START*/
static std::set<size_t> prevValidTracks; static std::set<size_t> prevValidTracks;
std::set<size_t> validTracks = M.getValidTracks(); std::set<size_t> validTracks = M.getValidTracks();
if (validTracks != prevValidTracks){ if (validTracks != prevValidTracks){
MEDIUM_MSG("Valid tracks count changed from %lu to %lu", prevValidTracks.size(), validTracks.size());
prevValidTracks = validTracks; prevValidTracks = validTracks;
if (Triggers::shouldTrigger("LIVE_TRACK_LIST")){ if (Triggers::shouldTrigger("LIVE_TRACK_LIST")){
JSON::Value triggerPayload; JSON::Value triggerPayload;
@ -545,7 +542,6 @@ namespace Mist{
bool inputBuffer::preRun(){ bool inputBuffer::preRun(){
// This function gets run periodically to make sure runtime updates of the config get parsed. // This function gets run periodically to make sure runtime updates of the config get parsed.
lastReTime = Util::epoch(); /*LTS*/
std::string strName = config->getString("streamname"); std::string strName = config->getString("streamname");
Util::sanitizeName(strName); Util::sanitizeName(strName);
strName = strName.substr(0, (strName.find_first_of("+ "))); strName = strName.substr(0, (strName.find_first_of("+ ")));
@ -553,16 +549,21 @@ namespace Mist{
snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, strName.c_str()); snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, strName.c_str());
Util::DTSCShmReader rStrmConf(tmpBuf); Util::DTSCShmReader rStrmConf(tmpBuf);
DTSC::Scan streamCfg = rStrmConf.getScan(); DTSC::Scan streamCfg = rStrmConf.getScan();
if (streamCfg){
JSON::Value configuredProcesses = streamCfg.getMember("processes").asJSON();
checkProcesses(configuredProcesses);
}
//Check if bufferTime setting is correct
uint64_t tmpNum = retrieveSetting(streamCfg, "DVR", "bufferTime"); uint64_t tmpNum = retrieveSetting(streamCfg, "DVR", "bufferTime");
if (tmpNum < 1000){tmpNum = 1000;} if (tmpNum < 1000){tmpNum = 1000;}
// if the new value is different, print a message and apply it
if (bufferTime != tmpNum){ if (bufferTime != tmpNum){
DEVEL_MSG("Setting bufferTime from %" PRIu64 " to new value of %" PRIu64, bufferTime, tmpNum); DEVEL_MSG("Setting bufferTime from %" PRIu64 " to new value of %" PRIu64, bufferTime, tmpNum);
bufferTime = tmpNum; bufferTime = tmpNum;
} }
/*LTS-START*/ /*LTS-START*/
//Check if cutTime setting is correct
tmpNum = retrieveSetting(streamCfg, "cut"); tmpNum = retrieveSetting(streamCfg, "cut");
// if the new value is different, print a message and apply it // if the new value is different, print a message and apply it
if (cutTime != tmpNum){ if (cutTime != tmpNum){
@ -570,28 +571,27 @@ namespace Mist{
cutTime = tmpNum; cutTime = tmpNum;
} }
//Check if resume setting is correct
tmpNum = retrieveSetting(streamCfg, "resume"); tmpNum = retrieveSetting(streamCfg, "resume");
// if the new value is different, print a message and apply it
if (resumeMode != (bool)tmpNum){ if (resumeMode != (bool)tmpNum){
INFO_MSG("Setting resume mode from %s to new value of %s", INFO_MSG("Setting resume mode from %s to new value of %s",
resumeMode ? "enabled" : "disabled", tmpNum ? "enabled" : "disabled"); resumeMode ? "enabled" : "disabled", tmpNum ? "enabled" : "disabled");
resumeMode = tmpNum; resumeMode = tmpNum;
} }
if (!meta){return true;}//abort the rest if we can't write metadata
lastReTime = Util::epoch(); /*LTS*/
//Check if segmentsize setting is correct
tmpNum = retrieveSetting(streamCfg, "segmentsize"); tmpNum = retrieveSetting(streamCfg, "segmentsize");
if (M && tmpNum < M.biggestFragment() / 2){tmpNum = M.biggestFragment() / 2;} if (tmpNum < meta.biggestFragment() / 2){tmpNum = meta.biggestFragment() / 2;}
// if the new value is different, print a message and apply it segmentSize = meta.getMinimumFragmentDuration();
if (segmentSize != tmpNum){ if (segmentSize != tmpNum){
INFO_MSG("Setting segmentSize from %" PRIu64 " to new value of %" PRIu64, segmentSize, tmpNum); INFO_MSG("Setting segmentSize from %" PRIu64 " to new value of %" PRIu64, segmentSize, tmpNum);
segmentSize = tmpNum; segmentSize = tmpNum;
if (M && M.getMinimumFragmentDuration() == 0){
meta.setMinimumFragmentDuration(segmentSize); meta.setMinimumFragmentDuration(segmentSize);
} }
}
if (streamCfg){
JSON::Value configuredProcesses = streamCfg.getMember("processes").asJSON();
checkProcesses(configuredProcesses);
}
/*LTS-END*/ /*LTS-END*/
return true; return true;
} }
@ -643,11 +643,8 @@ namespace Mist{
void inputBuffer::checkProcesses(const JSON::Value &procs){ void inputBuffer::checkProcesses(const JSON::Value &procs){
if (!M.getValidTracks().size()){return;} if (!M.getValidTracks().size()){return;}
std::set<std::string> newProcs; std::set<std::string> newProcs;
std::map<std::string, std::string> wouldSelect;
// used for building args // used for building args
int zero = 0;
int out = fileno(stdout);
int err = fileno(stderr); int err = fileno(stderr);
char *argarr[3]; char *argarr[3];
@ -660,22 +657,14 @@ namespace Mist{
continue; continue;
} }
if (tmp.isMember("source_track")){ if (tmp.isMember("source_track")){
std::string sourceTrack = tmp["source_track"].asString(); std::set<size_t> wouldSelect = Util::findTracks(M, JSON::Value(), "", tmp["source_track"].asStringRef());
if (sourceTrack != "null" && findTrack(sourceTrack) == INVALID_TRACK_ID){
// No match - skip this process // No match - skip this process
continue; if (!wouldSelect.size()){continue;}
} }
}
std::stringstream s;
if (tmp.isMember("track_select")){ if (tmp.isMember("track_select")){
std::set<size_t> wouldSelect = Util::wouldSelect(M, tmp["track_select"].asStringRef()); std::set<size_t> wouldSelect = Util::wouldSelect(M, tmp["track_select"].asStringRef());
if (!wouldSelect.size()){
// No match - skip this process // No match - skip this process
continue; if (!wouldSelect.size()){continue;}
}
for (std::set<size_t>::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){
s << *it << " ";
}
} }
if (tmp.isMember("track_inhibit")){ if (tmp.isMember("track_inhibit")){
std::set<size_t> wouldSelect = Util::wouldSelect( std::set<size_t> wouldSelect = Util::wouldSelect(
@ -693,7 +682,6 @@ namespace Mist{
} }
} }
newProcs.insert(tmp.toString()); newProcs.insert(tmp.toString());
wouldSelect[tmp.toString()] = s.str();
} }
// shut down deleted/changed processes // shut down deleted/changed processes
@ -722,8 +710,7 @@ namespace Mist{
argarr[1] = (char *)config.c_str(); argarr[1] = (char *)config.c_str();
argarr[2] = 0; argarr[2] = 0;
INFO_MSG("Starting process: %s %s", argarr[0], argarr[1]); INFO_MSG("Starting process: %s %s", argarr[0], argarr[1]);
INFO_MSG(" WouldSelect is %s", wouldSelect.at(*newProcs.begin()).c_str()); runningProcs[*newProcs.begin()] = Util::Procs::StartPiped(argarr, 0, 0, &err);
runningProcs[*newProcs.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err);
} }
newProcs.erase(newProcs.begin()); newProcs.erase(newProcs.begin());
} }

View file

@ -17,7 +17,8 @@ namespace Mist{
size_t segmentSize; /*LTS*/ size_t segmentSize; /*LTS*/
uint64_t lastReTime; /*LTS*/ uint64_t lastReTime; /*LTS*/
uint64_t finalMillis; uint64_t finalMillis;
bool hasPush; bool hasPush;//Is a push currently being received?
bool everHadPush;//Was there ever a push received?
bool resumeMode; bool resumeMode;
IPC::semaphore *liveMeta; IPC::semaphore *liveMeta;

View file

@ -361,7 +361,6 @@ namespace Mist{
thisPacket.reInit(srcConn); // read the next packet before continuing thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning continue; // parse the next packet before returning
} }
thisPacket = DTSC::Packet(thisPacket, M.trackIDToIndex(thisPacket.getTrackId(), getpid()));
return; // we have a packet return; // we have a packet
} }
} }

View file

@ -27,6 +27,11 @@ namespace Mist{
inputDTSC(Util::Config *cfg); inputDTSC(Util::Config *cfg);
bool needsLock(); bool needsLock();
virtual std::string getConnectedBinHost(){
if (srcConn){return srcConn.getBinHost();}
return Input::getConnectedBinHost();
}
protected: protected:
// Private Functions // Private Functions
bool openStreamSource(); bool openStreamSource();

View file

@ -114,13 +114,18 @@ namespace Mist{
while (ptr.size() < needed){ while (ptr.size() < needed){
if (!ptr.allocate(needed)){return false;} if (!ptr.allocate(needed)){return false;}
int64_t toRead = needed - ptr.size(); int64_t toRead = needed - ptr.size();
if (!fread(ptr + ptr.size(), toRead, 1, inFile)){ int readResult = 0;
// We assume if there is no current data buffered, that we are at EOF and don't print a warning while (!readResult){
if (ptr.size()){ readResult = fread(ptr + ptr.size(), toRead, 1, inFile);
if (!readResult){
if (errno == EINTR){continue;}
// At EOF we don't print a warning
if (!feof(inFile)){
FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed); FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed);
} }
return false; return false;
} }
}
totalBytes += toRead; totalBytes += toRead;
ptr.size() = needed; ptr.size() = needed;
needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal);
@ -463,7 +468,7 @@ namespace Mist{
}break; }break;
} }
} }
thisPacket.genericFill(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.ptr, C.dsize, thisPacket.genericFill(C.time, C.offset, C.track, C.ptr, C.dsize,
C.bpos, C.key); C.bpos, C.key);
} }

View file

@ -525,8 +525,7 @@ namespace Mist{
std::string test = root.link(entry.filename).getFilePath(); std::string test = root.link(entry.filename).getFilePath();
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary); fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));} if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
entry.byteEnd = fileSource.tellg(); totalBytes += fileSource.tellg();
totalBytes += entry.byteEnd;
} }
entry.timestamp = lastTimestamp + startTime; entry.timestamp = lastTimestamp + startTime;
@ -592,12 +591,9 @@ namespace Mist{
void inputHLS::parseStreamHeader(){ void inputHLS::parseStreamHeader(){
if (!initPlaylist(config->getString("input"))){ if (!initPlaylist(config->getString("input"))){
FAIL_MSG("Failed to load HLS playlist, aborting"); FAIL_MSG("Failed to load HLS playlist, aborting");
myMeta = DTSC::Meta();
return; return;
} }
myMeta = DTSC::Meta(); meta.reInit(config->getString("streamname"), false);
myMeta.live = true;
myMeta.vod = false;
INFO_MSG("Parsing live stream to create header..."); INFO_MSG("Parsing live stream to create header...");
TS::Packet packet; // to analyse and extract data TS::Packet packet; // to analyse and extract data
int counter = 1; int counter = 1;
@ -612,7 +608,7 @@ namespace Mist{
for (std::deque<playListEntries>::iterator entryIt = pListIt->second.begin(); for (std::deque<playListEntries>::iterator entryIt = pListIt->second.begin();
entryIt != pListIt->second.end(); ++entryIt){ entryIt != pListIt->second.end(); ++entryIt){
nProxy.userClient.keepAlive(); keepAlive();
if (!segDowner.loadSegment(*entryIt)){ if (!segDowner.loadSegment(*entryIt)){
WARN_MSG("Skipping segment that could not be loaded in an attempt to recover"); WARN_MSG("Skipping segment that could not be loaded in an attempt to recover");
tsStream.clear(); tsStream.clear();
@ -638,16 +634,19 @@ namespace Mist{
pidMapping[(((uint64_t)pListIt->first) << 32) + headerPack.getTrackId()] = counter; pidMapping[(((uint64_t)pListIt->first) << 32) + headerPack.getTrackId()] = counter;
pidMappingR[counter] = (((uint64_t)pListIt->first) << 32) + headerPack.getTrackId(); pidMappingR[counter] = (((uint64_t)pListIt->first) << 32) + headerPack.getTrackId();
packetId = counter; packetId = counter;
VERYHIGH_MSG("Added file %s, trackid: %zu, mapped to: %d", VERYHIGH_MSG("Added file %s, trackid: %zu, mapped to: %d", entryIt->filename.c_str(),
entryIt->filename.c_str(), headerPack.getTrackId(), counter); headerPack.getTrackId(), counter);
counter++; counter++;
} }
if ((!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ size_t idx = M.trackIDToIndex(packetId, getpid());
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); if ((idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
myMeta.tracks[packetId].minKeepAway = globalWaitTime * 2000; tsStream.initializeMetadata(meta, tmpTrackId, packetId);
VERYHIGH_MSG("setting minKeepAway = %d for track: %" PRIu64, idx = M.trackIDToIndex(packetId, getpid());
myMeta.tracks[packetId].minKeepAway, packetId); if (idx != INVALID_TRACK_ID){
meta.setMinKeepAway(idx, globalWaitTime * 2000);
VERYHIGH_MSG("setting minKeepAway = %" PRIu32 " for track: %zu", globalWaitTime * 2000, idx);
}
} }
} }
break; // we have all tracks discovered, next playlist! break; // we have all tracks discovered, next playlist!
@ -655,8 +654,6 @@ namespace Mist{
}while (!segDowner.atEnd()); }while (!segDowner.atEnd());
if (preCounter < counter){break;}// We're done reading this playlist! if (preCounter < counter){break;}// We're done reading this playlist!
} }
in.close();
} }
tsStream.clear(); tsStream.clear();
currentPlaylist = 0; currentPlaylist = 0;
@ -673,8 +670,6 @@ namespace Mist{
meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh"); meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh");
hasHeader = (bool)M; hasHeader = (bool)M;
if (M){return true;}
if (!hasHeader){meta.reInit(config->getString("streamname"), true);} if (!hasHeader){meta.reInit(config->getString("streamname"), true);}
TS::Packet packet; // to analyse and extract data TS::Packet packet; // to analyse and extract data
@ -704,7 +699,7 @@ namespace Mist{
DTSC::Packet headerPack; DTSC::Packet headerPack;
tsStream.getEarliestPacket(headerPack); tsStream.getEarliestPacket(headerPack);
int tmpTrackId = headerPack.getTrackId(); size_t tmpTrackId = headerPack.getTrackId();
uint64_t packetId = pidMapping[(((uint64_t)pListIt->first) << 32) + tmpTrackId]; uint64_t packetId = pidMapping[(((uint64_t)pListIt->first) << 32) + tmpTrackId];
if (packetId == 0){ if (packetId == 0){
@ -717,10 +712,8 @@ namespace Mist{
} }
size_t idx = M.trackIDToIndex(packetId, getpid()); size_t idx = M.trackIDToIndex(packetId, getpid());
INFO_MSG("PacketID: %" PRIu64 ", pid: %d, mapped to %zu", packetId, getpid(), idx);
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){ if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
tsStream.initializeMetadata(meta, tmpTrackId, packetId); tsStream.initializeMetadata(meta, tmpTrackId, packetId);
INFO_MSG("InitializingMeta for track %zu -> %zu", tmpTrackId, packetId);
idx = M.trackIDToIndex(packetId, getpid()); idx = M.trackIDToIndex(packetId, getpid());
} }
@ -757,6 +750,7 @@ namespace Mist{
counter++; counter++;
} }
size_t idx = M.trackIDToIndex(packetId, getpid());
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){ if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
tsStream.initializeMetadata(meta, tmpTrackId, packetId); tsStream.initializeMetadata(meta, tmpTrackId, packetId);
idx = M.trackIDToIndex(packetId, getpid()); idx = M.trackIDToIndex(packetId, getpid());
@ -781,7 +775,6 @@ namespace Mist{
INFO_MSG("write header file..."); INFO_MSG("write header file...");
M.toFile((config->getString("input") + ".dtsh").c_str()); M.toFile((config->getString("input") + ".dtsh").c_str());
in.close();
return true; return true;
} }
@ -794,26 +787,29 @@ namespace Mist{
INSANE_MSG("Getting next"); INSANE_MSG("Getting next");
uint32_t tid = 0; uint32_t tid = 0;
bool finished = false; bool finished = false;
if (userSelect.size()){tid = userSelect.begin()->first;}
thisPacket.null(); thisPacket.null();
while (config->is_active && (needsLock() || keepAlive())){ while (config->is_active && (needsLock() || keepAlive())){
// Check if we have a packet // Check if we have a packet
bool hasPacket = false; bool hasPacket = false;
if (streamIsLive){ if (idx == INVALID_TRACK_ID){
hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket()); hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket());
}else{ }else{
hasPacket = tsStream.hasPacket(M.getID(idx) & 0xFFFF); hasPacket = tsStream.hasPacket(getMappedTrackId(M.getID(idx)));
} }
// Yes? Excellent! Read and return it. // Yes? Excellent! Read and return it.
if (hasPacket){ if (hasPacket){
// Read // Read
if (M.getLive()){ if (idx == INVALID_TRACK_ID){
tsStream.getEarliestPacket(thisPacket); tsStream.getEarliestPacket(thisPacket);
tid = M.trackIDToIndex((((uint64_t)currentPlaylist) << 16) + thisPacket.getTrackId(), getpid()); tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId());
if (!tid){
INFO_MSG("Track %" PRIu64 " on PLS %" PRIu64 " -> %" PRIu32, thisPacket.getTrackId(), currentPlaylist, tid);
continue;
}
}else{ }else{
tsStream.getPacket(M.getID(idx) & 0xFFFF, thisPacket); tsStream.getPacket(getMappedTrackId(M.getID(idx)), thisPacket);
} }
if (!thisPacket){ if (!thisPacket){
FAIL_MSG("Could not getNext TS packet!"); FAIL_MSG("Could not getNext TS packet!");
@ -850,8 +846,8 @@ namespace Mist{
plsTimeOffset[currentPlaylist] += plsTimeOffset[currentPlaylist] +=
(int64_t)(plsLastTime[currentPlaylist] + plsInterval[currentPlaylist]) - (int64_t)newTime; (int64_t)(plsLastTime[currentPlaylist] + plsInterval[currentPlaylist]) - (int64_t)newTime;
newTime = thisPacket.getTime() + plsTimeOffset[currentPlaylist]; newTime = thisPacket.getTime() + plsTimeOffset[currentPlaylist];
INFO_MSG("[Guess; New offset: %" PRId64 " -> %" PRId64 "] Packet %" PRIu32 INFO_MSG("[Guess; New offset: %" PRId64 " -> %" PRId64 "] Packet %" PRIu32 "@%" PRIu64
"@%" PRIu64 "ms -> %" PRIu64 "ms", "ms -> %" PRIu64 "ms",
prevOffset, plsTimeOffset[currentPlaylist], tid, thisPacket.getTime(), newTime); prevOffset, plsTimeOffset[currentPlaylist], tid, thisPacket.getTime(), newTime);
} }
} }
@ -891,24 +887,28 @@ namespace Mist{
// No? Then we want to try reading the next file. // No? Then we want to try reading the next file.
// No segments? Wait until next playlist reloading time. // No segments? Wait until next playlist reloading time.
if (idx != INVALID_TRACK_ID){
currentPlaylist = getMappedTrackPlaylist(M.getID(idx));
}else{
currentPlaylist = firstSegment(); currentPlaylist = firstSegment();
}
if (currentPlaylist < 0){ if (currentPlaylist < 0){
VERYHIGH_MSG("Waiting for segments..."); VERYHIGH_MSG("Waiting for segments...");
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();} keepAlive();
Util::wait(500); Util::wait(500);
continue; continue;
} }
// Now that we know our playlist is up-to-date, actually try to read the file. // Now that we know our playlist is up-to-date, actually try to read the file.
VERYHIGH_MSG("Moving on to next TS segment (variant %" PRIu32 ")", currentPlaylist); VERYHIGH_MSG("Moving on to next TS segment (variant %" PRIu64 ")", currentPlaylist);
if (readNextFile()){ if (readNextFile()){
MEDIUM_MSG("Next segment read successfully"); MEDIUM_MSG("Next segment read successfully");
finished = false; finished = false;
continue; // Success! Continue regular parsing. continue; // Success! Continue regular parsing.
}else{ }else{
if (selectedTracks.size() > 1){ if (userSelect.size() > 1){
// failed to read segment for playlist, dropping it // failed to read segment for playlist, dropping it
WARN_MSG("Dropping variant %" PRIu32 " because we couldn't read anything from it", currentPlaylist); WARN_MSG("Dropping variant %" PRIu64 " because we couldn't read anything from it", currentPlaylist);
tthread::lock_guard<tthread::mutex> guard(entryMutex); tthread::lock_guard<tthread::mutex> guard(entryMutex);
listEntries.erase(currentPlaylist); listEntries.erase(currentPlaylist);
if (listEntries.size()){continue;} if (listEntries.size()){continue;}
@ -946,17 +946,17 @@ namespace Mist{
currentIndex = plistEntry - 1; currentIndex = plistEntry - 1;
currentPlaylist = getMappedTrackPlaylist(trackId); currentPlaylist = getMappedTrackPlaylist(trackId);
INFO_MSG("Seeking to index %d on playlist %d", currentIndex, currentPlaylist); INFO_MSG("Seeking to index %zu on playlist %" PRIu64, currentIndex, currentPlaylist);
{// Lock mutex for listEntries {// Lock mutex for listEntries
tthread::lock_guard<tthread::mutex> guard(entryMutex); tthread::lock_guard<tthread::mutex> guard(entryMutex);
if (!listEntries.count(currentPlaylist)){ if (!listEntries.count(currentPlaylist)){
WARN_MSG("Playlist %d not loaded, aborting seek", currentPlaylist); WARN_MSG("Playlist %" PRIu64 " not loaded, aborting seek", currentPlaylist);
return; return;
} }
std::deque<playListEntries> &curPlaylist = listEntries[currentPlaylist]; std::deque<playListEntries> &curPlaylist = listEntries[currentPlaylist];
if (curPlaylist.size() <= currentIndex){ if (curPlaylist.size() <= currentIndex){
WARN_MSG("Playlist %d has %zu <= %d entries, aborting seek", currentPlaylist, WARN_MSG("Playlist %" PRIu64 " has %zu <= %zu entries, aborting seek", currentPlaylist,
curPlaylist.size(), currentIndex); curPlaylist.size(), currentIndex);
return; return;
} }
@ -1179,7 +1179,7 @@ namespace Mist{
tthread::lock_guard<tthread::mutex> guard(entryMutex); tthread::lock_guard<tthread::mutex> guard(entryMutex);
std::deque<playListEntries> &curList = listEntries[currentPlaylist]; std::deque<playListEntries> &curList = listEntries[currentPlaylist];
if (!curList.size()){ if (!curList.size()){
WARN_MSG("no entries found in playlist: %d!", currentPlaylist); WARN_MSG("no entries found in playlist: %" PRIu64 "!", currentPlaylist);
return false; return false;
} }
if (!streamIsLive){ if (!streamIsLive){
@ -1204,7 +1204,7 @@ namespace Mist{
if (Util::bootSecs() < ntry.timestamp){ if (Util::bootSecs() < ntry.timestamp){
VERYHIGH_MSG("Slowing down to realtime..."); VERYHIGH_MSG("Slowing down to realtime...");
while (Util::bootSecs() < ntry.timestamp){ while (Util::bootSecs() < ntry.timestamp){
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();} keepAlive();
Util::wait(250); Util::wait(250);
} }
} }
@ -1228,7 +1228,7 @@ namespace Mist{
/// this will keep the playlists in sync while reading segments. /// this will keep the playlists in sync while reading segments.
size_t inputHLS::firstSegment(){ size_t inputHLS::firstSegment(){
// Only one selected? Immediately return the right playlist. // Only one selected? Immediately return the right playlist.
if (userSelect.size() == 1){return ((M.getID(userSelect.begin()->first) >> 16) & 0xFFFF);} if (userSelect.size() == 1){return getMappedTrackPlaylist(M.getID(userSelect.begin()->first));}
uint64_t firstTimeStamp = 0; uint64_t firstTimeStamp = 0;
int tmpId = -1; int tmpId = -1;
int segCount = 0; int segCount = 0;

View file

@ -121,7 +121,7 @@ namespace Mist{
Socket::Connection conn; Socket::Connection conn;
TS::Packet tsBuf; TS::Packet tsBuf;
int firstSegment(); size_t firstSegment();
void waitForNextSegment(); void waitForNextSegment();
void readPMT(); void readPMT();
bool checkArguments(); bool checkArguments();
@ -130,7 +130,6 @@ namespace Mist{
bool needHeader(){return true;} bool needHeader(){return true;}
void getNext(size_t idx = INVALID_TRACK_ID); void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
FILE *inFile; FILE *inFile;
FILE *tsFile; FILE *tsFile;
@ -141,6 +140,9 @@ namespace Mist{
void parseStreamHeader(); void parseStreamHeader();
uint32_t getMappedTrackId(uint64_t id);
uint32_t getMappedTrackPlaylist(uint64_t id);
uint64_t getOriginalTrackId(uint32_t playlistId, uint32_t id);
size_t getEntryId(uint32_t playlistId, uint64_t bytePos); size_t getEntryId(uint32_t playlistId, uint64_t bytePos);
}; };
}// namespace Mist }// namespace Mist

View file

@ -39,17 +39,19 @@ namespace Mist{
return true; return true;
} }
std::string inputPlaylist::streamMainLoop(){ void inputPlaylist::streamMainLoop(){
bool seenValidEntry = true; bool seenValidEntry = true;
uint64_t startTime = Util::bootMS(); uint64_t startTime = Util::bootMS();
while (config->is_active && nProxy.userClient.isAlive()){ while (config->is_active){
struct tm *wTime; struct tm *wTime;
time_t nowTime = time(0); time_t nowTime = time(0);
wTime = localtime(&nowTime); wTime = localtime(&nowTime);
wallTime = wTime->tm_hour * 60 + wTime->tm_min; wallTime = wTime->tm_hour * 60 + wTime->tm_min;
nProxy.userClient.keepAlive();
reloadPlaylist(); reloadPlaylist();
if (!playlist.size()){return "No entries in playlist";} if (!playlist.size()){
Util::logExitReason("No entries in playlist");
return;
}
++playlistIndex; ++playlistIndex;
if (playlistIndex >= playlist.size()){ if (playlistIndex >= playlist.size()){
if (!seenValidEntry){ if (!seenValidEntry){
@ -103,7 +105,7 @@ namespace Mist{
continue; continue;
} }
seenValidEntry = true; seenValidEntry = true;
while (Util::Procs::isRunning(spawn_pid) && nProxy.userClient.isAlive() && config->is_active){ while (Util::Procs::isRunning(spawn_pid) && config->is_active){
Util::sleep(1000); Util::sleep(1000);
if (reloadOn != 0xFFFF){ if (reloadOn != 0xFFFF){
time_t nowTime = time(0); time_t nowTime = time(0);
@ -117,13 +119,9 @@ namespace Mist{
Util::Procs::Stop(spawn_pid); Util::Procs::Stop(spawn_pid);
} }
} }
nProxy.userClient.keepAlive();
} }
if (!config->is_active && Util::Procs::isRunning(spawn_pid)){Util::Procs::Stop(spawn_pid);} if (!config->is_active && Util::Procs::isRunning(spawn_pid)){Util::Procs::Stop(spawn_pid);}
} }
if (!config->is_active){return "received deactivate signal";}
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
return "Unknown";
} }
void inputPlaylist::reloadPlaylist(){ void inputPlaylist::reloadPlaylist(){

View file

@ -11,9 +11,10 @@ namespace Mist{
protected: protected:
bool checkArguments(); bool checkArguments();
bool readHeader(){return true;} bool readHeader(){return true;}
virtual void parseStreamHeader(){myMeta.tracks[1].codec = "PLACEHOLDER";} virtual void parseStreamHeader(){}
std::string streamMainLoop(); void streamMainLoop();
virtual bool needHeader(){return false;} virtual bool needHeader(){return false;}
virtual bool publishesTracks(){return false;}
private: private:
void reloadPlaylist(); void reloadPlaylist();

View file

@ -45,6 +45,8 @@ namespace Mist{
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][0u].append("MPEG2"); capa["codecs"][0u][0u].append("MPEG2");
capa["codecs"][0u][0u].append("VP8");
capa["codecs"][0u][0u].append("VP9");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3"); capa["codecs"][0u][1u].append("AC3");
@ -194,45 +196,45 @@ namespace Mist{
tcpCon.close(); tcpCon.close();
} }
std::string InputRTSP::streamMainLoop(){ void InputRTSP::streamMainLoop(){
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); Comms::Statistics statComm;
uint64_t startTime = Util::epoch(); uint64_t startTime = Util::epoch();
uint64_t lastPing = Util::bootSecs(); uint64_t lastPing = Util::bootSecs();
uint64_t lastSecs = 0;
while (keepAlive() && parsePacket()){ while (keepAlive() && parsePacket()){
uint64_t currSecs = Util::bootSecs();
handleUDP(); handleUDP();
if (Util::bootSecs() - lastPing > 30){ if (Util::bootSecs() - lastPing > 30){
sendCommand("GET_PARAMETER", url.getUrl(), ""); sendCommand("GET_PARAMETER", url.getUrl(), "");
lastPing = Util::bootSecs(); lastPing = Util::bootSecs();
} }
if (lastSecs != currSecs){ if (lastSecs != currSecs){
if (!statsPage.getData()){ lastSecs = currSecs;
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); // Connect to stats for INPUT detection
} statComm.reload();
if (statsPage.getData()){ if (statComm){
if (!statsPage.isAlive()){ if (!statComm.isAlive()){
config->is_active = false; config->is_active = false;
statsPage.finish(); Util::logExitReason("received shutdown request from controller");
return "received shutdown request from controller"; return;
} }
uint64_t now = Util::epoch(); uint64_t now = Util::bootSecs();
IPC::statExchange tmpEx(statsPage.getData()); statComm.setNow(now);
tmpEx.now(now); statComm.setCRC(getpid());
tmpEx.crc(getpid()); statComm.setStream(streamName);
tmpEx.streamName(streamName); statComm.setConnector("INPUT:" + capa["name"].asStringRef());
tmpEx.connector("INPUT"); statComm.setUp(tcpCon.dataUp());
tmpEx.up(tcpCon.dataUp()); statComm.setDown(tcpCon.dataDown());
tmpEx.down(tcpCon.dataDown()); statComm.setTime(now - startTime);
tmpEx.time(now - startTime); statComm.setLastSecond(0);
tmpEx.lastSecond(0); statComm.setHost(getConnectedBinHost());
statsPage.keepAlive(); statComm.keepAlive();
} }
} }
} }
statsPage.finish(); if (!tcpCon){
if (!tcpCon){return "TCP connection closed";} Util::logExitReason("TCP connection closed");
if (!config->is_active){return "received deactivate signal";} }
if (!keepAlive()){return "buffer shutdown";}
return "Unknown";
} }
bool InputRTSP::parsePacket(bool mustHave){ bool InputRTSP::parsePacket(bool mustHave){

View file

@ -17,6 +17,11 @@ namespace Mist{
void incoming(const DTSC::Packet &pkt); void incoming(const DTSC::Packet &pkt);
void incomingRTP(const uint64_t track, const RTP::Packet &p); void incomingRTP(const uint64_t track, const RTP::Packet &p);
virtual std::string getConnectedBinHost(){
if (tcpCon){return tcpCon.getBinHost();}
return Input::getConnectedBinHost();
}
protected: protected:
// Private Functions // Private Functions
bool checkArguments(); bool checkArguments();
@ -29,7 +34,7 @@ namespace Mist{
const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth = true); const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth = true);
bool parsePacket(bool mustHave = false); bool parsePacket(bool mustHave = false);
bool handleUDP(); bool handleUDP();
std::string streamMainLoop(); void streamMainLoop();
Socket::Connection tcpCon; Socket::Connection tcpCon;
HTTP::Parser sndH, recH; HTTP::Parser sndH, recH;
HTTP::URL url; HTTP::URL url;

View file

@ -463,7 +463,7 @@ namespace Mist{
tmpIdx = meta.addTrack(0, 0, 0, 0); tmpIdx = meta.addTrack(0, 0, 0, 0);
} }
std::string inputTS::streamMainLoop(){ void inputTS::streamMainLoop(){
meta.removeTrack(tmpIdx); meta.removeTrack(tmpIdx);
INFO_MSG("Removed temptrack %zu", tmpIdx); INFO_MSG("Removed temptrack %zu", tmpIdx);
Comms::Statistics statComm; Comms::Statistics statComm;
@ -495,7 +495,8 @@ namespace Mist{
} }
if (!tcpCon){ if (!tcpCon){
config->is_active = false; config->is_active = false;
return "end of streamed input"; Util::logExitReason("end of streamed input");
return;
} }
}else{ }else{
std::string leftData; std::string leftData;
@ -557,17 +558,19 @@ namespace Mist{
if (statComm){ if (statComm){
if (!statComm.isAlive()){ if (!statComm.isAlive()){
config->is_active = false; config->is_active = false;
return "received shutdown request from controller"; Util::logExitReason("received shutdown request from controller");
return;
} }
uint64_t now = Util::bootSecs(); uint64_t now = Util::bootSecs();
statComm.setNow(now); statComm.setNow(now);
statComm.setCRC(getpid()); statComm.setCRC(getpid());
statComm.setStream(streamName); statComm.setStream(streamName);
statComm.setConnector("INPUT"); statComm.setConnector("INPUT:" + capa["name"].asStringRef());
statComm.setUp(0); statComm.setUp(0);
statComm.setDown(downCounter + tcpCon.dataDown()); statComm.setDown(downCounter + tcpCon.dataDown());
statComm.setTime(now - startTime); statComm.setTime(now - startTime);
statComm.setLastSecond(0); statComm.setLastSecond(0);
statComm.setHost(getConnectedBinHost());
statComm.keepAlive(); statComm.keepAlive();
} }
@ -577,7 +580,8 @@ namespace Mist{
if (hasStarted && !threadTimer.size()){ if (hasStarted && !threadTimer.size()){
if (!isAlwaysOn()){ if (!isAlwaysOn()){
config->is_active = false; config->is_active = false;
return "no active threads and we had input in the past"; Util::logExitReason("no active threads and we had input in the past");
return;
}else{ }else{
hasStarted = false; hasStarted = false;
} }
@ -607,13 +611,13 @@ namespace Mist{
if (Util::bootSecs() - noDataSince > 20){ if (Util::bootSecs() - noDataSince > 20){
if (!isAlwaysOn()){ if (!isAlwaysOn()){
config->is_active = false; config->is_active = false;
return "No packets received for 20 seconds - terminating"; Util::logExitReason("no packets received for 20 seconds");
return;
}else{ }else{
noDataSince = Util::bootSecs(); noDataSince = Util::bootSecs();
} }
} }
} }
return "received shutdown request";
} }
void inputTS::finish(){ void inputTS::finish(){

View file

@ -14,6 +14,11 @@ namespace Mist{
~inputTS(); ~inputTS();
bool needsLock(); bool needsLock();
virtual std::string getConnectedBinHost(){
if (tcpCon){return tcpCon.getBinHost();}
/// \TODO Handle UDP
return Input::getConnectedBinHost();
}
protected: protected:
// Private Functions // Private Functions
bool checkArguments(); bool checkArguments();
@ -25,7 +30,7 @@ namespace Mist{
void readPMT(); void readPMT();
bool openStreamSource(); bool openStreamSource();
void parseStreamHeader(); void parseStreamHeader();
std::string streamMainLoop(); void streamMainLoop();
void finish(); void finish();
FILE *inFile; ///< The input file with ts data FILE *inFile; ///< The input file with ts data
TS::Stream tsStream; ///< Used for parsing the incoming ts stream TS::Stream tsStream; ///< Used for parsing the incoming ts stream

View file

@ -303,10 +303,15 @@ namespace Mist{
/// Initiates/continues negotiation with the buffer as well /// Initiates/continues negotiation with the buffer as well
///\param packet The packet to buffer ///\param packet The packet to buffer
void InOutBase::bufferLivePacket(const DTSC::Packet &packet){ void InOutBase::bufferLivePacket(const DTSC::Packet &packet){
size_t idx = M.trackIDToIndex(packet.getTrackId(), getpid());
if (idx == INVALID_TRACK_ID){
INFO_MSG("Packet for track %zu has no valid index!", packet.getTrackId());
return;
}
char *data; char *data;
size_t dataLen; size_t dataLen;
packet.getString("data", data, dataLen); packet.getString("data", data, dataLen);
bufferLivePacket(packet.getTime(), packet.getInt("offset"), packet.getTrackId(), data, dataLen, bufferLivePacket(packet.getTime(), packet.getInt("offset"), idx, data, dataLen,
packet.getInt("bpos"), packet.getFlag("keyframe")); packet.getInt("bpos"), packet.getFlag("keyframe"));
/// \TODO META Build something that should actually be able to deal with "extra" values /// \TODO META Build something that should actually be able to deal with "extra" values
} }
@ -329,7 +334,7 @@ namespace Mist{
// Assume this is the first packet on the track // Assume this is the first packet on the track
isKeyframe = true; isKeyframe = true;
}else{ }else{
if (packTime - tPages.getInt("lastkeytime", tPages.getEndPos() - 1) >= 5000){ if (packTime - tPages.getInt("lastkeytime", tPages.getEndPos() - 1) >= AUDIO_KEY_INTERVAL){
isKeyframe = true; isKeyframe = true;
} }
} }
@ -343,9 +348,8 @@ namespace Mist{
packTime, M.getLastms(packTrack)); packTime, M.getLastms(packTrack));
return; return;
} }
if (packet.getTime() > myMeta.tracks[tid].lastms + 30000 && myMeta.tracks[tid].lastms){ if (packTime > M.getLastms(packTrack) + 30000 && M.getLastms(packTrack)){
WARN_MSG("Sudden jump in timestamp from %" PRIu64 " to %" PRIu64, myMeta.tracks[tid].lastms, WARN_MSG("Sudden jump in timestamp from %" PRIu64 " to %" PRIu64, M.getLastms(packTrack), packTime);
packet.getTime());
} }
} }
@ -361,7 +365,7 @@ namespace Mist{
tPages.setInt("firstkey", 0, 0); tPages.setInt("firstkey", 0, 0);
tPages.setInt("firsttime", packTime, 0); tPages.setInt("firsttime", packTime, 0);
tPages.setInt("size", DEFAULT_DATA_PAGE_SIZE, 0); tPages.setInt("size", DEFAULT_DATA_PAGE_SIZE, 0);
tPages.setInt("keycount", 0, endPage); tPages.setInt("keycount", 0, 0);
tPages.setInt("avail", 0, 0); tPages.setInt("avail", 0, 0);
++endPage; ++endPage;
} }

View file

@ -12,7 +12,7 @@ int spawnForked(Socket::Connection &S){
void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){ void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){
HIGH_MSG("USR1 received - triggering rolling restart"); HIGH_MSG("USR1 received - triggering rolling restart");
Util::Config::is_restarting = true; Util::Config::is_restarting = true;
Util::Config::logExitReason("setting is_active to false because of received USR1"); Util::logExitReason("signal USR1");
Util::Config::is_active = false; Util::Config::is_active = false;
} }
@ -47,5 +47,7 @@ int main(int argc, char *argv[]){
return tmp.run(); return tmp.run();
} }
} }
INFO_MSG("Exit reason: %s", Util::exitReason);
return 0; return 0;
} }

View file

@ -48,7 +48,6 @@ namespace Mist{
Output::Output(Socket::Connection &conn) : myConn(conn){ Output::Output(Socket::Connection &conn) : myConn(conn){
pushing = false; pushing = false;
pushIsOngoing = false;
firstTime = 0; firstTime = 0;
firstPacketTime = 0xFFFFFFFFFFFFFFFFull; firstPacketTime = 0xFFFFFFFFFFFFFFFFull;
lastPacketTime = 0; lastPacketTime = 0;
@ -67,6 +66,10 @@ namespace Mist{
lastRecv = Util::bootSecs(); lastRecv = Util::bootSecs();
if (myConn){ if (myConn){
setBlocking(true); setBlocking(true);
//Make sure that if the socket is a non-stdio socket, we close it when forking
if (myConn.getSocket() > 2){
Util::Procs::socketList.insert(myConn.getSocket());
}
}else{ }else{
WARN_MSG("Warning: MistOut created with closed socket!"); WARN_MSG("Warning: MistOut created with closed socket!");
} }
@ -129,6 +132,7 @@ namespace Mist{
}else{ }else{
MEDIUM_MSG("onFail '%s': %s", streamName.c_str(), msg.c_str()); MEDIUM_MSG("onFail '%s': %s", streamName.c_str(), msg.c_str());
} }
Util::logExitReason(msg.c_str());
isInitialized = false; isInitialized = false;
wantRequest = true; wantRequest = true;
parseData = false; parseData = false;
@ -143,7 +147,7 @@ namespace Mist{
} }
reconnect(); reconnect();
// if the connection failed, fail // if the connection failed, fail
if (streamName.size() < 1){ if (!meta || streamName.size() < 1){
onFail("Could not connect to stream", true); onFail("Could not connect to stream", true);
return; return;
} }
@ -181,19 +185,8 @@ namespace Mist{
if (shmSessions.mapped){ if (shmSessions.mapped){
char shmEmpty[SHM_SESSIONS_ITEM]; char shmEmpty[SHM_SESSIONS_ITEM];
memset(shmEmpty, 0, SHM_SESSIONS_ITEM); memset(shmEmpty, 0, SHM_SESSIONS_ITEM);
std::string host = statComm.getHost(); std::string host;
if (host.substr(0, 12) == Socket::hostBytesToStr(statComm.getHost().data(), 16, host);
std::string("\000\000\000\000\000\000\000\000\000\000\377\377", 12)){
char tmpstr[16];
snprintf(tmpstr, 16, "%hhu.%hhu.%hhu.%hhu", host[12], host[13], host[14], host[15]);
host = tmpstr;
}else{
char tmpstr[40];
snprintf(tmpstr, 40, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
host[0], host[1], host[2], host[3], host[4], host[5], host[6], host[7],
host[8], host[9], host[10], host[11], host[12], host[13], host[14], host[15]);
host = tmpstr;
}
uint32_t shmOffset = 0; uint32_t shmOffset = 0;
const std::string &cName = capa["name"].asStringRef(); const std::string &cName = capa["name"].asStringRef();
while (shmOffset + SHM_SESSIONS_ITEM < SHM_SESSIONS_SIZE){ while (shmOffset + SHM_SESSIONS_ITEM < SHM_SESSIONS_SIZE){
@ -259,12 +252,19 @@ namespace Mist{
std::string Output::getConnectedHost(){return myConn.getHost();} std::string Output::getConnectedHost(){return myConn.getHost();}
std::string Output::getConnectedBinHost(){return myConn.getBinHost();} std::string Output::getConnectedBinHost(){
if (!prevHost.size()){
if (myConn && myConn.getPureSocket() != -1){
prevHost = myConn.getBinHost();
}
if (!prevHost.size()){prevHost.assign("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001", 16);}
}
return prevHost;
}
bool Output::isReadyForPlay(){ bool Output::isReadyForPlay(){
// If a protocol does not support any codecs, we assume you know what you're doing // If a protocol does not support any codecs, we assume you know what you're doing
if (!capa.isMember("codecs")){return true;} if (!capa.isMember("codecs")){return true;}
if (isPushing()){return true;}
if (!isInitialized){initialize();} if (!isInitialized){initialize();}
meta.refresh(); meta.refresh();
if (getSupportedTracks().size()){ if (getSupportedTracks().size()){
@ -363,27 +363,25 @@ namespace Mist{
while (!meta && ++attempts < 20 && Util::streamAlive(streamName)){ while (!meta && ++attempts < 20 && Util::streamAlive(streamName)){
meta.reInit(streamName, false); meta.reInit(streamName, false);
} }
if (!meta){ if (!meta){return;}
onFail("Could not connect to stream data", true);
return;
}
meta.refresh(); meta.refresh();
isInitialized = true; isInitialized = true;
statComm.reload(); statComm.reload();
stats(true); stats(true);
if (!pushing){selectDefaultTracks();} if (isPushing()){return;}
if (!M.getVod() && !isReadyForPlay()){ if (!isRecording() && !M.getVod() && !isReadyForPlay()){
uint64_t waitUntil = Util::epoch() + 30; uint64_t waitUntil = Util::bootSecs() + 45;
while (!M.getVod() && !isReadyForPlay()){ while (!M.getVod() && !isReadyForPlay()){
if (Util::epoch() > waitUntil + 45 || (!userSelect.size() && Util::epoch() > waitUntil)){ if (Util::bootSecs() > waitUntil || (!userSelect.size() && Util::bootSecs() > waitUntil)){
INFO_MSG("Giving up waiting for playable tracks. Stream: %s, IP: %s", streamName.c_str(), INFO_MSG("Giving up waiting for playable tracks. IP: %s", getConnectedHost().c_str());
getConnectedHost().c_str());
break; break;
} }
Util::wait(500); Util::wait(500);
meta.refresh();
stats(); stats();
} }
} }
selectDefaultTracks();
} }
std::set<size_t> Output::getSupportedTracks(const std::string &type) const{ std::set<size_t> Output::getSupportedTracks(const std::string &type) const{
@ -403,22 +401,11 @@ namespace Mist{
bool autoSeek = buffer.size(); bool autoSeek = buffer.size();
uint64_t seekTarget = currentTime(); uint64_t seekTarget = currentTime();
std::set<size_t> newSelects = std::set<size_t> newSelects =
Util::wouldSelect(myMeta, targetParams, capa, UA, autoSeek ? seekTarget : 0); Util::wouldSelect(M, targetParams, capa, UA, autoSeek ? seekTarget : 0);
std::set<size_t> oldSel;
for (std::set<unsigned long>::iterator selIt = selectedTracks.begin();
selIt != selectedTracks.end(); ++selIt){
oldSel.insert(*selIt);
}
if (oldSel == newSelects){
// No new selections? Do nothing, return no change.
return false;
}
if (autoSeek){ if (autoSeek){
std::set<size_t> toRemove; std::set<size_t> toRemove;
for (std::set<size_t>::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ for (std::set<size_t>::iterator it = newSelects.begin(); it != newSelects.end(); it++){
// autoSeeking and target not in bounds? Drop it too. // autoSeeking and target not in bounds? Drop it too.
if (M.getLastms(*it) < std::max(seekTarget, (uint64_t)6000lu) - 6000){ if (M.getLastms(*it) < std::max(seekTarget, (uint64_t)6000lu) - 6000){
toRemove.insert(*it); toRemove.insert(*it);
@ -426,7 +413,7 @@ namespace Mist{
} }
// remove those from selectedtracks // remove those from selectedtracks
for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){ for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
wouldSelect.erase(*it); newSelects.erase(*it);
} }
} }
@ -435,7 +422,7 @@ namespace Mist{
userSelect.clear(); userSelect.clear();
// Select tracks here! // Select tracks here!
for (std::set<size_t>::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ for (std::set<size_t>::iterator it = newSelects.begin(); it != newSelects.end(); it++){
userSelect[*it].reload(streamName, *it); userSelect[*it].reload(streamName, *it);
} }
@ -453,6 +440,14 @@ namespace Mist{
parseData = false; parseData = false;
} }
///Returns the timestamp of the next upcoming keyframe after thisPacket, or 0 if that cannot be determined (yet).
uint64_t Output::nextKeyTime(){
DTSC::Keys keys(M.keys(getMainSelectedTrack()));
if (!keys.getValidCount()){return 0;}
size_t keyNum = keys.getNumForTime(lastPacketTime);
return keys.getTime(keyNum+1);
}
uint64_t Output::pageNumForKey(size_t trackId, size_t keyNum){ uint64_t Output::pageNumForKey(size_t trackId, size_t keyNum){
const Util::RelAccX &tPages = M.pages(trackId); const Util::RelAccX &tPages = M.pages(trackId);
for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){
@ -671,7 +666,7 @@ namespace Mist{
for (std::set<size_t>::iterator it = seekTracks.begin(); it != seekTracks.end(); it++){ for (std::set<size_t>::iterator it = seekTracks.begin(); it != seekTracks.end(); it++){
seek(*it, pos, false); seek(*it, pos, false);
} }
firstTime = Util::bootMS() - currentTime(); firstTime = Util::bootMS() - (currentTime() * realTime / 1000);
} }
bool Output::seek(size_t tid, uint64_t pos, bool getNextKey){ bool Output::seek(size_t tid, uint64_t pos, bool getNextKey){
@ -690,7 +685,7 @@ namespace Mist{
stats(); stats();
} }
} }
if (meta.getLastms(tid) <= pos){ if (meta.getLastms(tid) < pos){
WARN_MSG("Aborting seek to %" PRIu64 "ms in track %zu: past end of track (= %" PRIu64 "ms).", WARN_MSG("Aborting seek to %" PRIu64 "ms in track %zu: past end of track (= %" PRIu64 "ms).",
pos, tid, meta.getLastms(tid)); pos, tid, meta.getLastms(tid));
userSelect.erase(tid); userSelect.erase(tid);
@ -751,7 +746,7 @@ namespace Mist{
if (curPage[tid].mapped[tmp.offset]){return seek(tid, pos, getNextKey);} if (curPage[tid].mapped[tmp.offset]){return seek(tid, pos, getNextKey);}
FAIL_MSG("Track %zu no data (key %zu@%" PRIu64 ") - timeout", tid, keyNum + (getNextKey ? 1 : 0), tmp.offset); FAIL_MSG("Track %zu no data (key %zu@%" PRIu64 ") - timeout", tid, keyNum + (getNextKey ? 1 : 0), tmp.offset);
userSelect.erase(tid); userSelect.erase(tid);
firstTime = Util::bootMS() - buffer.begin()->time; firstTime = Util::bootMS() - (buffer.begin()->time * realTime / 1000);
return false; return false;
} }
@ -761,6 +756,7 @@ namespace Mist{
/// needsLookAhead+minKeepAway ms from the end. Unless lastms < 5000, then it seeks to the first /// needsLookAhead+minKeepAway ms from the end. Unless lastms < 5000, then it seeks to the first
/// keyframe of the main track. Aborts if there is no main track or it has no keyframes. /// keyframe of the main track. Aborts if there is no main track or it has no keyframes.
void Output::initialSeek(){ void Output::initialSeek(){
if (!meta){return;}
uint64_t seekPos = 0; uint64_t seekPos = 0;
if (meta.getLive()){ if (meta.getLive()){
size_t mainTrack = getMainSelectedTrack(); size_t mainTrack = getMainSelectedTrack();
@ -980,6 +976,7 @@ namespace Mist{
/// It seeks to the last sync'ed keyframe of the main track, no closer than needsLookAhead+minKeepAway ms from the end. /// It seeks to the last sync'ed keyframe of the main track, no closer than needsLookAhead+minKeepAway ms from the end.
/// Aborts if not live, there is no main track or it has no keyframes. /// Aborts if not live, there is no main track or it has no keyframes.
bool Output::liveSeek(){ bool Output::liveSeek(){
if (!realTime){return false;}//Makes no sense when playing in turbo mode
static uint32_t seekCount = 2; static uint32_t seekCount = 2;
uint64_t seekPos = 0; uint64_t seekPos = 0;
if (!meta.getLive()){return false;} if (!meta.getLive()){return false;}
@ -989,11 +986,15 @@ namespace Mist{
uint64_t cTime = thisPacket.getTime(); uint64_t cTime = thisPacket.getTime();
uint64_t mKa = getMinKeepAway(); uint64_t mKa = getMinKeepAway();
if (!maxSkipAhead){ if (!maxSkipAhead){
bool noReturn = false;
uint64_t newSpeed = 1000; uint64_t newSpeed = 1000;
if (lMs - mKa - needsLookAhead - extraKeepAway > cTime + 50){ if (lMs - mKa - needsLookAhead - extraKeepAway > cTime + 50){
// We need to speed up! // We need to speed up!
uint64_t diff = (lMs - mKa - needsLookAhead - extraKeepAway) - cTime; uint64_t diff = (lMs - mKa - needsLookAhead - extraKeepAway) - cTime;
if (diff > 1000){ if (diff > 3000){
noReturn = true;
newSpeed = 1000;
}else if (diff > 1000){
newSpeed = 750; newSpeed = 750;
}else if (diff > 500){ }else if (diff > 500){
newSpeed = 900; newSpeed = 900;
@ -1002,13 +1003,11 @@ namespace Mist{
} }
} }
if (realTime != newSpeed){ if (realTime != newSpeed){
HIGH_MSG("Changing playback speed from %" PRIu64 " to %" PRIu64 "(%" PRIu64 HIGH_MSG("Changing playback speed from %" PRIu64 " to %" PRIu64 "(%" PRIu64 " ms LA, %" PRIu64 " ms mKA, %lu eKA)", realTime, newSpeed, needsLookAhead, mKa, extraKeepAway);
" ms LA, %" PRIu64 " ms mKA, %lu eKA)", firstTime = Util::bootMS() - (cTime * newSpeed / 1000);
realTime, newSpeed, needsLookAhead, mKa, extraKeepAway);
firstTime = Util::bootMS() - cTime;
realTime = newSpeed; realTime = newSpeed;
} }
return false; if (!noReturn){return false;}
} }
// cancel if there are no keys in the main track // cancel if there are no keys in the main track
if (mainTrack == INVALID_TRACK_ID){return false;} if (mainTrack == INVALID_TRACK_ID){return false;}
@ -1167,8 +1166,14 @@ namespace Mist{
while (keepGoing() && (wantRequest || parseData)){ while (keepGoing() && (wantRequest || parseData)){
if (wantRequest){requestHandler();} if (wantRequest){requestHandler();}
if (parseData){ if (parseData){
if (!isInitialized){initialize();} if (!isInitialized){
if (!sentHeader){ initialize();
if (!isInitialized){
onFail("Stream initialization failed");
break;
}
}
if (!sentHeader && keepGoing()){
DONTEVEN_MSG("sendHeader"); DONTEVEN_MSG("sendHeader");
sendHeader(); sendHeader();
} }
@ -1186,6 +1191,8 @@ namespace Mist{
Util::sleep(std::min(thisPacket.getTime() - Util::sleep(std::min(thisPacket.getTime() -
((((Util::bootMS() - firstTime) * 1000) + maxSkipAhead) / realTime), ((((Util::bootMS() - firstTime) * 1000) + maxSkipAhead) / realTime),
1000ul)); 1000ul));
//Make sure we stay responsive to requests and stats while waiting
if (wantRequest){requestHandler();}
stats(); stats();
} }
} }
@ -1217,6 +1224,8 @@ namespace Mist{
}else{ }else{
playbackSleep(sleepTime); playbackSleep(sleepTime);
} }
//Make sure we stay responsive to requests and stats while waiting
if (wantRequest){requestHandler();}
stats(); stats();
} }
if (!timeoutTries){ if (!timeoutTries){
@ -1237,6 +1246,7 @@ namespace Mist{
INFO_MSG("Switching to next push target filename: %s", newTarget.c_str()); INFO_MSG("Switching to next push target filename: %s", newTarget.c_str());
if (!connectToFile(newTarget)){ if (!connectToFile(newTarget)){
FAIL_MSG("Failed to open file, aborting: %s", newTarget.c_str()); FAIL_MSG("Failed to open file, aborting: %s", newTarget.c_str());
Util::logExitReason("failed to open file, aborting: %s", newTarget.c_str());
onFinish(); onFinish();
break; break;
} }
@ -1247,13 +1257,14 @@ namespace Mist{
}else{ }else{
if (!onFinish()){ if (!onFinish()){
INFO_MSG("Shutting down because planned stopping point reached"); INFO_MSG("Shutting down because planned stopping point reached");
Util::logExitReason("planned stopping point reached");
break; break;
} }
} }
} }
sendNext(); sendNext();
}else{ }else{
INFO_MSG("Shutting down because of stream end"); Util::logExitReason("end of stream");
/*LTS-START*/ /*LTS-START*/
if (Triggers::shouldTrigger("CONN_STOP", streamName)){ if (Triggers::shouldTrigger("CONN_STOP", streamName)){
std::string payload = std::string payload =
@ -1265,18 +1276,14 @@ namespace Mist{
} }
} }
if (!meta){ if (!meta){
Util::Config::logExitReason("No connection to the metadata"); Util::logExitReason("lost internal connection to stream data");
break; break;
} }
} }
stats(); stats();
} }
MEDIUM_MSG("MistOut client handler shutting down: %s, %s, %s", if (!myConn){Util::logExitReason("remote connection closed");}
myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", INFO_MSG("Client handler shutting down, exit reason: %s", Util::exitReason);
parseData ? "parsing_data" : "not_parsing_data");
if (Util::Config::exitReason.size()){
INFO_MSG("Logged exit reason: %s", Util::Config::exitReason.c_str());
}
onFinish(); onFinish();
/*LTS-START*/ /*LTS-START*/
@ -1460,42 +1467,66 @@ namespace Mist{
DTSC::Keys keys(M.keys(nxt.tid)); DTSC::Keys keys(M.keys(nxt.tid));
size_t thisKey = keys.getNumForTime(nxt.time); size_t thisKey = keys.getNumForTime(nxt.time);
while (!nextTime && keepGoing()){
// The next packet is either not available yet, or on another page
// Check if we have a next valid packet // Check if we have a next valid packet
if (memcmp(curPage[nxt.tid].mapped + nxt.offset + preLoad.getDataLen(), "\000\000\000\000", 4)){ if (memcmp(curPage[nxt.tid].mapped + nxt.offset + preLoad.getDataLen(), "\000\000\000\000", 4)){
nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset + preLoad.getDataLen()); nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset + preLoad.getDataLen());
// After 500ms, we assume the time will not update anymore if (!nextTime){
if (++emptyCount >= 20){break;} WARN_MSG("Next packet is available, but has no time. Please warn the developers if you see this message!");
}else{ dropTrack(nxt.tid, "EOP: invalid next packet");
if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){break;}
if (M.getPageNumberForKey(nxt.tid, thisKey + 1) != currentPage[nxt.tid]){
// Check if its on a different page
nextTime = keys.getTime(thisKey + 1);
}
if (++emptyCount >= 1000){
// after ~25 seconds, give up and drop the track.
dropTrack(nxt.tid, "EOP: data wait timeout");
return false; return false;
} }
Util::sleep(25); }else{
// we're waiting for new data to show up //no next packet yet!
if (emptyCount % 640 == 0){ //Check if this is the last packet of a VoD stream. Return success and drop the track.
reconnect(); // reconnect every 16 seconds
// if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile.
if (!meta){return false;}
}
}
}
if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){ if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
thisIdx = nxt.tid; thisIdx = nxt.tid;
dropTrack(nxt.tid, "end of VoD track reached", false); dropTrack(nxt.tid, "end of VoD track reached", false);
return true; return true;
} }
//Check if there exists a different page for the next key
size_t nextKeyPage = M.getPageNumberForKey(nxt.tid, thisKey + 1);
if (nextKeyPage != INVALID_KEY_NUM && nextKeyPage != currentPage[nxt.tid]){
// If so, the next key is our next packet
nextTime = keys.getTime(thisKey + 1);
}else{
//Okay, there's no next page yet, and no next packet on this page either.
//That means we're waiting for data to show up, somewhere.
// after ~25 seconds, give up and drop the track.
if (++emptyCount >= 1000){
dropTrack(nxt.tid, "EOP: data wait timeout");
return false;
}
//every ~1 second, check if the stream is not offline
if (emptyCount % 40 == 0 && M.getLive() && Util::getStreamStatus(streamName) == STRMSTAT_OFF){
Util::logExitReason("Stream source shut down");
thisPacket.null();
return true;
}
//every ~16 seconds, reconnect to metadata
if (emptyCount % 640 == 0){
reconnect();
if (!meta){
onFail("Could not connect to stream data", true);
thisPacket.null();
return true;
}
// if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile.
if (!meta){
Util::logExitReason("Attempted reconnect to source failed");
thisPacket.null();
return true;
}
return false;//no sleep after reconnect
}
//Fine! We didn't want a packet, anyway. Let's try again later.
Util::sleep(25);
return false;
}
}
// If we don't have a timestamp at all, this is due to a different cause. //If the next packet should've been before the current packet, something is wrong. Abort, abort!
if (!nextTime && (emptyCount < 20)){return false;}
if (nextTime < nxt.time){ if (nextTime < nxt.time){
dropTrack(nxt.tid, "time going backwards"); dropTrack(nxt.tid, "time going backwards");
return false; return false;
@ -1526,12 +1557,6 @@ namespace Mist{
// exchange the current packet in the buffer for the next one // exchange the current packet in the buffer for the next one
buffer.erase(buffer.begin()); buffer.erase(buffer.begin());
if (M.getVod() && nxt.time > M.getLastms(nxt.tid)){
dropTrack(nxt.tid, "detected last VoD packet");
return true;
}
buffer.insert(nxt); buffer.insert(nxt);
return true; return true;
@ -1541,8 +1566,10 @@ namespace Mist{
/// Outputs used as an input should return INPUT, outputs used for automation should return OUTPUT, others should return their proper name. /// Outputs used as an input should return INPUT, outputs used for automation should return OUTPUT, others should return their proper name.
/// The default implementation is usually good enough for all the non-INPUT types. /// The default implementation is usually good enough for all the non-INPUT types.
std::string Output::getStatsName(){ std::string Output::getStatsName(){
if (isPushing()){return "INPUT";} if (isPushing()){return "INPUT:" + capa["name"].asStringRef();}
if (config->hasOption("target") && config->getString("target").size()){return "OUTPUT";} if (config->hasOption("target") && config->getString("target").size()){
return "OUTPUT:" + capa["name"].asStringRef();
}
return capa["name"].asStringRef(); return capa["name"].asStringRef();
} }
@ -1554,6 +1581,25 @@ namespace Mist{
uint64_t now = Util::bootSecs(); uint64_t now = Util::bootSecs();
if (now == lastStats && !force){return;} if (now == lastStats && !force){return;}
if (isRecording()){
static uint64_t lastPushUpdate = now;
if (lastPushUpdate + 5 <= now){
JSON::Value pStat;
pStat["push_status_update"]["id"] = getpid();
JSON::Value & pData = pStat["push_status_update"]["status"];
pData["mediatime"] = currentTime();
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
pData["tracks"].append(it->first);
}
pData["bytes"] = myConn.dataUp();
pData["active_seconds"] = (now - myConn.connTime());
Socket::UDPConnection uSock;
uSock.SetDestination("localhost", 4242);
uSock.SendNow(pStat.toString());
lastPushUpdate = now;
}
}
if (!statComm){statComm.reload();} if (!statComm){statComm.reload();}
if (!statComm){return;} if (!statComm){return;}
@ -1562,16 +1608,13 @@ namespace Mist{
HIGH_MSG("Writing stats: %s, %s, %u, %lu, %lu", getConnectedHost().c_str(), streamName.c_str(), HIGH_MSG("Writing stats: %s, %s, %u, %lu, %lu", getConnectedHost().c_str(), streamName.c_str(),
crc & 0xFFFFFFFFu, myConn.dataUp(), myConn.dataDown()); crc & 0xFFFFFFFFu, myConn.dataUp(), myConn.dataDown());
/*LTS-START*/ /*LTS-START*/
if (statComm.getStatus() == COMM_STATUS_DISCONNECT){ if (statComm.getStatus() == COMM_STATUS_REQDISCONNECT){
onFail("Shutting down on controller request"); onFail("Shutting down on controller request");
return; return;
} }
/*LTS-END*/ /*LTS-END*/
statComm.setNow(now); statComm.setNow(now);
if (statComm.getHost() ==
std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){
statComm.setHost(getConnectedBinHost()); statComm.setHost(getConnectedBinHost());
}
statComm.setCRC(crc); statComm.setCRC(crc);
statComm.setStream(streamName); statComm.setStream(streamName);
statComm.setConnector(getStatsName()); statComm.setConnector(getStatsName());
@ -1597,14 +1640,36 @@ namespace Mist{
doSync(); doSync();
if (isPushing()){
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
it->second.keepAlive();
if (it->second.getStatus() == COMM_STATUS_REQDISCONNECT){
if (dropPushTrack(it->second.getTrack(), "disconnect request from buffer")){break;}
}
if (!it->second.isAlive()){
if (dropPushTrack(it->second.getTrack(), "track mapping no longer valid")){break;}
}
//if (Util::bootSecs() - M.getLastUpdated(it->first) > 5){
// if (dropPushTrack(it->second.getTrack(), "track updates being ignored by buffer")){break;}
//}
}
}else{
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
it->second.keepAlive(); it->second.keepAlive();
if (it->second.isAlive() && M.getLive() && it->second.getStatus() & COMM_STATUS_SOURCE){
if (Util::bootSecs() - M.getLastUpdated(it->first) > 3){
INFO_MSG("Not updating data for track %zu?", it->first);
} }
} }
} }
bool Output::dropPushTrack(uint32_t trackId, const std::string & dropReason){
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (it->second.getTrack() == trackId){
WARN_MSG("Dropping input track %" PRIu32 ": %s", trackId, dropReason.c_str());
userSelect.erase(it);
return true;
break;
}
}
return false;
} }
void Output::onRequest(){ void Output::onRequest(){
@ -1651,7 +1716,7 @@ namespace Mist{
// Initialize the stream source if needed, connect to it // Initialize the stream source if needed, connect to it
waitForStreamPushReady(); waitForStreamPushReady();
// pull the source setting from metadata // pull the source setting from metadata
strmSource = meta.getSource(); if (meta){strmSource = meta.getSource();}
if (!strmSource.size()){ if (!strmSource.size()){
FAIL_MSG("Push rejected - stream %s not configured or unavailable", streamName.c_str()); FAIL_MSG("Push rejected - stream %s not configured or unavailable", streamName.c_str());
@ -1683,13 +1748,10 @@ namespace Mist{
} }
} }
std::string smp = streamName.substr(0, streamName.find_first_of("+ ")); if (Triggers::shouldTrigger("STREAM_PUSH", streamName)){
if (Triggers::shouldTrigger("STREAM_PUSH", smp)){ std::string payload = streamName + "\n" + getConnectedHost() + "\n" + capa["name"].asStringRef() + "\n" + reqUrl;
std::string payload = if (!Triggers::doTrigger("STREAM_PUSH", payload, streamName)){
streamName + "\n" + getConnectedHost() + "\n" + capa["name"].asStringRef() + "\n" + reqUrl; WARN_MSG("Push from %s rejected by STREAM_PUSH trigger", getConnectedHost().c_str());
if (!Triggers::doTrigger("STREAM_PUSH", payload, smp)){
FAIL_MSG("Push from %s to %s rejected - STREAM_PUSH trigger denied the push",
getConnectedHost().c_str(), streamName.c_str());
pushing = false; pushing = false;
return false; return false;
} }
@ -1698,8 +1760,7 @@ namespace Mist{
if (IP != ""){ if (IP != ""){
if (!myConn.isAddress(IP)){ if (!myConn.isAddress(IP)){
FAIL_MSG("Push from %s to %s rejected - source host not whitelisted", WARN_MSG("Push from %s rejected; not whitelisted", getConnectedHost().c_str());
getConnectedHost().c_str(), streamName.c_str());
pushing = false; pushing = false;
return false; return false;
} }
@ -1712,7 +1773,31 @@ namespace Mist{
void Output::waitForStreamPushReady(){ void Output::waitForStreamPushReady(){
uint8_t streamStatus = Util::getStreamStatus(streamName); uint8_t streamStatus = Util::getStreamStatus(streamName);
MEDIUM_MSG("Current status for %s buffer is %u", streamName.c_str(), streamStatus); MEDIUM_MSG("Current status for %s buffer is %u", streamName.c_str(), streamStatus);
while (streamStatus != STRMSTAT_WAIT && streamStatus != STRMSTAT_READY && keepGoing()){ if (streamStatus == STRMSTAT_READY){
reconnect();
std::set<size_t> vTracks = M.getValidTracks(true);
INFO_MSG("Stream already active (%zu valid tracks) - check if it's not shutting down...", vTracks.size());
uint64_t oneTime = 0;
uint64_t twoTime = 0;
for (std::set<size_t>::iterator it = vTracks.begin(); it != vTracks.end(); ++it){
if (M.getLastms(*it) > oneTime){oneTime = M.getLastms(*it);}
}
Util::wait(2000);
for (std::set<size_t>::iterator it = vTracks.begin(); it != vTracks.end(); ++it){
if (M.getLastms(*it) > twoTime){twoTime = M.getLastms(*it);}
}
if (twoTime <= oneTime+500){
disconnect();
INFO_MSG("Waiting for stream reset before attempting push input accept (%" PRIu64 " <= %" PRIu64 "+500)", twoTime, oneTime);
while (streamStatus != STRMSTAT_OFF && keepGoing()){
userSelect.clear();
Util::wait(1000);
streamStatus = Util::getStreamStatus(streamName);
}
reconnect();
}
}
while (((streamStatus != STRMSTAT_WAIT && streamStatus != STRMSTAT_READY) || !meta) && keepGoing()){
INFO_MSG("Waiting for %s buffer to be ready... (%u)", streamName.c_str(), streamStatus); INFO_MSG("Waiting for %s buffer to be ready... (%u)", streamName.c_str(), streamStatus);
disconnect(); disconnect();
userSelect.clear(); userSelect.clear();
@ -1720,11 +1805,15 @@ namespace Mist{
streamStatus = Util::getStreamStatus(streamName); streamStatus = Util::getStreamStatus(streamName);
if (streamStatus == STRMSTAT_OFF || streamStatus == STRMSTAT_WAIT || streamStatus == STRMSTAT_READY){ if (streamStatus == STRMSTAT_OFF || streamStatus == STRMSTAT_WAIT || streamStatus == STRMSTAT_READY){
INFO_MSG("Reconnecting to %s buffer... (%u)", streamName.c_str(), streamStatus); INFO_MSG("Reconnecting to %s buffer... (%u)", streamName.c_str(), streamStatus);
Util::wait(500);
reconnect(); reconnect();
streamStatus = Util::getStreamStatus(streamName); streamStatus = Util::getStreamStatus(streamName);
} }
} }
if (streamStatus == STRMSTAT_READY || streamStatus == STRMSTAT_WAIT){reconnect();} if (streamStatus == STRMSTAT_READY || streamStatus == STRMSTAT_WAIT){reconnect();}
if (!meta){
onFail("Could not connect to stream data", true);
}
} }
void Output::selectAllTracks(){ void Output::selectAllTracks(){

View file

@ -65,6 +65,7 @@ namespace Mist{
/// This function is called whenever a packet is ready for sending. /// This function is called whenever a packet is ready for sending.
/// Inside it, thisPacket is guaranteed to contain a valid packet. /// Inside it, thisPacket is guaranteed to contain a valid packet.
virtual void sendNext(){}// REQUIRED! Others are optional. virtual void sendNext(){}// REQUIRED! Others are optional.
virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason);
bool getKeyFrame(); bool getKeyFrame();
bool prepareNext(); bool prepareNext();
virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true); virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true);
@ -105,6 +106,7 @@ namespace Mist{
std::set<sortedPageInfo> buffer; ///< A sorted list of next-to-be-loaded packets. 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 bool sought; ///< If a seek has been done, this is set to true. Used for seeking on
///< prepareNext(). ///< prepareNext().
std::string prevHost; ///< Old value for getConnectedBinHost, for caching
protected: // these are to be messed with by child classes protected: // these are to be messed with by child classes
virtual bool inlineRestartCapable() const{ virtual bool inlineRestartCapable() const{
return false; return false;
@ -128,6 +130,7 @@ namespace Mist{
Comms::Statistics statComm; Comms::Statistics statComm;
bool isBlocking; ///< If true, indicates that myConn is blocking. bool isBlocking; ///< If true, indicates that myConn is blocking.
uint32_t crc; ///< Checksum, if any, for usage in the stats. uint32_t crc; ///< Checksum, if any, for usage in the stats.
uint64_t nextKeyTime();
// stream delaying variables // stream delaying variables
uint64_t maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps. uint64_t maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps.
@ -148,7 +151,6 @@ namespace Mist{
virtual bool isPushing(){return pushing;}; virtual bool isPushing(){return pushing;};
bool allowPush(const std::string &passwd); bool allowPush(const std::string &passwd);
void waitForStreamPushReady(); void waitForStreamPushReady();
bool pushIsOngoing;
uint64_t firstPacketTime; uint64_t firstPacketTime;
uint64_t lastPacketTime; uint64_t lastPacketTime;

View file

@ -13,6 +13,8 @@
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/timing.h> #include <mist/timing.h>
uint64_t bootMsOffset;
namespace Mist{ namespace Mist{
OutCMAF::OutCMAF(Socket::Connection &conn) : HTTPOutput(conn){ OutCMAF::OutCMAF(Socket::Connection &conn) : HTTPOutput(conn){
@ -68,6 +70,8 @@ namespace Mist{
void OutCMAF::onHTTP(){ void OutCMAF::onHTTP(){
initialize(); initialize();
bootMsOffset = 0;
if (M.getLive()){bootMsOffset = M.getBootMsOffset();}
if (H.url.size() < streamName.length() + 7){ if (H.url.size() < streamName.length() + 7){
H.Clean(); H.Clean();
@ -440,6 +444,12 @@ namespace Mist{
} }
void hlsSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){ void hlsSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){
if (bootMsOffset){
uint64_t unixMs = start + bootMsOffset + (Util::unixMS() - Util::bootMS());
time_t uSecs = unixMs/1000;
struct tm * tVal = gmtime(&uSecs);
s << "#EXT-X-PROGRAM-DATE-TIME: " << (tVal->tm_year+1900) << "-" << std::setw(2) << std::setfill('0') << (tVal->tm_mon+1) << "-" << std::setw(2) << std::setfill('0') << tVal->tm_mday << "T" << std::setw(2) << std::setfill('0') << tVal->tm_hour << ":" << std::setw(2) << std::setfill('0') << tVal->tm_min << ":" << std::setw(2) << std::setfill('0') << tVal->tm_sec << "." << std::setw(3) << std::setfill('0') << (unixMs%1000) << "Z" << std::endl;
}
s << "#EXTINF:" << (((double)duration) / 1000) << ",\r\nchunk_" << start << ".m4s" << std::endl; s << "#EXTINF:" << (((double)duration) / 1000) << ",\r\nchunk_" << start << ".m4s" << std::endl;
} }
@ -537,7 +547,6 @@ namespace Mist{
result << "#EXTM3U\r\n" result << "#EXTM3U\r\n"
"#EXT-X-VERSION:7\r\n" "#EXT-X-VERSION:7\r\n"
"#EXT-X-DISCONTINUITY\r\n"
"#EXT-X-TARGETDURATION:" "#EXT-X-TARGETDURATION:"
<< targetDuration << "\r\n"; << targetDuration << "\r\n";
if (M.getLive()){result << "#EXT-X-MEDIA-SEQUENCE:" << firstFragment << "\r\n";} if (M.getLive()){result << "#EXT-X-MEDIA-SEQUENCE:" << firstFragment << "\r\n";}

View file

@ -63,7 +63,7 @@ namespace Mist{
config = cfg; config = cfg;
} }
std::string OutDTSC::getStatsName(){return (pushing ? "INPUT" : "OUTPUT");} std::string OutDTSC::getStatsName(){return (pushing ? "INPUT:DTSC" : "OUTPUT:DTSC");}
/// Seeks to the first sync'ed keyframe of the main track. /// Seeks to the first sync'ed keyframe of the main track.
/// Aborts if there is no main track or it has no keyframes. /// Aborts if there is no main track or it has no keyframes.

View file

@ -6,6 +6,8 @@
namespace Mist{ namespace Mist{
bool OutHLS::isReadyForPlay(){ bool OutHLS::isReadyForPlay(){
if (!isInitialized){initialize();}
meta.refresh();
if (!M.getValidTracks().size()){return false;} if (!M.getValidTracks().size()){return false;}
uint32_t mainTrack = M.mainTrack(); uint32_t mainTrack = M.mainTrack();
if (mainTrack == INVALID_TRACK_ID){return false;} if (mainTrack == INVALID_TRACK_ID){return false;}
@ -244,7 +246,11 @@ namespace Mist{
bool isTS = (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"); bool isTS = (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u");
H.Clean(); H.Clean();
H.setCORSHeaders(); H.setCORSHeaders();
H.SetHeader("Content-Type", "application/octet-stream"); if (isTS){
H.SetHeader("Content-Type", "video/mp2t");
}else{
H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
}
if (isTS && !hasSessionIDs()){ if (isTS && !hasSessionIDs()){
H.SetHeader("Cache-Control", "public, max-age=600, immutable"); H.SetHeader("Cache-Control", "public, max-age=600, immutable");
H.SetHeader("Pragma", ""); H.SetHeader("Pragma", "");
@ -343,6 +349,7 @@ namespace Mist{
std::string request = H.url.substr(H.url.find("/", 5) + 1); std::string request = H.url.substr(H.url.find("/", 5) + 1);
H.Clean(); H.Clean();
H.setCORSHeaders(); H.setCORSHeaders();
H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
if (!M.getValidTracks().size()){ if (!M.getValidTracks().size()){
H.SendResponse("404", "Not online or found", myConn); H.SendResponse("404", "Not online or found", myConn);
H.Clean(); H.Clean();
@ -378,16 +385,14 @@ namespace Mist{
wantRequest = true; wantRequest = true;
parseData = false; parseData = false;
// Ensure alignment of contCounters for selected tracks, to prevent discontinuities. // Ensure alignment of contCounters, to prevent discontinuities.
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, uint16_t>::iterator it = contCounters.begin(); it != contCounters.end(); it++){
uint32_t pkgPid = 255 + it->first; if (it->second % 16 != 0){
uint16_t &contPkg = contCounters[pkgPid];
if (contPkg % 16 != 0){
packData.clear(); packData.clear();
packData.setPID(pkgPid); packData.setPID(it->first);
packData.addStuffing(); packData.addStuffing();
while (contPkg % 16 != 0){ while (it->second % 16 != 0){
packData.setContinuityCounter(++contPkg); packData.setContinuityCounter(++it->second);
sendTS(packData.checkAndGetBuffer()); sendTS(packData.checkAndGetBuffer());
} }
packData.clear(); packData.clear();

View file

@ -111,6 +111,7 @@ namespace Mist{
capa["provides"] = "HTTP"; capa["provides"] = "HTTP";
capa["protocol"] = "http://"; capa["protocol"] = "http://";
capa["url_rel"] = "/$.html"; capa["url_rel"] = "/$.html";
capa["codecs"][0u][0u].append("+*");
capa["url_match"].append("/crossdomain.xml"); capa["url_match"].append("/crossdomain.xml");
capa["url_match"].append("/clientaccesspolicy.xml"); capa["url_match"].append("/clientaccesspolicy.xml");
capa["url_match"].append("/$.html"); capa["url_match"].append("/$.html");
@ -1028,7 +1029,7 @@ namespace Mist{
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_STATE, streamName.c_str()); snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_STATE, streamName.c_str());
IPC::sharedPage streamStatus(pageName, 1, false, false); IPC::sharedPage streamStatus(pageName, 1, false, false);
uint8_t prevState, newState, pingCounter = 0; uint8_t prevState, newState, pingCounter = 0;
uint64_t prevTracks; std::set<size_t> prevTracks;
prevState = newState = STRMSTAT_INVALID; prevState = newState = STRMSTAT_INVALID;
while (keepGoing()){ while (keepGoing()){
if (!streamStatus || !streamStatus.exists()){streamStatus.init(pageName, 1, false, false);} if (!streamStatus || !streamStatus.exists()){streamStatus.init(pageName, 1, false, false);}
@ -1038,10 +1039,10 @@ namespace Mist{
newState = streamStatus.mapped[0]; newState = streamStatus.mapped[0];
} }
if (newState != prevState || (newState == STRMSTAT_READY && M.getValidTracks().size() != prevTracks)){ if (newState != prevState || (newState == STRMSTAT_READY && M.getValidTracks() != prevTracks)){
if (newState == STRMSTAT_READY){ if (newState == STRMSTAT_READY){
reconnect(); reconnect();
prevTracks = M.getValidTracks().size(); prevTracks = M.getValidTracks();
}else{ }else{
disconnect(); disconnect();
} }

View file

@ -92,6 +92,7 @@ namespace Mist{
char error_buf[200]; char error_buf[200];
mbedtls_strerror(ret, error_buf, 200); mbedtls_strerror(ret, error_buf, 200);
MEDIUM_MSG("Could not handshake, SSL error: %s (%d)", error_buf, ret); MEDIUM_MSG("Could not handshake, SSL error: %s (%d)", error_buf, ret);
Util::logExitReason("Could not handshake, SSL error: %s (%d)", error_buf, ret);
C.close(); C.close();
return; return;
}else{ }else{
@ -110,6 +111,7 @@ namespace Mist{
int fd[2]; int fd[2];
if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) != 0){ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) != 0){
FAIL_MSG("Could not open anonymous socket for SSL<->HTTP connection!"); FAIL_MSG("Could not open anonymous socket for SSL<->HTTP connection!");
Util::logExitReason("Could not open anonymous socket for SSL<->HTTP connection!");
return 1; return 1;
} }
std::deque<std::string> args; std::deque<std::string> args;
@ -135,6 +137,7 @@ namespace Mist{
close(fd[1]); close(fd[1]);
if (http_proc < 2){ if (http_proc < 2){
FAIL_MSG("Could not spawn MistOutHTTP process for SSL connection!"); FAIL_MSG("Could not spawn MistOutHTTP process for SSL connection!");
Util::logExitReason("Could not spawn MistOutHTTP process for SSL connection!");
return 1; return 1;
} }
Socket::Connection http(fd[0]); Socket::Connection http(fd[0]);
@ -150,6 +153,7 @@ namespace Mist{
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){ if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){
if (ret <= 0){ if (ret <= 0){
HIGH_MSG("SSL disconnect!"); HIGH_MSG("SSL disconnect!");
Util::logExitReason("SSL client disconnected");
break; break;
} }
// we received ret bytes of data to pass on. Do so. // we received ret bytes of data to pass on. Do so.
@ -168,6 +172,7 @@ namespace Mist{
ret = mbedtls_ssl_write(&ssl, (const unsigned char *)http_buf.get().data() + done, toSend - done); ret = mbedtls_ssl_write(&ssl, (const unsigned char *)http_buf.get().data() + done, toSend - done);
if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT){ if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT){
HIGH_MSG("SSL disconnect!"); HIGH_MSG("SSL disconnect!");
Util::logExitReason("SSL client disconnected");
http.close(); http.close();
break; break;
} }

View file

@ -1229,7 +1229,7 @@ namespace Mist{
static std::map<size_t, AMF::Object> pushMeta; static std::map<size_t, AMF::Object> pushMeta;
static std::map<size_t, uint64_t> lastTagTime; static std::map<size_t, uint64_t> lastTagTime;
static std::map<size_t, size_t> reTrackToID; static std::map<size_t, size_t> reTrackToID;
if (!isInitialized){ if (!isInitialized || !meta){
MEDIUM_MSG("Received useless media data"); MEDIUM_MSG("Received useless media data");
onFinish(); onFinish();
break; break;

View file

@ -91,6 +91,8 @@ namespace Mist{
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][0u].append("MPEG2"); capa["codecs"][0u][0u].append("MPEG2");
capa["codecs"][0u][0u].append("VP8");
capa["codecs"][0u][0u].append("VP9");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3"); capa["codecs"][0u][1u].append("AC3");

View file

@ -189,7 +189,7 @@ namespace Mist{
std::string OutTS::getStatsName(){ std::string OutTS::getStatsName(){
if (!parseData){ if (!parseData){
return "INPUT"; return "INPUT:" + capa["name"].asStringRef();
}else{ }else{
return Output::getStatsName(); return Output::getStatsName();
} }

View file

@ -65,17 +65,12 @@ namespace Mist{
std::string type = M.getType(thisIdx); std::string type = M.getType(thisIdx);
std::string codec = M.getCodec(thisIdx); std::string codec = M.getCodec(thisIdx);
bool video = (type == "video"); bool video = (type == "video");
size_t pkgPid = TS::getUniqTrackID(M, thisIdx);
size_t pkgPid = M.getID(thisIdx);
if (pkgPid < 255){pkgPid += 255;}
bool &firstPack = first[thisIdx]; bool &firstPack = first[thisIdx];
uint16_t &contPkg = contCounters[pkgPid]; uint16_t &contPkg = contCounters[pkgPid];
uint64_t packTime = thisPacket.getTime(); uint64_t packTime = thisPacket.getTime();
bool keyframe = thisPacket.getInt("keyframe"); bool keyframe = thisPacket.getInt("keyframe");
firstPack = true; firstPack = true;
char *dataPointer = 0; char *dataPointer = 0;
size_t dataLen = 0; size_t dataLen = 0;
thisPacket.getString("data", dataPointer, dataLen); // data thisPacket.getString("data", dataPointer, dataLen); // data

View file

@ -3,6 +3,7 @@
#include <mist/procs.h> #include <mist/procs.h>
#include <mist/sdp.h> #include <mist/sdp.h>
#include <mist/timing.h> #include <mist/timing.h>
#include <mist/url.h>
#include <netdb.h> // ifaddr, listing ip addresses. #include <netdb.h> // ifaddr, listing ip addresses.
namespace Mist{ namespace Mist{
@ -31,10 +32,12 @@ namespace Mist{
/* ------------------------------------------------ */ /* ------------------------------------------------ */
OutWebRTC::OutWebRTC(Socket::Connection &myConn) : HTTPOutput(myConn){ OutWebRTC::OutWebRTC(Socket::Connection &myConn) : HTTPOutput(myConn){
lastPackMs = 0;
vidTrack = INVALID_TRACK_ID; vidTrack = INVALID_TRACK_ID;
prevVidTrack = INVALID_TRACK_ID; prevVidTrack = INVALID_TRACK_ID;
audTrack = INVALID_TRACK_ID; audTrack = INVALID_TRACK_ID;
stayLive = true; stayLive = true;
target_rate = 0.0;
firstKey = true; firstKey = true;
repeatInit = true; repeatInit = true;
@ -95,6 +98,7 @@ namespace Mist{
capa["url_match"] = "/webrtc/$"; capa["url_match"] = "/webrtc/$";
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("VP8"); capa["codecs"][0u][0u].append("VP8");
capa["codecs"][0u][0u].append("VP9");
capa["codecs"][0u][1u].append("opus"); capa["codecs"][0u][1u].append("opus");
capa["codecs"][0u][1u].append("ALAW"); capa["codecs"][0u][1u].append("ALAW");
capa["codecs"][0u][1u].append("ULAW"); capa["codecs"][0u][1u].append("ULAW");
@ -107,7 +111,7 @@ namespace Mist{
capa["optional"]["preferredvideocodec"]["help"] = capa["optional"]["preferredvideocodec"]["help"] =
"Comma separated list of video codecs you want to support in preferred order. e.g. " "Comma separated list of video codecs you want to support in preferred order. e.g. "
"H264,VP8"; "H264,VP8";
capa["optional"]["preferredvideocodec"]["default"] = "H264,VP8"; capa["optional"]["preferredvideocodec"]["default"] = "H264,VP9,VP8";
capa["optional"]["preferredvideocodec"]["type"] = "string"; capa["optional"]["preferredvideocodec"]["type"] = "string";
capa["optional"]["preferredvideocodec"]["option"] = "--webrtc-video-codecs"; capa["optional"]["preferredvideocodec"]["option"] = "--webrtc-video-codecs";
capa["optional"]["preferredvideocodec"]["short"] = "V"; capa["optional"]["preferredvideocodec"]["short"] = "V";
@ -129,14 +133,26 @@ namespace Mist{
capa["optional"]["bindhost"]["option"] = "--bindhost"; capa["optional"]["bindhost"]["option"] = "--bindhost";
capa["optional"]["bindhost"]["short"] = "B"; capa["optional"]["bindhost"]["short"] = "B";
capa["optional"]["mergesessions"]["name"] = "Merge sessions"; capa["optional"]["mergesessions"]["name"] = "merge sessions";
capa["optional"]["mergesessions"]["help"] = capa["optional"]["mergesessions"]["help"] =
"If enabled, merges together all views from a single user into a single combined session. " "if enabled, merges together all views from a single user into a single combined session. "
"If disabled, each view (reconnection of the signalling websocket) is a separate session."; "if disabled, each view (reconnection of the signalling websocket) is a separate session.";
capa["optional"]["mergesessions"]["option"] = "--mergesessions"; capa["optional"]["mergesessions"]["option"] = "--mergesessions";
capa["optional"]["mergesessions"]["short"] = "m"; capa["optional"]["mergesessions"]["short"] = "m";
capa["optional"]["mergesessions"]["default"] = 0; capa["optional"]["mergesessions"]["default"] = 0;
capa["optional"]["nackdisable"]["name"] = "Disallow NACKs for viewers";
capa["optional"]["nackdisable"]["help"] = "Disallows viewers to send NACKs for lost packets";
capa["optional"]["nackdisable"]["option"] = "--nackdisable";
capa["optional"]["nackdisable"]["short"] = "n";
capa["optional"]["nackdisable"]["default"] = 0;
capa["optional"]["jitterlog"]["name"] = "Write jitter log";
capa["optional"]["jitterlog"]["help"] = "Writes log of frame transmit jitter to /tmp/ for each outgoing connection";
capa["optional"]["jitterlog"]["option"] = "--jitterlog";
capa["optional"]["jitterlog"]["short"] = "J";
capa["optional"]["jitterlog"]["default"] = 0;
config->addOptionsFromCapabilities(capa); config->addOptionsFromCapabilities(capa);
} }
@ -280,13 +296,61 @@ namespace Mist{
parseData = true; parseData = true;
selectDefaultTracks(); selectDefaultTracks();
} }
stayLive = (endTime() < seek_time + 5000); stayLive = (target_rate == 0.0) && (endTime() < seek_time + 5000);
if (command["seek_time"].asStringRef() == "live"){stayLive = true;}
if (stayLive){seek_time = endTime();} if (stayLive){seek_time = endTime();}
seek(seek_time, true); seek(seek_time, true);
JSON::Value commandResult; JSON::Value commandResult;
commandResult["type"] = "on_seek"; commandResult["type"] = "on_seek";
commandResult["result"] = true; commandResult["result"] = true;
if (M.getLive()){commandResult["live_point"] = stayLive;} if (M.getLive()){commandResult["live_point"] = stayLive;}
if (target_rate == 0.0){
commandResult["play_rate_curr"] = "auto";
}else{
commandResult["play_rate_curr"] = target_rate;
}
webSock->sendFrame(commandResult.toString());
onIdle();
return;
}
if (command["type"] == "set_speed"){
if (!command.isMember("play_rate")){
sendSignalingError("on_speed", "Received a playback speed setting request but no `play_rate` property.");
return;
}
double set_rate = command["play_rate"].asDouble();
if (!parseData){
parseData = true;
selectDefaultTracks();
}
JSON::Value commandResult;
commandResult["type"] = "on_speed";
if (target_rate == 0.0){
commandResult["play_rate_prev"] = "auto";
}else{
commandResult["play_rate_prev"] = target_rate;
}
if (set_rate == 0.0){
commandResult["play_rate_curr"] = "auto";
}else{
commandResult["play_rate_curr"] = set_rate;
}
if (target_rate != set_rate){
target_rate = set_rate;
if (target_rate == 0.0){
realTime = 1000;//set playback speed to default
firstTime = Util::bootMS() - currentTime();
maxSkipAhead = 0;//enabled automatic rate control
}else{
stayLive = false;
//Set new realTime speed
realTime = 1000 / target_rate;
firstTime = Util::bootMS() - (currentTime() / target_rate);
maxSkipAhead = 1;//disable automatic rate control
}
}
if (M.getLive()){commandResult["live_point"] = stayLive;}
webSock->sendFrame(commandResult.toString()); webSock->sendFrame(commandResult.toString());
onIdle(); onIdle();
return; return;
@ -337,6 +401,15 @@ namespace Mist{
sendSignalingError(command["type"].asString(), "Unhandled command type: " + command["type"].asString()); sendSignalingError(command["type"].asString(), "Unhandled command type: " + command["type"].asString());
} }
bool OutWebRTC::dropPushTrack(uint32_t trackId, const std::string & dropReason){
JSON::Value commandResult;
commandResult["type"] = "on_track_drop";
commandResult["track"] = trackId;
commandResult["mediatype"] = M.getType(trackId);
webSock->sendFrame(commandResult.toString());
return Output::dropPushTrack(trackId, dropReason);
}
void OutWebRTC::sendSignalingError(const std::string &commandType, const std::string &errorMessage){ void OutWebRTC::sendSignalingError(const std::string &commandType, const std::string &errorMessage){
JSON::Value commandResult; JSON::Value commandResult;
commandResult["type"] = "on_error"; commandResult["type"] = "on_error";
@ -357,6 +430,12 @@ namespace Mist{
initialize(); initialize();
selectDefaultTracks(); selectDefaultTracks();
if (config && config->hasOption("jitterlog") && config->getBool("jitterlog")){
std::string fileName = "/tmp/jitter_"+JSON::Value(getpid()).asString();
jitterLog.open(fileName.c_str());
lastPackMs = Util::bootMS();
}
if (0 == udpPort){bindUDPSocketOnLocalCandidateAddress(0);} if (0 == udpPort){bindUDPSocketOnLocalCandidateAddress(0);}
std::string videoCodec; std::string videoCodec;
@ -389,8 +468,10 @@ namespace Mist{
return false; return false;
} }
videoTrack.rtpPacketizer = RTP::Packet(videoTrack.payloadType, rand(), 0, videoTrack.SSRC, 0); videoTrack.rtpPacketizer = RTP::Packet(videoTrack.payloadType, rand(), 0, videoTrack.SSRC, 0);
// Enabled NACKs if (!config || !config->hasOption("nackdisable") || !config->getBool("nackdisable")){
// Enable NACKs
sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK; sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK;
}
videoTrack.sorter.tmpVideoLossPrevention = sdpAnswer.videoLossPrevention; videoTrack.sorter.tmpVideoLossPrevention = sdpAnswer.videoLossPrevention;
} }
} }
@ -500,7 +581,7 @@ namespace Mist{
capa["codecs"].null(); capa["codecs"].null();
const char *videoCodecPreference[] ={"H264", "VP8", NULL}; const char *videoCodecPreference[] ={"H264", "VP9", "VP8", NULL};
const char **videoCodec = videoCodecPreference; const char **videoCodec = videoCodecPreference;
SDP::Media *videoMediaOffer = sdpSession.getMediaForType("video"); SDP::Media *videoMediaOffer = sdpSession.getMediaForType("video");
if (videoMediaOffer){ if (videoMediaOffer){
@ -535,12 +616,12 @@ namespace Mist{
if (0 == udpPort){bindUDPSocketOnLocalCandidateAddress(0);} if (0 == udpPort){bindUDPSocketOnLocalCandidateAddress(0);}
std::string prefVideoCodec = "VP8,H264"; std::string prefVideoCodec = "VP9,VP8,H264";
if (config && config->hasOption("preferredvideocodec")){ if (config && config->hasOption("preferredvideocodec")){
prefVideoCodec = config->getString("preferredvideocodec"); prefVideoCodec = config->getString("preferredvideocodec");
if (prefVideoCodec.empty()){ if (prefVideoCodec.empty()){
WARN_MSG("No preferred video codec value set; resetting to default."); WARN_MSG("No preferred video codec value set; resetting to default.");
prefVideoCodec = "VP8,H264"; prefVideoCodec = "VP9,VP8,H264";
} }
} }
@ -579,10 +660,11 @@ namespace Mist{
SDP::MediaFormat *fmtRED = sdpSession.getMediaFormatByEncodingName("video", "RED"); SDP::MediaFormat *fmtRED = sdpSession.getMediaFormatByEncodingName("video", "RED");
SDP::MediaFormat *fmtULPFEC = sdpSession.getMediaFormatByEncodingName("video", "ULPFEC"); SDP::MediaFormat *fmtULPFEC = sdpSession.getMediaFormatByEncodingName("video", "ULPFEC");
if (fmtRED && fmtULPFEC){ if (fmtRED || fmtULPFEC){
videoTrack.ULPFECPayloadType = fmtULPFEC->payloadType; videoTrack.ULPFECPayloadType = fmtULPFEC->payloadType;
videoTrack.REDPayloadType = fmtRED->payloadType; videoTrack.REDPayloadType = fmtRED->payloadType;
payloadTypeToWebRTCTrack[fmtRED->payloadType] = videoTrack.payloadType; payloadTypeToWebRTCTrack[fmtRED->payloadType] = videoTrack.payloadType;
payloadTypeToWebRTCTrack[fmtULPFEC->payloadType] = videoTrack.payloadType;
} }
sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK; sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK;
videoTrack.sorter.tmpVideoLossPrevention = sdpAnswer.videoLossPrevention; videoTrack.sorter.tmpVideoLossPrevention = sdpAnswer.videoLossPrevention;
@ -594,7 +676,7 @@ namespace Mist{
videoTrack.rtpToDTSC.setProperties(meta, vIdx); videoTrack.rtpToDTSC.setProperties(meta, vIdx);
videoTrack.rtpToDTSC.setCallbacks(onDTSCConverterHasPacketCallback, onDTSCConverterHasInitDataCallback); videoTrack.rtpToDTSC.setCallbacks(onDTSCConverterHasPacketCallback, onDTSCConverterHasInitDataCallback);
videoTrack.sorter.setCallback(vIdx, onRTPSorterHasPacketCallback); videoTrack.sorter.setCallback(M.getID(vIdx), onRTPSorterHasPacketCallback);
userSelect[vIdx].reload(streamName, vIdx, COMM_STATUS_SOURCE); userSelect[vIdx].reload(streamName, vIdx, COMM_STATUS_SOURCE);
INFO_MSG("Video push received on track %zu", vIdx); INFO_MSG("Video push received on track %zu", vIdx);
@ -616,7 +698,7 @@ namespace Mist{
audioTrack.rtpToDTSC.setProperties(meta, aIdx); audioTrack.rtpToDTSC.setProperties(meta, aIdx);
audioTrack.rtpToDTSC.setCallbacks(onDTSCConverterHasPacketCallback, onDTSCConverterHasInitDataCallback); audioTrack.rtpToDTSC.setCallbacks(onDTSCConverterHasPacketCallback, onDTSCConverterHasInitDataCallback);
audioTrack.sorter.setCallback(aIdx, onRTPSorterHasPacketCallback); audioTrack.sorter.setCallback(M.getID(aIdx), onRTPSorterHasPacketCallback);
userSelect[aIdx].reload(streamName, aIdx, COMM_STATUS_SOURCE); userSelect[aIdx].reload(streamName, aIdx, COMM_STATUS_SOURCE);
INFO_MSG("Audio push received on track %zu", aIdx); INFO_MSG("Audio push received on track %zu", aIdx);
@ -639,13 +721,47 @@ namespace Mist{
return false; return false;
} }
udpPort = std::string bindAddr;
udp.bind(port, (config && config->hasOption("bindhost") && config->getString("bindhost").size()) //If a bind host has been put in as override, use it
? config->getString("bindhost") if (config && config->hasOption("bindhost") && config->getString("bindhost").size()){
: myConn.getBoundAddress()); bindAddr = config->getString("bindhost");
Util::Procs::socketList.insert(udp.getSock()); udpPort = udp.bind(port, bindAddr);
sdpAnswer.setCandidate(externalAddr, udpPort); if (!udpPort){
WARN_MSG("UDP bind address not valid - ignoring setting and using best guess instead");
bindAddr.clear();
}else{
INFO_MSG("Bound to pre-configured UDP bind address");
}
}
//use the best IPv4 guess we have
if (!bindAddr.size()){
bindAddr = Socket::resolveHostToBestExternalAddrGuess(externalAddr, AF_INET, myConn.getBoundAddress());
if (!bindAddr.size()){
WARN_MSG("UDP bind to best guess failed - using same address as incoming connection as a last resort");
bindAddr.clear();
}else{
udpPort = udp.bind(port, bindAddr);
if (!udpPort){
WARN_MSG("UDP bind to best guess failed - using same address as incoming connection as a last resort");
bindAddr.clear();
}else{
INFO_MSG("Bound to public UDP bind address derived from hostname");
}
}
}
if (!bindAddr.size()){
bindAddr = myConn.getBoundAddress();
udpPort = udp.bind(port, bindAddr);
if (!udpPort){
FAIL_MSG("UDP bind to connected address failed - we're out of options here, I'm afraid...");
bindAddr.clear();
}else{
INFO_MSG("Bound to same UDP address as TCP address - this is potentially wrong, but used as a last resort");
}
}
Util::Procs::socketList.insert(udp.getSock());
sdpAnswer.setCandidate(bindAddr, udpPort);
return true; return true;
} }
@ -813,8 +929,8 @@ namespace Mist{
if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){ if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){
FAIL_MSG("Received an RTP packet for a track that we didn't prepare for. PayloadType is " FAIL_MSG("Received an RTP packet for a track that we didn't prepare for. PayloadType is "
"%" PRIu32, "%" PRIu32 ", idx %zu",
rtp_pkt.getPayloadType()); rtp_pkt.getPayloadType(), idx);
return; return;
} }
@ -830,6 +946,8 @@ namespace Mist{
FAIL_MSG("Failed to unprotect a RTP packet."); FAIL_MSG("Failed to unprotect a RTP packet.");
return; return;
} }
RTP::Packet unprotPack(udp.data, len);
DONTEVEN_MSG("%s", unprotPack.toString().c_str());
// Here follows a very rudimentary algo for requesting lost // Here follows a very rudimentary algo for requesting lost
// packets; I guess after some experimentation a better // packets; I guess after some experimentation a better
@ -843,11 +961,11 @@ namespace Mist{
rtcTrack.prevReceivedSequenceNumber = currSeqNum; rtcTrack.prevReceivedSequenceNumber = currSeqNum;
if (rtp_pkt.getPayloadType() == rtcTrack.REDPayloadType){ if (rtp_pkt.getPayloadType() == rtcTrack.REDPayloadType || rtp_pkt.getPayloadType() == rtcTrack.ULPFECPayloadType){
rtcTrack.sorter.addREDPacket(udp.data, len, rtcTrack.payloadType, rtcTrack.REDPayloadType, rtcTrack.sorter.addREDPacket(udp.data, len, rtcTrack.payloadType, rtcTrack.REDPayloadType,
rtcTrack.ULPFECPayloadType); rtcTrack.ULPFECPayloadType);
}else{ }else{
rtcTrack.sorter.addPacket(RTP::Packet(udp.data, len)); rtcTrack.sorter.addPacket(unprotPack);
} }
}else if ((pt >= 64) && (pt < 96)){ }else if ((pt >= 64) && (pt < 96)){
@ -909,7 +1027,7 @@ namespace Mist{
void OutWebRTC::onDTSCConverterHasPacket(const DTSC::Packet &pkt){ void OutWebRTC::onDTSCConverterHasPacket(const DTSC::Packet &pkt){
// extract meta data (init data, width/height, etc); // extract meta data (init data, width/height, etc);
size_t idx = pkt.getTrackId(); size_t idx = M.trackIDToIndex(pkt.getTrackId(), getpid());
std::string codec = M.getCodec(idx); std::string codec = M.getCodec(idx);
if (codec == "H264"){ if (codec == "H264"){
if (M.getInit(idx).empty()){ if (M.getInit(idx).empty()){
@ -919,9 +1037,10 @@ namespace Mist{
} }
if (codec == "VP8" && pkt.getFlag("keyframe")){extractFrameSizeFromVP8KeyFrame(pkt);} if (codec == "VP8" && pkt.getFlag("keyframe")){extractFrameSizeFromVP8KeyFrame(pkt);}
if (codec == "VP9" && pkt.getFlag("keyframe")){extractFrameSizeFromVP8KeyFrame(pkt);}
// create rtcp packet (set bitrate and request keyframe). // create rtcp packet (set bitrate and request keyframe).
if (codec == "H264" || codec == "VP8"){ if (codec == "H264" || codec == "VP8" || codec == "VP9"){
uint64_t now = Util::bootMS(); uint64_t now = Util::bootMS();
if (now >= rtcpTimeoutInMillis){ if (now >= rtcpTimeoutInMillis){
@ -942,13 +1061,15 @@ namespace Mist{
INFO_MSG("Validated track %zu in meta", idx); INFO_MSG("Validated track %zu in meta", idx);
meta.validateTrack(idx); meta.validateTrack(idx);
} }
DONTEVEN_MSG("DTSC: %s", pkt.toSummary().c_str());
bufferLivePacket(pkt); bufferLivePacket(pkt);
} }
void OutWebRTC::onDTSCConverterHasInitData(size_t idx, const std::string &initData){ void OutWebRTC::onDTSCConverterHasInitData(size_t trackId, const std::string &initData){
size_t idx = M.trackIDToIndex(trackId, getpid());
if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){ if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){
ERROR_MSG( ERROR_MSG(
"Recieved init data for a track that we don't manager. TrackID %zu /PayloadType: %zu", "Recieved init data for a track that we don't manage. TrackID %zu /PayloadType: %zu",
idx, M.getID(idx)); idx, M.getID(idx));
return; return;
} }
@ -972,9 +1093,10 @@ namespace Mist{
meta.setInit(idx, avccbox.payload(), avccbox.payloadSize()); meta.setInit(idx, avccbox.payload(), avccbox.payloadSize());
} }
void OutWebRTC::onRTPSorterHasPacket(size_t idx, const RTP::Packet &pkt){ void OutWebRTC::onRTPSorterHasPacket(size_t trackId, const RTP::Packet &pkt){
size_t idx = M.trackIDToIndex(trackId, getpid());
if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){ if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){
ERROR_MSG("Received a sorted RTP packet for track %zu but we don't manage this track.", idx); ERROR_MSG("Received a sorted RTP packet for payload %zu (idx %zu) but we don't manage this track.", trackId, idx);
return; return;
} }
webrtcTracks[idx].rtpToDTSC.addRTP(pkt); webrtcTracks[idx].rtpToDTSC.addRTP(pkt);
@ -989,7 +1111,7 @@ namespace Mist{
int protectedSize = nbytes; int protectedSize = nbytes;
if (srtpWriter.protectRtp((uint8_t *)(void *)rtpOutBuffer, &protectedSize) != 0){ if (srtpWriter.protectRtp((uint8_t *)(void *)rtpOutBuffer, &protectedSize) != 0){
ERROR_MSG("Failed to protect the RTCP message."); ERROR_MSG("Failed to protect the RTP message.");
return; return;
} }
@ -1076,6 +1198,14 @@ namespace Mist{
// If we see this is audio or video, use the webrtc track we negotiated // If we see this is audio or video, use the webrtc track we negotiated
if (M.getType(tid) == "video" && webrtcTracks.count(vidTrack)){ if (M.getType(tid) == "video" && webrtcTracks.count(vidTrack)){
trackPointer = &webrtcTracks[vidTrack]; trackPointer = &webrtcTracks[vidTrack];
if (lastPackMs){
uint64_t newMs = Util::bootMS();
jitterLog << (newMs - lastPackMs) << std::endl;
lastPackMs = newMs;
}
} }
if (M.getType(tid) == "audio" && webrtcTracks.count(audTrack)){ if (M.getType(tid) == "audio" && webrtcTracks.count(audTrack)){
trackPointer = &webrtcTracks[audTrack]; trackPointer = &webrtcTracks[audTrack];
@ -1093,19 +1223,17 @@ namespace Mist{
WebRTCTrack &rtcTrack = *trackPointer; WebRTCTrack &rtcTrack = *trackPointer;
uint64_t timestamp = thisPacket.getTime(); uint64_t timestamp = thisPacket.getTime();
rtcTrack.rtpPacketizer.setTimestamp(timestamp * SDP::getMultiplier(&M, thisIdx)); uint64_t newTime = timestamp * SDP::getMultiplier(&M, thisIdx);
rtcTrack.rtpPacketizer.setTimestamp(newTime);
bool isKeyFrame = thisPacket.getFlag("keyframe"); bool isKeyFrame = thisPacket.getFlag("keyframe");
didReceiveKeyFrame = isKeyFrame; didReceiveKeyFrame = isKeyFrame;
if (M.getCodec(thisIdx) == "H264"){ if (M.getCodec(thisIdx) == "H264"){
if (isKeyFrame && firstKey){ if (isKeyFrame && firstKey){
char *data;
size_t dataLen;
thisPacket.getString("data", data, dataLen);
size_t offset = 0; size_t offset = 0;
while (offset + 4 < dataLen){ while (offset + 4 < dataLen){
size_t nalLen = Bit::btohl(data + offset); size_t nalLen = Bit::btohl(dataPointer + offset);
uint8_t nalType = data[offset + 4] & 0x1F; uint8_t nalType = dataPointer[offset + 4] & 0x1F;
if (nalType == 7 || nalType == 8){// Init data already provided in-band, skip repeating if (nalType == 7 || nalType == 8){// Init data already provided in-band, skip repeating
// it. // it.
repeatInit = false; repeatInit = false;

View file

@ -67,6 +67,7 @@
#include <mist/stun.h> #include <mist/stun.h>
#include <mist/tinythread.h> #include <mist/tinythread.h>
#include <mist/websocket.h> #include <mist/websocket.h>
#include <fstream>
#define NACK_BUFFER_SIZE 1024 #define NACK_BUFFER_SIZE 1024
@ -130,6 +131,7 @@ namespace Mist{
virtual void sendNext(); virtual void sendNext();
virtual void onWebsocketFrame(); virtual void onWebsocketFrame();
virtual void preWebsocketConnect(); virtual void preWebsocketConnect();
virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason);
void onIdle(); void onIdle();
bool onFinish(); bool onFinish();
bool doesWebsockets(){return true;} bool doesWebsockets(){return true;}
@ -142,6 +144,8 @@ namespace Mist{
void onRTPPacketizerHasRTCPPacket(const char *data, uint32_t nbytes); void onRTPPacketizerHasRTCPPacket(const char *data, uint32_t nbytes);
private: private:
uint64_t lastPackMs;
std::ofstream jitterLog;
std::string externalAddr; std::string externalAddr;
void ackNACK(uint32_t SSRC, uint16_t seq); void ackNACK(uint32_t SSRC, uint16_t seq);
bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read
@ -198,6 +202,7 @@ namespace Mist{
///< the signaling channel. Defaults to 6mbit. ///< the signaling channel. Defaults to 6mbit.
size_t audTrack, vidTrack, prevVidTrack; size_t audTrack, vidTrack, prevVidTrack;
double target_rate; ///< Target playback speed rate (1.0 = normal, 0 = auto)
bool didReceiveKeyFrame; /* TODO burst delay */ bool didReceiveKeyFrame; /* TODO burst delay */
int64_t packetOffset; ///< For timestamp rewrite with BMO int64_t packetOffset; ///< For timestamp rewrite with BMO

View file

@ -9,7 +9,7 @@
#include <sys/types.h> //for stat #include <sys/types.h> //for stat
#include <unistd.h> //for stat #include <unistd.h> //for stat
int pipein[2], pipeout[2], pipeerr[2]; int pipein[2], pipeout[2];
Util::Config co; Util::Config co;
Util::Config conf; Util::Config conf;
@ -134,8 +134,14 @@ int main(int argc, char *argv[]){
} }
// create pipe pair before thread // create pipe pair before thread
pipe(pipein); if (pipe(pipein) || pipe(pipeout)){
pipe(pipeout); FAIL_MSG("Could not create pipes for process!");
return 1;
}
Util::Procs::socketList.insert(pipeout[0]);
Util::Procs::socketList.insert(pipeout[1]);
Util::Procs::socketList.insert(pipein[0]);
Util::Procs::socketList.insert(pipein[1]);
// stream which connects to input // stream which connects to input
tthread::thread source(sourceThread, 0); tthread::thread source(sourceThread, 0);
@ -188,9 +194,18 @@ namespace Mist{
int ffer = 2; int ffer = 2;
pid_t execd_proc = -1; pid_t execd_proc = -1;
std::string streamName = opt["sink"].asString();
if (!streamName.size()){streamName = opt["source"].asStringRef();}
Util::streamVariables(streamName, opt["source"].asStringRef());
//Do variable substitution on command
std::string tmpCmd = opt["exec"].asStringRef();
Util::streamVariables(tmpCmd, streamName, opt["source"].asStringRef());
// exec command // exec command
char exec_cmd[10240]; char exec_cmd[10240];
strncpy(exec_cmd, opt["exec"].asString().c_str(), 10240); strncpy(exec_cmd, tmpCmd.c_str(), 10240);
INFO_MSG("Executing command: %s", exec_cmd); INFO_MSG("Executing command: %s", exec_cmd);
uint8_t argCnt = 0; uint8_t argCnt = 0;
char *startCh = 0; char *startCh = 0;

View file

@ -21,7 +21,9 @@ namespace Mist{
class ProcessSink : public InputEBML{ class ProcessSink : public InputEBML{
public: public:
ProcessSink(Util::Config *cfg) : InputEBML(cfg){}; ProcessSink(Util::Config *cfg) : InputEBML(cfg){
capa["name"] = "MKVExec";
};
void getNext(size_t idx = INVALID_TRACK_ID){ void getNext(size_t idx = INVALID_TRACK_ID){
static bool recurse = false; static bool recurse = false;
if (recurse){return InputEBML::getNext(idx);} if (recurse){return InputEBML::getNext(idx);}
@ -52,7 +54,15 @@ namespace Mist{
class ProcessSource : public OutEBML{ class ProcessSource : public OutEBML{
public: public:
ProcessSource(Socket::Connection &c) : OutEBML(c){realTime = 1000;}; bool isRecording(){return false;}
ProcessSource(Socket::Connection &c) : OutEBML(c){
capa["name"] = "MKVExec";
realTime = 0;
};
void sendHeader(){
realTime = 0;
OutEBML::sendHeader();
};
void sendNext(){ void sendNext(){
extraKeepAway = 0; extraKeepAway = 0;
needsLookAhead = 0; needsLookAhead = 0;

View file

@ -13,7 +13,7 @@
int ofin = -1, ofout = 1, oferr = 2; int ofin = -1, ofout = 1, oferr = 2;
int ifin = -1, ifout = -1, iferr = 2; int ifin = -1, ifout = -1, iferr = 2;
int pipein[2], pipeout[2], pipeerr[2]; int pipein[2], pipeout[2];
Util::Config co; Util::Config co;
Util::Config conf; Util::Config conf;
@ -307,8 +307,12 @@ int main(int argc, char *argv[]){
} }
// create pipe pair before thread // create pipe pair before thread
pipe(pipein); if (pipe(pipein) || pipe(pipeout)){
pipe(pipeout); FAIL_MSG("Could not create pipes for process!");
return 1;
}
Util::Procs::socketList.insert(pipeout[0]);
Util::Procs::socketList.insert(pipein[1]);
// stream which connects to input // stream which connects to input
tthread::thread source(sourceThread, 0); tthread::thread source(sourceThread, 0);
@ -384,14 +388,14 @@ namespace Mist{
std::string EncodeOutputEBML::getTrackType(int tid){return M.getType(tid);} std::string EncodeOutputEBML::getTrackType(int tid){return M.getType(tid);}
void EncodeOutputEBML::setVideoTrack(std::string tid){ void EncodeOutputEBML::setVideoTrack(std::string tid){
std::set<size_t> tracks = Util::findTracks(M, "video", tid); std::set<size_t> tracks = Util::findTracks(M, capa, "video", tid);
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){ for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
userSelect[*it].reload(streamName, *it); userSelect[*it].reload(streamName, *it);
} }
} }
void EncodeOutputEBML::setAudioTrack(std::string tid){ void EncodeOutputEBML::setAudioTrack(std::string tid){
std::set<size_t> tracks = Util::findTracks(M, "audio", tid); std::set<size_t> tracks = Util::findTracks(M, capa, "audio", tid);
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){ for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
userSelect[*it].reload(streamName, *it); userSelect[*it].reload(streamName, *it);
} }
@ -571,7 +575,7 @@ namespace Mist{
if (!preset.empty()){options.append(" -preset " + preset);} if (!preset.empty()){options.append(" -preset " + preset);}
snprintf(ffcmd, 10240, "ffmpeg -hide_banner -loglevel warning -f lavfi -i color=c=black:s=%dx%d %s %s -c:v %s %s %s %s -an -f matroska - ", snprintf(ffcmd, 10240, "ffmpeg -hide_banner -loglevel warning -f lavfi -i color=c=black:s=%dx%d %s %s -c:v %s %s %s %s -an -force_key_frames source -f matroska - ",
res_x, res_y, s_input.c_str(), s_overlay.c_str(), codec.c_str(), options.c_str(), res_x, res_y, s_input.c_str(), s_overlay.c_str(), codec.c_str(), options.c_str(),
getBitrateSetting().c_str(), flags.c_str()); getBitrateSetting().c_str(), flags.c_str());
@ -681,16 +685,26 @@ namespace Mist{
}else{ }else{
// sources array missing, create empty object in array // sources array missing, create empty object in array
opt["sources"][0u]["src"] = "-"; opt["sources"][0u]["src"] = "-";
if (opt.isMember("resolution")){
WARN_MSG("No stdin input set in config, adding input stream with default settings"); opt["sources"][0u]["width"] = -1;
opt["sources"][0u]["height"] = res_y;
opt["sources"][0u]["anchor"] = "center";
}
INFO_MSG("Default source: input stream at preserved-aspect same height");
stdinSource = true; stdinSource = true;
} }
if (!stdinSource){ if (!stdinSource){
// no stdin source item found in sources configuration, add source object at the beginning // no stdin source item found in sources configuration, add source object at the beginning
opt["sources"].prepend(JSON::fromString("{\"src\':\"-\"}")); JSON::Value nOpt;
WARN_MSG("No stdin input stream found in 'inputs' config, adding stdin input stream at the " nOpt["src"] = "-";
"beginning of the array"); if (opt.isMember("resolution")){
nOpt["width"] = -1;
nOpt["height"] = res_y;
nOpt["anchor"] = "center";
}
opt["sources"].prepend(nOpt);
WARN_MSG("Source is not used: adding source stream at preserved-aspect same height");
} }
return true; return true;
@ -799,7 +813,6 @@ namespace Mist{
} }
prepareCommand(); prepareCommand();
MEDIUM_MSG("Starting ffmpeg process...");
ffout = p.StartPiped(args, &pipein[0], &pipeout[1], &ffer); ffout = p.StartPiped(args, &pipein[0], &pipeout[1], &ffer);
while (conf.is_active && p.isRunning(ffout)){Util::sleep(200);} while (conf.is_active && p.isRunning(ffout)){Util::sleep(200);}

View file

@ -52,6 +52,7 @@ namespace Mist{
class EncodeOutputEBML : public OutEBML{ class EncodeOutputEBML : public OutEBML{
public: public:
EncodeOutputEBML(Socket::Connection &c) : OutEBML(c){}; // realTime = 0;}; EncodeOutputEBML(Socket::Connection &c) : OutEBML(c){}; // realTime = 0;};
bool isRecording(){return false;}
void setVideoTrack(std::string tid); void setVideoTrack(std::string tid);
void setAudioTrack(std::string tid); void setAudioTrack(std::string tid);
void sendNext(); void sendNext();

View file

@ -13,6 +13,7 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <sstream>
#include <unistd.h> #include <unistd.h>
std::string getContents(const char *fileName){ std::string getContents(const char *fileName){

View file

@ -664,7 +664,9 @@ void handleServer(void *hostEntryPointer){
entry->state = STATE_ERROR; entry->state = STATE_ERROR;
}else{ }else{
if (down){ if (down){
WARN_MSG("Connection established with %s", url.host.c_str()); std::string ipStr;
Socket::hostBytesToStr(DL.getSocket().getBinHost().data(), 16, ipStr);
WARN_MSG("Connection established with %s (%s)", url.host.c_str(), ipStr.c_str());
memcpy(entry->details->binHost, DL.getSocket().getBinHost().data(), 16); memcpy(entry->details->binHost, DL.getSocket().getBinHost().data(), 16);
entry->state = STATE_ONLINE; entry->state = STATE_ONLINE;
down = false; down = false;

46
test/status.cpp Normal file
View file

@ -0,0 +1,46 @@
#include <iostream>
#include <mist/stream.h>
#include <mist/config.h>
#include <unistd.h>
int main(int argc, char **argv){
Util::Config cfg;
cfg.activate();
uint8_t prevStat = 255;
while (cfg.is_active){
uint8_t currStat = Util::getStreamStatus(argv[1]);
if (currStat != prevStat){
std::cout << "Stream status: ";
switch (currStat){
case STRMSTAT_OFF:
std::cout << "Off";
break;
case STRMSTAT_INIT:
std::cout << "Init";
break;
case STRMSTAT_BOOT:
std::cout << "Boot";
break;
case STRMSTAT_WAIT:
std::cout << "Wait";
break;
case STRMSTAT_READY:
std::cout << "Ready";
break;
case STRMSTAT_SHUTDOWN:
std::cout << "Shutdown";
break;
case STRMSTAT_INVALID:
std::cout << "Invalid";
break;
default:
std::cout << "??? (" << currStat << ")";
}
std::cout << std::endl;
prevStat = currStat;
}
Util::sleep(200);
}
return 0;
}