Various fixes, among which:

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

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

View file

@ -115,7 +115,7 @@ namespace Comms{
do{
for (size_t i = firstValid(); i < endValid(); i++){
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 (firstValid() < endValid() && ++c < 10);
@ -174,7 +174,7 @@ namespace Comms{
dataAccX.addField("lastsecond", RAX_64UINT);
dataAccX.addField("down", 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("connector", RAX_STRING, 20);
dataAccX.addField("crc", RAX_32UINT);
@ -277,8 +277,13 @@ namespace Comms{
up.set(_up, idx);
}
std::string Statistics::getHost() const{return host.string(index);}
std::string Statistics::getHost(size_t idx) const{return (master ? host.string(idx) : "");}
std::string Statistics::getHost() const{
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, size_t idx){
if (!master){return;}

View file

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

View file

@ -33,13 +33,22 @@
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h> // for va_list
bool Util::Config::is_active = false;
bool Util::Config::is_restarting = false;
static Socket::Server *serv_sock_pointer = 0;
uint32_t Util::Config::printDebugLevel = DEBUG; //
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;
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;
if (!is_active && ++ctr > 4){BACKTRACE;}
#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;
default:
switch (sigInfo->si_code){

View file

@ -13,6 +13,8 @@
/// Contains utility code, not directly related to streaming media
namespace Util{
extern char exitReason[256];
void logExitReason(const char * format, ...);
/// Deals with parsing configuration from commandline options.
class Config{
@ -27,10 +29,6 @@ namespace Util{
static bool is_restarting; ///< Set to true when restarting, set to false on boot.
static uint32_t printDebugLevel;
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
Config();
Config(std::string cmd);

View file

@ -163,7 +163,7 @@ static inline void show_stackframe(){}
// assumed
#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_RECORDSIZE 576
@ -214,6 +214,7 @@ static inline void show_stackframe(){}
#define SHM_TRIGGER "MstTRGR%s" //%s trigger name
#define SEM_LIVE "/MstLIVE%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 SHM_CAPA "MstCapa"
#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.
#define SIMULATED_LIVE_BUFFER 7000
/// The time between virtual audio "keyframes"
#define AUDIO_KEY_INTERVAL 2047
#define STAT_EX_SIZE 177
#define PLAY_EX_SIZE 2 + 6 * SIMUL_TRACKS

View file

@ -11,10 +11,6 @@
#include <fstream>
#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{
char Magic_Header[] = "DTSC";
char Magic_Packet[] = "DTPD";
@ -448,6 +444,14 @@ namespace DTSC{
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.
///\return The track id of this packet.
size_t Packet::getTrackId() const{
@ -544,6 +548,32 @@ namespace DTSC{
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 invalid object if this indice doesn't exist or this isn't an object type.
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
/// already populated.
void Meta::refresh(){
if (!stream.getPointer("tracks")){
if (!stream.isReady() || !stream.getPointer("tracks")){
INFO_MSG("No track pointer, not refreshing.");
return;
}
@ -1480,6 +1510,15 @@ namespace DTSC{
/// Adds a track to the metadata structure.
/// 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){
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 +
(TRACK_FRAGMENT_OFFSET + (TRACK_FRAGMENT_RECORDSIZE * fragCount)) +
(TRACK_KEY_OFFSET + (TRACK_KEY_RECORDSIZE * keyCount)) +
@ -1488,7 +1527,6 @@ namespace DTSC{
size_t tNumber = trackList.getPresent();
char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_TM, streamName.c_str(), getpid(), tNumber);
Track &t = tracks[tNumber];
@ -1511,7 +1549,7 @@ namespace DTSC{
trackList.setInt(trackPidField, getpid(), tNumber);
trackList.setInt(trackSourceTidField, INVALID_TRACK_ID, tNumber);
if (setValid){validateTrack(tNumber);}
trackLock.post();
return tNumber;
}
@ -1740,6 +1778,7 @@ namespace DTSC{
}
std::string Meta::getLang(size_t trackIdx) const{
const DTSC::Track &t = tracks.at(trackIdx);
if (!t.track.isReady()){return "";}
return t.track.getPointer(t.trackLangField);
}
@ -1864,8 +1903,14 @@ namespace DTSC{
if (getType(*it) != "video"){continue;}
DTSC::Parts p(parts(*it));
size_t ctr = 0;
int64_t prevOffset = 0;
bool firstOffset = true;
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;}
}
}
@ -1903,7 +1948,7 @@ namespace DTSC{
std::string(trackList.getPointer(trackEncryptionField, 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 (res.count(i) && !tracks.at(i).parts.getPresent()){res.erase(i);}
}
@ -2032,10 +2077,12 @@ namespace DTSC{
curJitter = 0;
}
if (t > lastTime + 2500){
if ((x % 4) == 0 && maxJitter > 50 && curJitter < maxJitter - 50){
HIGH_MSG("Jitter lowered from %" PRIu64 " to %" PRIu64 " ms", maxJitter, curJitter);
maxJitter = curJitter;
curJitter = 0;
if ((x % 4) == 0){
if (maxJitter > 50 && curJitter < maxJitter - 50){
MEDIUM_MSG("Jitter lowered from %" PRIu64 " to %" PRIu64 " ms", maxJitter, curJitter);
maxJitter = curJitter;
}
curJitter = maxJitter*0.75;
}
++x;
trueTime[x % 8] = curMs;
@ -2055,7 +2102,11 @@ namespace DTSC{
// Postive jitter = packets arriving too late.
// We need to delay playback at least by this amount to account for it.
if ((uint64_t)jitter > maxJitter){
HIGH_MSG("Jitter increased from %" PRIu64 " to %" PRId64 " ms", maxJitter, jitter);
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);
}
maxJitter = (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();}
streamPage.master = true;
}
stream = Util::RelAccX();
trackList = Util::RelAccX();
streamPage.close();
tM.clear();
tracks.clear();
@ -2875,6 +2928,7 @@ namespace DTSC{
const Util::RelAccX &pages = tracks.at(idx).pages;
size_t res = pages.getStartPos();
for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){
if (pages.getInt("avail", i) == 0){continue;}
if (pages.getInt("firsttime", i) > time){break;}
res = i;
}
@ -2887,6 +2941,7 @@ namespace DTSC{
const Util::RelAccX &pages = tracks.at(idx).pages;
size_t res = pages.getStartPos();
for (size_t i = pages.getStartPos(); i < pages.getEndPos(); ++i){
if (pages.getInt("avail", i) == 0){continue;}
if (pages.getInt("firstkey", i) > keyNum){break;}
res = i;
}

View file

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

View file

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

View file

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

View file

@ -325,42 +325,50 @@ pid_t Util::Procs::StartPiped(const char *const *argv, int *fdin, int *fdout, in
pid = fork();
if (pid == 0){// child
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
for (std::set<int>::iterator it = Util::Procs::socketList.begin();
it != Util::Procs::socketList.end(); ++it){
close(*it);
}
if (!fdin){
dup2(devnull, STDIN_FILENO);
}else if (*fdin == -1){
close(pipein[1]); // close unused write end
dup2(pipein[0], STDIN_FILENO);
close(pipein[0]);
}else if (*fdin != STDIN_FILENO){
dup2(*fdin, STDIN_FILENO);
}
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);}
//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(100, 0);
dup2(101, 1);
dup2(102, 2);
close(100);
close(101);
close(102);
//There! Now we normalized our stdio
// Because execvp requires a char* const* and we have a const char* const*
execvp(argv[0], (char *const *)argv);
/*LTS-START*/

View file

@ -354,7 +354,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
switch (headertype){
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);
timestamp = indata[i++] * 256 * 256;
timestamp += indata[i++] * 256;
@ -372,7 +375,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
msg_stream_id += indata[i++] * 256 * 256 * 256;
break;
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);
if (!allow_short){WARN_MSG("Warning: Header type 0x40 with no valid previous chunk!");}
timestamp = indata[i++] * 256 * 256;
@ -391,7 +397,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
msg_stream_id = prev.msg_stream_id;
break;
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);
if (!allow_short){WARN_MSG("Warning: Header type 0x80 with no valid previous chunk!");}
timestamp = indata[i++] * 256 * 256;
@ -435,7 +444,10 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer &buffer){
// read extended timestamp, if necessary
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);
timestamp += indata[i++] * 256 * 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
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
if (prev.len_left > 0){
data = prev.data + buffer.remove(real_len); // append the data and remove from buffer

View file

@ -58,12 +58,13 @@ namespace RTP{
if ((payload[0] & 0x1F) == 12){return;}
/// \todo This function probably belongs in DMS somewhere.
if (payloadlen + getHsize() + 2 <= maxDataLen){
data[1] &= 0x7F; // setting the RTP marker bit to 0
if (lastOfAccesUnit){
data[1] |= 0x80; // setting the RTP marker bit to 1
}
uint8_t nal_type = (payload[0] & 0x1F);
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);
callBack(socket, data, getHsize() + payloadlen, channel);
@ -239,6 +240,10 @@ namespace RTP{
sendVP8(socket, callBack, payload, payloadlen, channel);
return;
}
if (codec == "VP9"){
sendVP8(socket, callBack, payload, payloadlen, channel);
return;
}
if (codec == "HEVC"){
unsigned long sent = 0;
while (sent < payloadlen){
@ -414,6 +419,18 @@ namespace RTP{
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;}
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.
void Sorter::addPacket(const Packet &pack){
if (!rtpSeq){rtpSeq = pack.getSequence();}
// packet is very early - assume dropped after 30 packets
while ((int16_t)(rtpSeq - ((uint16_t)pack.getSequence())) < -30){
// packet is very early - assume dropped after 150 packets
while ((int16_t)(rtpSeq - ((uint16_t)pack.getSequence())) < -150){
WARN_MSG("Giving up on packet %u", rtpSeq);
++rtpSeq;
++lostTotal;
@ -574,7 +591,10 @@ namespace RTP{
if (M.getType(tid) == "video" || M.getCodec(tid) == "MP2" || M.getCodec(tid) == "MP3"){
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),
@ -627,6 +647,9 @@ namespace RTP{
if (codec == "VP8"){
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.
if (codec == "ALAW" || codec == "opus" || codec == "PCM" || codec == "ULAW"){
DTSC::Packet nextPack;
@ -902,6 +925,51 @@ namespace RTP{
// Header data? Compare to init, set if needed, and throw away
uint8_t nalType = (buffer[4] & 0x1F);
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){
case 6: // SEI
return;
@ -950,80 +1018,26 @@ namespace RTP{
}
return;
case 5:{
// @todo add check if ppsData and spsData are not empty?
static Util::ResizeablePointer tmp;
tmp.assign(0, 0);
//If this is a keyframe and we have no buffer yet, prepend the SPS/PPS
if (!h264OutBuffer.size()){
char sizeBuffer[4];
Bit::htobl(sizeBuffer, spsData.size());
h264OutBuffer.append(sizeBuffer, 4);
h264OutBuffer.append(spsData.data(), spsData.size());
char sizeBuffer[4];
Bit::htobl(sizeBuffer, spsData.size());
tmp.append(sizeBuffer, 4);
tmp.append(spsData.data(), spsData.size());
Bit::htobl(sizeBuffer, ppsData.size());
tmp.append(sizeBuffer, 4);
tmp.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);
Bit::htobl(sizeBuffer, ppsData.size());
h264OutBuffer.append(sizeBuffer, 4);
h264OutBuffer.append(ppsData.data(), ppsData.size());
}
// Fill the new DTSC packet, buffer it.
DTSC::Packet nextPack;
nextPack.genericFill(newTs, offset, trackId, tmp, tmp.size(), 0, isKey);
packCount++;
outPacket(nextPack);
return;
//Note: no return, we still want to buffer the packet itself, below!
}
default: // others, continue parsing
break;
}
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.
DTSC::Packet nextPack;
nextPack.genericFill(newTs, offset, trackId, buffer, len, 0, isKey);
packCount++;
outPacket(nextPack);
//Buffer the packet
h264OutBuffer.append(buffer, len);
}
/// Handles a single H264 packet, checking if others are appended at the end in Annex B format.

View file

@ -77,6 +77,7 @@ namespace RTP{
Packet(const char *dat, uint64_t len);
const char *getData();
char *ptr() const{return data;}
std::string toString() const;
};
/// Sorts RTP packets, outputting them through a callback in correct order.
@ -163,6 +164,9 @@ namespace RTP{
h265::initData hevcInfo; ///< For HEVC init parsing
Util::ResizeablePointer fuaBuffer; ///< For H264/HEVC FU-A 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 handleH264Single(uint64_t ts, const char *buffer, const uint32_t len, bool isKey);
void handleH264Multi(uint64_t ts, char *buffer, const uint32_t len);

View file

@ -462,9 +462,18 @@ namespace SDP{
myMeta->setCodec(tid, "HEVC");
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"){
myMeta->setCodec(tid, "opus");
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 == "PCMU"){myMeta->setCodec(tid, "ULAW");}
@ -484,7 +493,10 @@ namespace SDP{
myMeta->setCodec(tid, "PCM");
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()){
ERROR_MSG("Unsupported RTP mapping: %s", mediaType.c_str());
}else{
@ -669,6 +681,9 @@ namespace SDP{
if (M->getType(tid) == "video" || M->getCodec(tid) == "MP2" || M->getCodec(tid) == "MP3"){
return 90.0;
}
if (M->getCodec(tid) == "opus"){
return 48.0;
}
return ((double)M->getRate(tid) / 1000.0);
}

View file

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

View file

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

View file

@ -462,7 +462,7 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
while (!streamAlive(streamname) && ++waiting < 240){
Util::wait(250);
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;
}
}
@ -548,7 +548,7 @@ JSON::Value Util::getInputBySource(const std::string &filename, bool isProvider)
/// streamname MUST be pre-sanitized
/// 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.
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)){
std::string payload = streamname + "\n" + 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
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
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());
// Start output.
std::string dLvl = JSON::Value(debugLvl).asString();
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;
// Cache return value so we can do some cleaning before we return
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.
/// It is necessary to follow up with a selectDefaultTracks() call to strip unsupported
/// 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;
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){
// Comma-separated list, recurse.
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);
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(),
M.getType(idx).c_str(), M.getCodec(idx).c_str());
return result;
@ -720,23 +726,22 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
Util::stringToLower(trackLow);
if (trackLow == "all" || trackLow == "*"){
// 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++){
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;
}
if (trackLow == "highbps" || trackLow == "bestbps" || trackLow == "maxbps"){
// 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;
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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
if (currRate < Trk.bps){
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (currRate < M.getBps(*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"){
// 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;
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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
if (currRate > Trk.bps){
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (currRate > M.getBps(*it)){
currVal = *it;
currRate = Trk.bps;
currRate = M.getBps(*it);
}
}
}
if (currVal != INVALID_TRACK_ID){result.insert(currVal);}
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] == '>'){
unsigned int bpsVal;
uint64_t targetBps = 0;
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 (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 (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 (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){
targetBps >>= 3;
// 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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
if (trackLow[0] == '>' && Trk.bps > targetBps){result.insert(*it);}
if (trackLow[0] == '<' && Trk.bps < targetBps){result.insert(*it);}
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (trackLow[0] == '>' && M.getBps(*it) > targetBps){result.insert(*it);}
if (trackLow[0] == '<' && M.getBps(*it) < targetBps){result.insert(*it);}
}
}
return result;
}
unsigned int resX, resY;
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){
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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
uint64_t trackArea = Trk.width * Trk.height;
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
uint64_t trackArea = M.getWidth(*it)*M.getHeight(*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;
}
}
// approx bitrate matching
//approx bitrate matching
{
unsigned int bpsVal;
uint64_t targetBps = 0;
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 (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){
targetBps >>= 3;
// 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;
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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
if (currVal == INVALID_TRACK_ID || (Trk.bps >= targetBps && currDist > (Trk.bps - targetBps)) ||
(Trk.bps < targetBps && currDist > (targetBps - Trk.bps))){
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
if (currVal == INVALID_TRACK_ID || (M.getBps(*it) >= targetBps && currDist > (M.getBps(*it)-targetBps)) || (M.getBps(*it) < targetBps && currDist > (targetBps-M.getBps(*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 (trackLow == "highres" || trackLow == "bestres" || trackLow == "maxres"){
// select highest resolution track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID;
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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
uint64_t trackRes = Trk.width * Trk.height;
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
uint64_t trackRes = M.getWidth(*it)*M.getHeight(*it);
if (currRes < trackRes){
currVal = *it;
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"){
// select lowest resolution track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID;
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++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
uint64_t trackRes = Trk.width * Trk.height;
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
uint64_t trackRes = M.getWidth(*it)*M.getHeight(*it);
if (currRes > trackRes){
currVal = *it;
currRes = trackRes;
@ -918,18 +896,16 @@ std::set<size_t> Util::findTracks(const DTSC::Meta &M, const std::string &trackT
unsigned int resX, resY;
if (sscanf(trackLow.c_str(), "~%ux%u", &resX, &resY) == 2){
// select nearest resolution track of this type
std::set<size_t> validTracks = capa?getSupportedTracks(M, capa):M.getValidTracks();
size_t currVal = INVALID_TRACK_ID;
uint64_t currDist = 0;
uint64_t targetArea = resX * resY;
std::set<size_t> validTracks = getSupportedTracks(M, capa);
uint64_t targetArea = resX*resY;
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
const DTSC::Track &Trk = M.tracks.at(*it);
if (!trackType.size() || Trk.type == trackType || Trk.codec == trackType){
uint64_t trackArea = Trk.width * Trk.height;
if (currVal == INVALID_TRACK_ID || (trackArea >= targetArea && currDist > (trackArea - targetArea)) ||
(trackArea < targetArea && currDist > (targetArea - trackArea))){
if (!trackType.size() || M.getType(*it) == trackType || M.getCodec(*it) == trackType){
uint64_t trackArea = M.getWidth(*it)*M.getHeight(*it);
if (currVal == INVALID_TRACK_ID || (trackArea >= targetArea && currDist > (trackArea-targetArea)) || (trackArea < targetArea && currDist > (targetArea-trackArea))){
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
// convert 2-character language codes into 3-character language codes
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++){
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);
Util::stringToLower(codecLow);
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;
@ -1021,7 +1011,7 @@ std::set<size_t> Util::getSupportedTracks(const DTSC::Meta &M, const JSON::Value
std::string encryptionType = M.getEncryption(*it);
encryptionType = encryptionType.substr(0, encryptionType.find('/'));
bool found = false;
jsonForEach(capa["encryption"], itb){
jsonForEachConst(capa["encryption"], itb){
if (itb->asStringRef() == encryptionType){
found = true;
break;
@ -1067,7 +1057,8 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
}
/*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
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);
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
for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
@ -1103,22 +1090,22 @@ std::set<size_t> Util::wouldSelect(const DTSC::Meta &M, const std::map<std::stri
/*LTS-START*/
if (!capa.isMember("codecs")){
for (std::set<size_t>::iterator trit = validTracks.begin(); trit != validTracks.end(); trit++){
const DTSC::Track &Trk = M.tracks.at(*trit);
bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){
jsonForEachConst(capa["exceptions"], ex){
if (ex.key() == "codec:" + Trk.codec){
problems = !Util::checkException(*ex, UA);
break;
bool problems = false;
if (capa.isMember("exceptions") && capa["exceptions"].isObject() &&
capa["exceptions"].size()){
jsonForEachConst(capa["exceptions"], ex){
if (ex.key() == "codec:" + M.getCodec(*trit)){
problems = !Util::checkException(*ex, UA);
break;
}
}
}
}
// if (!allowBFrames && M.hasBFrames(*trit)){problems = true;}
if (problems){continue;}
if (noSelAudio && Trk.type == "audio"){continue;}
if (noSelVideo && Trk.type == "video"){continue;}
if (noSelSub && (Trk.type == "subtitle" || Trk.codec == "subtitle")){continue;}
result.insert(*trit);
if (!allowBFrames && M.hasBFrames(*trit)){problems = true;}
if (problems){continue;}
if (noSelAudio && M.getType(*trit) == "audio"){continue;}
if (noSelVideo && M.getType(*trit) == "video"){continue;}
if (noSelSub && (M.getType(*trit) == "subtitle" || M.getCodec(*trit) == "subtitle")){continue;}
result.insert(*trit);
}
return result;
}

View file

@ -9,6 +9,8 @@
#include "util.h"
#include <string>
const JSON::Value empty;
namespace Util{
void streamVariables(std::string &str, const std::string &streamname, const std::string &source = "");
std::string getTmpFolder();
@ -18,7 +20,7 @@ namespace Util{
bool isProvider = false,
const std::map<std::string, std::string> &overrides = std::map<std::string, std::string>(),
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 getGlobalConfig(const std::string &optionName);
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);
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 = "");
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 = "",
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,
JSON::Value capa = JSON::Value(), const std::string &UA = "");
const JSON::Value &capa = empty, const std::string &UA = "", uint64_t seekTarget = 0);
class DTSCShmReader{
public:

View file

@ -1162,6 +1162,14 @@ namespace TS{
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.
/// 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.
@ -1201,16 +1209,12 @@ namespace TS{
}
}
if (vidTrack == -1){vidTrack = *(selectedTracks.begin());}
size_t pcrPid = M.getID(vidTrack);
if (pcrPid < 255){pcrPid += 255;}
PMT.setPCRPID(pcrPid);
PMT.setPCRPID(getUniqTrackID(M, vidTrack));
PMT.setProgramInfoLength(0);
ProgramMappingEntry entry = PMT.getEntry(0);
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
std::string codec = M.getCodec(*it);
size_t pkgId = M.getID(*it);
if (pkgId < 255){pkgId += 255;}
entry.setElementaryPid(pkgId);
entry.setElementaryPid(getUniqTrackID(M, *it));
entry.setESInfo("");
if (codec == "H264"){
entry.setStreamType(0x1B);

View file

@ -245,6 +245,8 @@ namespace TS{
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 *createSDT(const std::string &streamName, int contCounter = 0);

View file

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

View file

@ -276,7 +276,7 @@ namespace Util{
/// 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
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];
FILE *output = fdopen(in, "r");
@ -347,7 +347,7 @@ namespace Util{
while (j < 1023 && buf[j] != '\n' && buf[j] != 0){++j;}
buf[j] = 0;
// print message
if (callback){callback(kind, message, strmNm, true);}
if (callback){callback(kind, message, strmNm, JSON::Value(progpid).asInt(), true);}
color_msg = color_end;
if (colored){
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);}
std::string FieldAccX::string(size_t recordNo) const{
std::string res(src->getPointer(field, recordNo));
if (res.size() > field.size){res.resize(field.size);}
return res;
return std::string(src->getPointer(field, recordNo));
}
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);}
@ -396,7 +398,9 @@ namespace Util{
void FieldAccX::set(const std::string &val, size_t recordNo){
char *place = src->getPointer(field, recordNo);
memcpy(place, val.data(), std::min((size_t)field.size, val.size()));
place[std::min((size_t)field.size - 1, val.size())] = 0;
if ((field.type & 0xF0) == RAX_STRING){
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.
@ -597,7 +601,7 @@ namespace Util{
char *ptr = getPointer(it->first, i);
size_t sz = getSize(it->first, i);
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 << " ";
if (ptr[j] == 0x00){
zeroCount++;
@ -770,13 +774,13 @@ namespace Util{
}
void RelAccX::setString(const RelAccXFieldData &fd, const std::string &val, uint64_t recordNo){
if ((fd.type & 0xF0) != RAX_STRING){
WARN_MSG("Setting non-string");
if ((fd.type & 0xF0) != RAX_STRING && (fd.type & 0xF0) != RAX_RAW){
WARN_MSG("Setting non-string data type to a string value");
return;
}
char *ptr = RECORD_POINTER;
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.

View file

@ -20,7 +20,7 @@ namespace Util{
class DataCallback{
public:
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 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();
/// Holds type, size and offset for RelAccX class internal data fields.
@ -194,6 +194,7 @@ namespace Util{
FieldAccX(RelAccX *_src = NULL, RelAccXFieldData _field = RelAccXFieldData());
uint64_t uint(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(const std::string &val, size_t recordNo = 0);