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:
parent
2b99f2f5ea
commit
0af992d405
75 changed files with 1512 additions and 790 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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;}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
79
lib/dtsc.cpp
79
lib/dtsc.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
150
lib/rtp.cpp
150
lib/rtp.cpp
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
17
lib/sdp.cpp
17
lib/sdp.cpp
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
183
lib/stream.cpp
183
lib/stream.cpp
|
@ -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,66 +750,50 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currVal != INVALID_TRACK_ID){result.insert(currVal);}
|
if (currVal != INVALID_TRACK_ID){result.insert(currVal);}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// less-than or greater-than track matching on bit rate or resolution
|
//less-than or greater-than track matching on bit rate or resolution
|
||||||
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;
|
||||||
}
|
}
|
||||||
unsigned int resX, resY;
|
unsigned int resX, resY;
|
||||||
uint64_t targetArea = 0;
|
uint64_t targetArea = 0;
|
||||||
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);}
|
||||||
}
|
}
|
||||||
|
@ -812,32 +801,23 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// approx bitrate matching
|
//approx bitrate matching
|
||||||
{
|
{
|
||||||
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,18 +896,16 @@ 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;
|
||||||
|
|
12
lib/stream.h
12
lib/stream.h
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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{
|
||||||
|
|
22
lib/util.cpp
22
lib/util.cpp
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()){
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);}
|
||||||
|
@ -648,6 +688,7 @@ void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::sessType Controller::statSession::getSessType(){
|
Controller::sessType Controller::statSession::getSessType(){
|
||||||
|
@ -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];
|
||||||
|
|
|
@ -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;}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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[]){
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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
|
||||||
|
|
16
src/io.cpp
16
src/io.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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";}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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
46
test/status.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue